JSON conversion and request thread reuse
Wrapping up (I hope) my experience with custom JSON conversions in Grails… The day before we were supposed to hit a major release milestone last week, I finally caught sight of a random bug that had been driving us nuts. For some AJAX requests, seemingly at random, the object that has the transient properties was not being rendered with them. Instead, it had only the regular, persistent properties and full representations of its associated objects rather than just the normal stubs of related objects with just the type and id. In other words, it was using deep JSON rendering, and this was overriding the custom JSON ObjectMarshaller that we had set for the class. But why was it doing it seemingly at random? This is where luck and experience combined to make the solution clear rather quickly and avoided some painful slogging through request handling on the server.
The luck part was that I remembered seeing a checkin from a teammate a couple of weeks ago where he got rid of direct use of a grails.converters.deep.JSON object (which is deprecated) and instead switched to a normal grails.converters.JSON object with a use('deep') statement:
def get = {
def o = OurClass.get(params.id);
JSON.use("deep")
render(contentType: "application/json", text: o as JSON)
}
This change was in a different controller for a different domain object though. Why was it affecting our other domain class? That’s where the experience part came in. A few years ago I had to track down why hibernate transactions would fail seemingly at random in another web app when running under JBoss. I could see why an initial transaction would fail (we’d get a timeout and not rollback properly), but there was no reason why later requests would cause failures. That’s when I discovered the downside of request thread pooling. Under most web contains, HTTP requests are handled by a pool of reusable threads. That means changes to thread local variables stick around between requests unless explicitly reset. In the case of the Hibernate bug, since the session was not getting closed properly, it wasn’t getting fully reset and cleared out, so subsequent requests that used that thread were getting the old session and failing because there was no way to get it back to a good state.
For the current JSON conversion bug, the JSON.use method was calling ConvertersConfigurationHolder.setTheadLocalConverterConfiguration(...), thus making the deep converter stick around for that thread. So when future requests came in to any controller on that thread, all JSON conversion calls were made with the deep configuration.
The solution is straightforward. Use the JSON.use(String, Closure) call instead when you want to make a deep call:
def get = {
def o = OurClass.get(params.id);
JSON.use("deep") {
render(contentType: "application/json", text: o as JSON)
}
}
This sets the configuration only for the closure, saving you from thread-based side effects later on.