我在使用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。这是一个令人讨厌的问题,一切看起来都很好,直到你的应用程序开始在完全不相关的部分表现得很奇怪。