我在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()
}
}
}
}
控制器和服务代码似乎工作正常(虽然上面的代码已经简化),但有时会导致数据库会话在过去被卡住了#39;
我们假设我有一个UserProperties
个实例,其属性accountId
在应用程序的其他位置从1
更新为20
,而rent
动作在异步块中等待。由于异步块最终会以这种或那种方式终止(它可能成功,失败或超时),因此应用程序有时会使用UserProperties
获得陈旧的accountId: 1
实例。让我们说我刷新更新后的用户属性页面,我会看到accountId: 1
每10次刷新大约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)
进行注释。
过时的会话似乎每次都没出现,到目前为止我们的解决方案是每天晚上重新启动服务器(应用程序目前用户很少),但是错误令人讨厌并且原因很难确定。我的猜测是,由于异步代码,数据库事务不会被提交或回滚,但是GORM,Spring和Hibernate有许多可能会卡住的角落和缝隙。
我们正在使用Postgres 9.4.1(开发机器上的9.2,同样的问题),Grails 2.5.0,Hibernate插件4.3.8.1,Tomcat 8,Cache插件1.1.8,Hibernate Filter插件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
}