I recently ran into issues with a third party application that had certain issues with threadlocals. Namely certain parts of the application were using threadlocal to store information, but did not clean up the stored information. This information then caused issues on other part of the application. Now this certainly was a bug in the application, but fixing the actual bug was not possible in the given time frame. I also did not want to introduce some custom filters/valves that could have been used to solve the issue.
The first idea was to disable thread pooling all together and to create a new thread for each request. This would have some kind of overhead, but it would solve the issues with thread local. There is good discussion on Stack Overflow about the performance issues related to creating threads. Result from some testing was that the overhead for most cases would be negligible. This testing obviously only covered pure threads, Tomcat might be doing some things with threads which have impact on performance if thread pooling is disabled.
Disabling thread pooling is not easy, as there does not seem to be direct configuration options for this. There are certain thread related configuration settings for the connectors, but these does not seem to allow disabling pooling. Also there are some reports that the threads never die.
A solution seems to be to switch to using Executors. Executors have been added to Tomcat to give better control over thread pools. Each Executor has its own thread pool and connectors can be associated with executors. This means you can either make multiple connectors share a thread pool or you can create separate thread pools for each connector.
With executors you have a little bit more control over the tread pools. The settings are still not comprehensive enough to really disable pooling of the threads, but it seems to be you can get pretty close. My solution was to make the threads as short lived as possible. So minimise spare threads and make sure the threads are killed as soon as they are released.
<Executor name="myThreadPool" namePrefix="my-" maxThreads="100" minSpareThreads="0" maxIdleTime="1" />
What this means is that by-default we want all threads to die. We don’t want to have threads waiting to serve incoming requests. Once the thread has been idle for 1ms, it should be thrown away.
Associating a connector with executor is fairly simple. Just add executor parameter to the connector. If the executor specified exists, then the thread settings in connector are ignored and executor is used. If the executor specified does not exist this parameter is just ignored and connector settings are used.
There is one caveat related to AJP connectors, which is pretty well documented by Haxx. Namely certain AJP connectors only support limited set of configuration options. If you are using just “AJP/1.3” in the protocol field of the connector, you may end up with JkCoyoteHandler which does does not support the executor parameter. You can go around this issue by explicitly specifying org.apache.coyote.ajp.AjpProtocol in the protocol field.
One more thing to check is the keepAliveTimeout in the AJP connector. This specifies how the connector will wait for another AJP connection before closing down the connector. Default value is the same connectionTimeout attribute. The default value for connectionTimeout is to wait forever. So it seems to be that if you omit both settings, then the connector will keep the thread running forever while it is waiting for new connection.
<Service name="Catalina"> ... <Executor name="myThreadPool" namePrefix="my-" maxThreads="100" minSpareThreads="0" maxIdleTime="1" /> ... <Connector port="8010" executor="myThreadPool" protocol="org.apache.coyote.ajp.AjpProtocol" keepAliveTimeout="1" /> ... </Service>
In my tests this did not completely eliminate the thread pool. If requests are coming in quickly enough, the same thread may be used to serve two requests.
In order to debug things you might want to enable access log to see which threads are actually handling each request. You can do this with access log valve.
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t %I %r %s %b" resolveHosts="false"/>
Important part is %I which includes the thread name. You can verify that Tomcat is using your executor by checking that the thread names match then name prefix specified for executor.