In this post, I would like to share my investigation of the RequestContextHolder logic.
The library developed by another team did not work as expected in our REST API microservice. After the source code investigation, we realized that the problem is related to the RequestContextHolder. The library relies on RequestContextHolder to get HTTP request-related information, and implementation uses thread-local variables. So it will work well when we have just one thread that processes the request end to end. But in our microservice, we use separate Thread Pools for Spring service beans with set-up timeouts and rate limiters. Can we still use the library as it is and tune our microservice?
The complete source code for fragments below is available on GitHub.
RequestContextHolder is holder class to expose the web request in the form of a thread-bound RequestAttributes object.
So you can get information about web requests in any place in the code:
RequestContextHolder uses ThreadLocal and InheritableThreadLocal to store the value associated with each thread separately. Based on the inheritable flag, the class uses one or another.
Let’s look at the Java Doc again:
The request will be inherited by any child threads spawned by the current thread if the inheritable flag is set to true.
The flag is a member of the parent DispatcherServlet class. We can set it directly on the bean of DispatcherServlet:
setThreadContextInheritable seems just like what we need. But would this implementation inherit the ThreadLocal values for a real-world environment where we don’t create thread directly but re-use threads provided by Thread Pools?
Tests
For the test, let’s create a simple controller which sets Attribute and tries to read its value in child Thread. For each HTTP request Attribute, we set the value incrementing AtomicInteger, so it will never be the same.
And then let’s create a simple test that verifies that value is the same only two first times for the Thread Pool, which has at most two threads, and all further requests do not get the updated value.
The test succeeds, which means that RequestContextHolder does not work well with Thread Pool.
Now let’s look at how InheritableThreadLocal works.
InheritableThreadLocal
Dive deep in code
Each Java Thread has two variables - threadLocals and inheritableThreadLocals:
ThreadLocalMap is a special implementation of Hash Map where the key is ThreadLocal<?> k and value is Object v designed only for the ThreadLocal. It does not follow the same collision resolution technique as the HashMap but simply puts value on the next available slot.
How we can get and set ThreadLocal value for the current thread?
Both methods first get the ThreadLocalMap for the current thread and then get the value based on this as ThreadLocal. The only difference is how we get the ThreadLocalMap:
Now let’s find how inheritThreadLocals is created. The Constructor of the Thread class has the inheritThreadLocals parameter which is true by default for public constructors:
When inheritThreadLocals is true, constructor creates the inheritableThreadLocals based on the values of the parent (current) thread. It means that the thread copies the inheritableThreadLocals value only one time and never syncs inheritableThreadLocals back with parent thread. Because of that, changes in the parent thread inheritableThreadLocals are not reflected. But ThreadLocal.createInheritedMapdoes not perform “deep copy”, so changes in the ThreadLocal values themselves will be visible to the child Thread.
Based on the source code analysis, InheritableThreadLocal will not work well with Thread Pools, where we do not create a thread every time and can re-use the thread for the next task.
Tests
Now let’s verify our findings with unit tests.
We create a single-thread Thread Pool so at most one thread should be created. Then we submit first Runnable and wait until it will finish. In that Runnable, we get ThreadLocal value and save it in the dataFromChildThread. Thread Pool creates a new thread and copies the inheritableThreadLocals from the parent thread.
Then we change ThreadLocal value and submit the second Runnable which has the same logic. This time no new Thread will be created and the Thread Pool will re-use the existing Thread. If InheritableThreadLocal can refresh the value each time we would see updated value from the second Runnable. But the test succeeded and it means that we still got the original data we saw in the first Runnable.
Also, let’s verify if the child thread can get updated value inside the Object stored in ThreadLocal (because child thread doesn’t do deep copy for the inheritableThreadLocal):
We use to control data flow and wait until the thread starts.
Then we modify data inside the threadLocalData and release the second CountDownLatch which is used to suspend child Thread until we update the value.
As expected child thread can see the updated value.
How can we overcome the InheritableThreadLocal limitation? We can use a library developed in Alibaba.
TransmittableThreadLocal(TTL): The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.
Library provides three ways to transmit value, even using a thread pool:
1. Decorate Runnable and Callable
Decorate input Runnable and Callable by TtlRunnable and TtlCallable. Each time we create Runnable or Callable, we need to decorate it. TtlRunnable wrapper captures values on its creation and wraps the run method. First, it updates thread local values with captured data, runs original Runnable logic, and finally restores original thread local values:
Example of wrapping:
2. Decorate thread pool
Instead of decorating Runnable or Callable, we can decorate the Thread Pool itself only one time when we create it - the wrapper will automatically instantiate the TtlRunnable.:
Example of wrapping:
3. Use Java Agent to decorate thread pool implementation class
In this approach, no decoration needed, and you have to only add -javaagent:path/to/transmittable-thread-local-2.x.y.jar to the Java command options.
Test
Let’s do the same test as we did with InheritableThreadLocal:
Now second Runnable sumbitted to the single-thread Thread Pool got updated Thread Local value.
Everything works as expected.
Integrating Transmittable Thread Local to Spring
Unfortunately, RequestContextHolder has only static methods and is used directly in the FrameworkServlet, so it is not so easy to change RequestContextHolder logic to use TransmittableThreadLocal. Some methods of FrameworkServlet which uses RequestContextHolder are protected final (like the processRequest()), so we have to override higher-level methods and, in the end, will touch too many methods.
It might be more effective just to copy the whole FrameworkServlet, RequestContextHolder and DispatcherServlet source code and change the NamedInheritableThreadLocal to TransmittableThreadLocal.
Let’s run a similar test for the updated version for the RequestContextHolder:
Now all ten requests got updated value in the RequestAttributes.
Now let’s verify if logic works fine with Spring Async MVC. It is enabled by default in the recent Spring versions. The controller and test code are below:
Everything works fine as well, but there is some difference - RequestAttributes Object is not the same even it has the same data (isChildThreadHasSameRequestAttributes is false).
Let’s look at the official Spring documentation:
Servlet 3 web application can call request.startAsync() and use the returned AsyncContext to continue to write to the response from some other separate thread. At the same time from a client’s perspective the request still looks like any other HTTP request-response interaction. It just takes longer to complete. The following is the sequence of events:
Client sends a request
Servlet container allocates a thread and invokes a servlet in it
The servlet calls request.startAsync(), saves the AsyncContext, and returns
The container thread is exited all the way but the response remains open
Some other thread uses the saved AsyncContext to complete the response
Client receives the response
In the middle of the data flow, Spring creates ServletRequestAttributes again based on the HttpServletRequest and HttpServletResponse so we don’t need to wrap the AsyncTaskExecutor, which is used for the async processing.
GitHub code also contains similar tests for request handler, which uses two Threads, so it needs to pass RequestAttribures to the grand-child Thread.
Conclusion
We can use the TransmittableThreadLocal to fix default InheritableThreadLocal Java implementation.
But for the Spring RequestContextHolder, we have to modify too much source code, which can be troublesome for a production project, and you have to do it each time you upgrade the Spring version to avoid any side effects.
It feels more reasonable to pass Request related information implicitly as method parameters if you need to use it in logic executed inside Thread Pool. When we have to use a 3rd party library in the existing application, we might consider cloning FrameworkServlet to avoid many changes in the code. Even in this case, we manually set RequestAttributes to the RequestContextHolder just before calling the library function.
Outside of Spring’s internal usage, it works fine, and for the Decorate thread pool option, we don’t need to do many changes, only add TtlExecutors.getTtlExecutorService to Thread Pool creation code.