Accessing request scoped beans in a multi-threaded web application
Asked Answered
H

1

9

Scenario: We have a Spring managed web application that runs inside Websphere. (Spring 3.0.x, WAS 7) The webapp leverages Websphere's work manager via Spring's WorkManagerTaskExecutor (configured with a thread pool size of 10) to execute computational intensive db read operations. So basically, a request comes in to generate, lets say, 10 different documents. To generate the documents only db reads are needed to gather/process the data. So we basically spawn 10 threads to process the 10 documents and at the end gather the 10 documents returned from the 10 workers and merge them and write back one big response to the client. What we identified is that while the 10 threads are gathering/processing the data there are bunch of similar db calls made. So what we came up with is to create an Aspect around the most-executed db methods to cache the response. The aspect is configured as a singleton, and the cache the aspect uses is autowired into the aspect with a scope set to request-scope so that each request has its own cache.

Problem: Now the problem with this approach is that when the threads are doing their db calls and the Aspect is interjects we are getting java.lang.IllegalStateException: No thread-bound request found exception. Which I understand is totally valid as the threads are being executed outside the request context.

Is there a way to circum-navigate this issue? Is it possible to apply the aspect with a request scoped cache to the methods invoked by these threads?

Hensley answered 3/10, 2011 at 16:42 Comment(0)
M
7

I don't think you can do this directly. Even if you could, it would be a bit ugly. However, you can generate a unique request identifier (or even - use the session id, but careful with multiple tabs), and pass that to each processing thread. Then the aspect can use that id as the key to the cache. The cache itself will also be singleton, but there will be Map<String, X>, where String is the ID and X is your cached result.

To make things easier to handle, you can have @Async methods (rather than manually spawning threads), and each @Async method can have the cache id passed as its first parameter.

(Of course, your asynchronous methods should return Future<Result> so that you can collect their results in the request thread)

Misdeal answered 3/10, 2011 at 19:3 Comment(3)
I thought about the approach, but the one drawback that I find is that it will force me to pass the unique request identifier downstream to all the method calls (during the thread execution) that I would like to cache. Also, wouldn't the cache get pretty big after a while? Which would then force me to manage it periodically. Maybe I am missing something here.Hensley
perhaps there is a "thread execution context" that I can use to store my unique request identifier and retrieve it in the aspect without having to pass it myself?Hensley
I managed to get around this issue. I started using SimpleAsyncTaskExecutor instead of WorkManagerTaskExecutor. The benefit is that SimpleAsyncTaskExecutor will never re-use threads. That's only half the solution. The other half of the solution is to use a RequestContextFilter instead of RequestContextListener. RequestContextFilter has a setThreadContextInheritable() method which basically will allow child threads to inherit the parent context.Hensley

© 2022 - 2024 — McMap. All rights reserved.