Search code examples
javaspringmultithreadingservlet-dispatching

Why must a child thread that uses a ThreadPoolExecutor not be exposed to any inherited context of the parent thread?


I have made a component that implements ApplicationListener<AuthenticationSuccessEvent> and should log the ip address, the ip address can be retrieved from HttpServletRequest. Apparently this component is run in a child thread and therefore i needed to set the property ThreadContextInheritable to true on the DispatcherServlet component in order to gain access to HttpServletRequest. (I tried using a RequestContextListener but that had no effect).

In the spring documentation which can be found here it states the following warning when you set ThreadContextInheritable to true.

WARNING: Do not use inheritance for child threads if you are accessing a thread pool which is configured to potentially add new threads on demand (e.g. a JDK ThreadPoolExecutor), since this will expose the inherited context to such a pooled thread.

My question is: why would exposing the inherited context to a pooled thread be such a bad thing? What could go wrong in such a case? Also, do they mean a child thread that is using a ThreadPoolExecutor instance OR do they mean a child thread that is created using a ThreadPoolExecutor?


Solution

  • InheritableThreadLocal extends ThreadLocal and used when we need to pass parent thread-local attribute values automatically to child thread on creation.

    This inheritance works perfectly fine when you are creating new child thread each time and not reusing already created thread.

    Let us consider a scenario - We have thread pool of size 5. When we send first 5 tasks to thread pool to process for each task new thread is created so inheritance of thread-local attribute works perfectly fine. Problem starts from 6th request. When you send 6th task no new thread gets created but already created thread from thread pool is reused to process it. On 6th request parent thread-local attribute values doesn't get inherited to the child thread as we are not creating new thread but reusing already created thread from pool.

    This code snippet will explain the point -

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class DeadlyComboTest {
    
        public static void main(String[] args) throws InterruptedException {    
            int noOfThreads     = 5;
            //Execution 1
            int threadPoolSize  = noOfThreads;
            
            //Execution 2 : uncomment below line and comment line above
            //int threadPoolSize = noOfThreads/2;
                
            //1. create thread pool
            ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);  
    
            //2. create Inheritable thread local
            InheritableThreadLocal<Object> value = new InheritableThreadLocal<>(); 
    
            //3. create new command and execute using thread pool created in step 1
            for (int i = 1; i <= noOfThreads; i++) {
                value.set(i);
                executor.execute(() -> printThreadLocalValue(value));
            }   
            executor.shutdown();
        }
       
        private static void printThreadLocalValue(ThreadLocal<Object> value) {
            System.out.println(Thread.currentThread().getName() + " = " + value.get());
        }
    }
    
    Exection 1: noOfThreads = threadPoolSize = 5
        OUTPUT: you may get the output in different sequence
            pool-1-thread-1 = 1
            pool-1-thread-2 = 2
            pool-1-thread-3 = 3
            pool-1-thread-4 = 4
            pool-1-thread-5 = 5
            
    

    Everything looks good. As on every request new thread get created and child thread correctly inherits the thread-local values of parent.

    Execution 2: noOfThreads = 5 && threadPoolSize = noOfThreads/2 = 2
      OUTPUT:
            pool-1-thread-1 = 1
            pool-1-thread-2 = 2
            pool-1-thread-1 = 1
            pool-1-thread-2 = 2
            pool-1-thread-1 = 1
    
    
            
    

    Only for first two requests thread-local inheritance works correctly, as thread pool size is 2 so for first two requests new threads get created and pooled. Third request onward already created threads from pool get reused which still have the inherited values of old thread.

    Execution 2 is a real world scenario where threads from pool will get reused.

    What can go bad when we use Thread pool and InheritableThreadLocal combination?

    It will cause unintended information leak as worker/child thread from pool will leak thread-local attribute values of one thread to another.

    If your business logic is dependent on these attribute values, you will get hard to track bugs.

    That's why spring documentation warns against using this combo of Thread pool and InheritableThreadLocal.