当使用异步控制器和长轮询时,Hibernate会话获取过时的数据



我在使用Grails中的异步控制器时遇到一个问题。考虑以下控制器:

@Transactional(readOnly=true)
class RentController  {
    def myService

    UserProperties props
    def beforeInterceptor = {
        this.props = fetchUserProps() 
    }

    //..other actions
    @Transactional
    def rent(Long id) {
        //check some preconditions here, calling various service methods...
        if (!allOk) {
            render status: 403, text: 'appropriate.message.key'
            return
        }
        //now we long poll because most of the time the result will be
        //success within a couple of seconds
        AsyncContext ctx = startAsync()
        ctx.timeout = 5 * 1000 * 60 + 5000
        ctx.start {
            try {
                //wait for external service to confirm - can take a long time or even time out
                //save appropriate domain objects if successful
                //placeRental is also marked with @Transactional (if that makes any difference)
                def result = myService.placeRental() 
                if (result.success) {
                    render text:"OK", status: 200
                } else {
                    render status:400, text: "rejection.reason.${result.rejectionCode}"
                }
            } catch (Throwable t) {
                log.error "Rental process failed", t
                render text: "Rental process failed with exception ${t?.message}", status: 500
            } finally {
                ctx.complete()
            }
        }
    }
}

控制器和服务代码看起来工作得很好(尽管上面的代码是简化的),但有时会导致数据库会话"卡在过去"。

假设我有一个UserProperties实例,其属性accountId在应用程序的其他地方从1更新到20,而rent动作正在异步块中等待。当异步块最终以这样或那样的方式终止时(它可能成功,失败或超时),应用程序有时会得到一个带有accountId: 1的陈旧UserProperties实例。假设我刷新了更新后的用户属性页面,大约每刷新10次就会看到1次accountId: 1,而其余时间则是20——这是在我的开发机器上,没有其他人访问应用程序(尽管在生产中可以观察到相同的行为)。我的连接池也有10个连接,所以我怀疑这里可能存在关联。

其他奇怪的事情会发生-例如,我将从行动中获得StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect),做一些像render (UserProperties.list() as JSON)一样简单的事情-在响应已经渲染之后(成功地除去日志中的噪音),尽管行动被@Transactional(readOnly=true)注释。

一个陈旧的会话似乎不是每次都出现,到目前为止,我们的解决方案是每天晚上重启服务器(应用程序现在很少有用户),但错误是恼人的,原因很难查明。我的猜测是,由于异步代码,DB事务不会被提交或回滚,但是GORM, Spring和Hibernate有许多可能卡住的角落和缝隙。

我们正在使用Postgres 9.4.1(开发机器上的9.2,同样的问题),Grails 2.5.0, Hibernate插件4.3.8.1,Tomcat 8,缓存插件1.1.8,Hibernate过滤器插件0.3.2和审计日志插件1.0.1(其他东西也很明显,但这感觉可能是相关的)。我的数据源配置包含:

hibernate {
    cache.use_second_level_cache = true
    cache.use_query_cache = false
    cache.region.factory_class = 'org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory'
    singleSession = true
    flush.mode = 'manual'
    format_sql = true
}

Grails bug。这是一个令人讨厌的问题,一切看起来都很好,直到你的应用程序开始在完全不相关的部分表现得很奇怪。

最新更新