Bug 68692 - Http11Nio2Protocol not using provided executor
Summary: Http11Nio2Protocol not using provided executor
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 9
Classification: Unclassified
Component: Catalina (show other bugs)
Version: 9.0.83
Hardware: PC Linux
: P2 normal (vote)
Target Milestone: -----
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2024-02-29 16:06 UTC by Gabor Bodo
Modified: 2024-03-01 09:38 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Gabor Bodo 2024-02-29 16:06:37 UTC
We're using NIO2 with a StandardThreadExecutor we declared in our server.xml, and noticed in our logs that REST controllers run in a default thread pool, i.e. thread names in the format Thread-x are logged, e.g.

2024-02-29 14:42:48,313 (Thread-6:[]) INFO ...

Upon further debugging we noticed that the threadGroup field of the Nio2Endpoint is NULL. Looking at the source code, it seems that this condition in the bind() method evaluates to false:

if (getExecutor() instanceof ExecutorService) {
            threadGroup = AsynchronousChannelGroup.withThreadPool((ExecutorService) getExecutor());
}

which makes sense, since StandardThreadExecutor doesn't implement ExecutorService. We suspect that the NULL threadGroup causes NIO2 to use the default thread pool.

If we specify the thread attributes on the connector itself instead of passing an executor attribute, the correct channel group/thread pool is used:

2024-02-29 15:15:07,166 (http-nio2-1776-exec-7:[]) INFO ...

since in that case, the endpoint instantiates a standard ThreadPoolExecutor which implements ExecutorService.

       if (getUseVirtualThreads()) {
            executor = new VirtualThreadExecutor(getName() + "-virt-");
        } else {
            TaskQueue taskqueue = new TaskQueue();
            TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
            executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
            taskqueue.setParent( (ThreadPoolExecutor) executor);
        }
Comment 1 Remy Maucherat 2024-02-29 22:43:25 UTC
I think it is possible to upgrade the implementations to ExecutorService and I will do that. However, NIO2 needs its own exclusive pool so it is probably not very useful to configure a custom one like this.
Comment 2 Gabor Bodo 2024-03-01 08:29:13 UTC
Hi Remy,

Thank you for the quick reply! Just to be clear, we are only passing this executor to a single connector, so it is used exclusively. 

I think that the current behaviour is a bit inconsistent in the sense that if one implemented a custom Catalina executor, e.g. a StandardThreadExecutor subclass which also implements ExecutorService, the executor and its thread pool would be used. This instanceof check is only visible in the source code, and it's not clear to consumers of the NIO2 connector.
Comment 3 Remy Maucherat 2024-03-01 09:38:33 UTC
The fix will be in 11.0.0-M18, 10.1.20 and 9.0.87.