Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Propagating ThreadLocal to a new Thread fetched from a ExecutorService

I'm running a process in a separate thread with a timeout, using an ExecutorService and a Future (example code here) (the thread "spawning" takes place in a AOP Aspect).

Now, the main thread is a Resteasy request. Resteasy uses one ore more ThreadLocal variables to store some context information that I need to retrieve at some point in my Rest method call. Problem is, since the Resteasy thread is running in a new thread, the ThreadLocal variables are lost.

What would be the best way to "propagate" whatever ThreadLocal variable is used by Resteasy to the new thread? It seems that Resteasy uses more than one ThreadLocal variable to keep track of context information and I would like to "blindly" transfer all the information to the new thread.

I have looked at subclassing ThreadPoolExecutor and using the beforeExecute method to pass the current thread to the pool, but I couldn't find a way to pass the ThreadLocal variables to the pool.

Any suggestion?

Thanks

like image 755
Luciano Fiandesio Avatar asked Aug 31 '11 16:08

Luciano Fiandesio


People also ask

When should ThreadLocal be removed?

You should always call remove because ThreadLocal class puts values from the Thread Class defined by ThreadLocal. Values localValues; This will also cause to hold reference of Thread and associated objects. the value will be set to null and the underlying entry will still be present.

Are ThreadLocal variables thread safe?

Instead, give each thread its own instance of the object. Documentation. Another alternative to synchronization or threadlocal is to make the variable a local variable. Local vars are always thread safe.

Does ThreadLocal have to be static?

Although not necessary, you can have multiple map (without declare static) to keep each thread object as well, which, it is totally redundant, that's why static variable is preferred.

How does ThreadLocal cause memory leak?

Memory leak is caused when ThreadLocal is always existing. If ThreadLocal object could be GC, it will not cause memory leak. Because the entry in ThreadLocalMap extends WeakReference, the entry will be GC after ThreadLocal object is GC.


2 Answers

The set of ThreadLocal instances associated with a thread are held in private members of each Thread. Your only chance to enumerate these is to do some reflection on the Thread; this way, you can override the access restrictions on the thread's fields.

Once you can get the set of ThreadLocal, you could copy in the background threads using the beforeExecute() and afterExecute() hooks of ThreadPoolExecutor, or by creating a Runnable wrapper for your tasks that intercepts the run() call to set an unset the necessary ThreadLocal instances. Actually, the latter technique might work better, since it would give you a convenient place to store the ThreadLocal values at the time the task is queued.


Update: Here's a more concrete illustration of the second approach. Contrary to my original description, all that is stored in the wrapper is the calling thread, which is interrogated when the task is executed.

static Runnable wrap(Runnable task) {   Thread caller = Thread.currentThread();   return () -> {     Iterable<ThreadLocal<?>> vars = copy(caller);     try {       task.run();     }     finally {       for (ThreadLocal<?> var : vars)         var.remove();     }   }; }  /**  * For each {@code ThreadLocal} in the specified thread, copy the thread's   * value to the current thread.    *   * @param caller the calling thread  * @return all of the {@code ThreadLocal} instances that are set on current thread  */ private static Collection<ThreadLocal<?>> copy(Thread caller) {   /* Use a nasty bunch of reflection to do this. */   throw new UnsupportedOperationException(); } 
like image 96
erickson Avatar answered Sep 20 '22 06:09

erickson


Based on @erickson answer I wrote this code. It is working for inheritableThreadLocals. It builds list of inheritableThreadLocals using same method as is used in Thread contructor. Of course I use reflection to do this. Also I override the executor class.

public class MyThreadPoolExecutor extends ThreadPoolExecutor {    @Override    public void execute(Runnable command)    {       super.execute(new Wrapped(command, Thread.currentThread()));    } } 

Wrapper:

   private class Wrapped implements Runnable    {       private final Runnable task;        private final Thread caller;        public Wrapped(Runnable task, Thread caller)       {          this.task = task;          this.caller = caller;       }        public void run()       {          Iterable<ThreadLocal<?>> vars = null;          try          {             vars = copy(caller);          }          catch (Exception e)          {             throw new RuntimeException("error when coping Threads", e);          }          try {             task.run();          }          finally {             for (ThreadLocal<?> var : vars)                var.remove();          }       }    } 

copy method:

public static Iterable<ThreadLocal<?>> copy(Thread caller) throws Exception    {       List<ThreadLocal<?>> threadLocals = new ArrayList<>();       Field field = Thread.class.getDeclaredField("inheritableThreadLocals");       field.setAccessible(true);       Object map = field.get(caller);       Field table = Class.forName("java.lang.ThreadLocal$ThreadLocalMap").getDeclaredField("table");       table.setAccessible(true);        Method method = ThreadLocal.class               .getDeclaredMethod("createInheritedMap", Class.forName("java.lang.ThreadLocal$ThreadLocalMap"));       method.setAccessible(true);       Object o = method.invoke(null, map);        Field field2 = Thread.class.getDeclaredField("inheritableThreadLocals");       field2.setAccessible(true);       field2.set(Thread.currentThread(), o);        Object tbl = table.get(o);       int length = Array.getLength(tbl);       for (int i = 0; i < length; i++)       {          Object entry = Array.get(tbl, i);          Object value = null;          if (entry != null)          {             Method referentField = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry").getMethod(                     "get");             referentField.setAccessible(true);             value = referentField.invoke(entry);             threadLocals.add((ThreadLocal<?>) value);          }       }       return threadLocals;    } 
like image 28
Mr Jedi Avatar answered Sep 19 '22 06:09

Mr Jedi