View Javadoc
1   /*
2   * Copyright 2014 The Netty Project
3   *
4   * The Netty Project licenses this file to you under the Apache License,
5   * version 2.0 (the "License"); you may not use this file except in compliance
6   * with the License. You may obtain a copy of the License at:
7   *
8   *   https://www.apache.org/licenses/LICENSE-2.0
9   *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16  package io.netty.util.concurrent;
17  
18  import io.netty.util.internal.InternalThreadLocalMap;
19  import io.netty.util.internal.LongLongHashMap;
20  import io.netty.util.internal.logging.InternalLogger;
21  import io.netty.util.internal.logging.InternalLoggerFactory;
22  
23  import java.util.concurrent.atomic.AtomicReference;
24  
25  /**
26   * A special {@link Thread} that provides fast access to {@link FastThreadLocal} variables.
27   */
28  public class FastThreadLocalThread extends Thread {
29  
30      private static final InternalLogger logger = InternalLoggerFactory.getInstance(FastThreadLocalThread.class);
31  
32      /**
33       * Set of thread IDs that are treated like {@link FastThreadLocalThread}.
34       */
35      private static final AtomicReference<FallbackThreadSet> fallbackThreads =
36              new AtomicReference<>(FallbackThreadSet.EMPTY);
37  
38      // This will be set to true if we have a chance to wrap the Runnable.
39      private final boolean cleanupFastThreadLocals;
40  
41      private InternalThreadLocalMap threadLocalMap;
42  
43      public FastThreadLocalThread() {
44          cleanupFastThreadLocals = false;
45      }
46  
47      public FastThreadLocalThread(Runnable target) {
48          super(FastThreadLocalRunnable.wrap(target));
49          cleanupFastThreadLocals = true;
50      }
51  
52      public FastThreadLocalThread(ThreadGroup group, Runnable target) {
53          super(group, FastThreadLocalRunnable.wrap(target));
54          cleanupFastThreadLocals = true;
55      }
56  
57      public FastThreadLocalThread(String name) {
58          super(name);
59          cleanupFastThreadLocals = false;
60      }
61  
62      public FastThreadLocalThread(ThreadGroup group, String name) {
63          super(group, name);
64          cleanupFastThreadLocals = false;
65      }
66  
67      public FastThreadLocalThread(Runnable target, String name) {
68          super(FastThreadLocalRunnable.wrap(target), name);
69          cleanupFastThreadLocals = true;
70      }
71  
72      public FastThreadLocalThread(ThreadGroup group, Runnable target, String name) {
73          super(group, FastThreadLocalRunnable.wrap(target), name);
74          cleanupFastThreadLocals = true;
75      }
76  
77      public FastThreadLocalThread(ThreadGroup group, Runnable target, String name, long stackSize) {
78          super(group, FastThreadLocalRunnable.wrap(target), name, stackSize);
79          cleanupFastThreadLocals = true;
80      }
81  
82      /**
83       * Returns the internal data structure that keeps the thread-local variables bound to this thread.
84       * Note that this method is for internal use only, and thus is subject to change at any time.
85       */
86      public final InternalThreadLocalMap threadLocalMap() {
87          if (this != Thread.currentThread() && logger.isWarnEnabled()) {
88              logger.warn(new RuntimeException("It's not thread-safe to get 'threadLocalMap' " +
89                      "which doesn't belong to the caller thread"));
90          }
91          return threadLocalMap;
92      }
93  
94      /**
95       * Sets the internal data structure that keeps the thread-local variables bound to this thread.
96       * Note that this method is for internal use only, and thus is subject to change at any time.
97       */
98      public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
99          if (this != Thread.currentThread() && logger.isWarnEnabled()) {
100             logger.warn(new RuntimeException("It's not thread-safe to set 'threadLocalMap' " +
101                     "which doesn't belong to the caller thread"));
102         }
103         this.threadLocalMap = threadLocalMap;
104     }
105 
106     /**
107      * Returns {@code true} if {@link FastThreadLocal#removeAll()} will be called once {@link #run()} completes.
108      *
109      * @deprecated Use {@link FastThreadLocalThread#currentThreadWillCleanupFastThreadLocals()} instead
110      */
111     @Deprecated
112     public boolean willCleanupFastThreadLocals() {
113         return cleanupFastThreadLocals;
114     }
115 
116     /**
117      * Returns {@code true} if {@link FastThreadLocal#removeAll()} will be called once {@link Thread#run()} completes.
118      *
119      * @deprecated Use {@link FastThreadLocalThread#currentThreadWillCleanupFastThreadLocals()} instead
120      */
121     @Deprecated
122     public static boolean willCleanupFastThreadLocals(Thread thread) {
123         return thread instanceof FastThreadLocalThread &&
124                 ((FastThreadLocalThread) thread).willCleanupFastThreadLocals();
125     }
126 
127     /**
128      * Returns {@code true} if {@link FastThreadLocal#removeAll()} will be called once {@link Thread#run()} completes.
129      */
130     public static boolean currentThreadWillCleanupFastThreadLocals() {
131         // intentionally doesn't accept a thread parameter to work with ScopedValue in the future
132         Thread currentThread = currentThread();
133         if (currentThread instanceof FastThreadLocalThread) {
134             return ((FastThreadLocalThread) currentThread).willCleanupFastThreadLocals();
135         }
136         return isFastThreadLocalVirtualThread();
137     }
138 
139     /**
140      * Returns {@code true} if this thread supports {@link FastThreadLocal}.
141      */
142     public static boolean currentThreadHasFastThreadLocal() {
143         // intentionally doesn't accept a thread parameter to work with ScopedValue in the future
144         return currentThread() instanceof FastThreadLocalThread || isFastThreadLocalVirtualThread();
145     }
146 
147     private static boolean isFastThreadLocalVirtualThread() {
148         return fallbackThreads.get().contains(currentThread().getId());
149     }
150 
151     /**
152      * Run the given task with {@link FastThreadLocal} support. This call should wrap the runnable for any thread that
153      * is long-running enough to make treating it as a {@link FastThreadLocalThread} reasonable, but that can't
154      * actually extend this class (e.g. because it's a virtual thread). Netty will use optimizations for recyclers and
155      * allocators as if this was a {@link FastThreadLocalThread}.
156      * <p>This method will clean up any {@link FastThreadLocal}s at the end, and
157      * {@link #currentThreadWillCleanupFastThreadLocals()} will return {@code true}.
158      * <p>At the moment, {@link FastThreadLocal} uses normal {@link ThreadLocal} as the backing storage here, but in
159      * the future this may be replaced with scoped values, if semantics can be preserved and performance is good.
160      *
161      * @param runnable The task to run
162      */
163     public static void runWithFastThreadLocal(Runnable runnable) {
164         Thread current = currentThread();
165         if (current instanceof FastThreadLocalThread) {
166             throw new IllegalStateException("Caller is a real FastThreadLocalThread");
167         }
168         long id = current.getId();
169         fallbackThreads.updateAndGet(set -> {
170             if (set.contains(id)) {
171                 throw new IllegalStateException("Reentrant call to run()");
172             }
173             return set.add(id);
174         });
175 
176         try {
177             runnable.run();
178         } finally {
179             fallbackThreads.getAndUpdate(set -> set.remove(id));
180             FastThreadLocal.removeAll();
181         }
182     }
183 
184     /**
185      * Query whether this thread is allowed to perform blocking calls or not.
186      * {@link FastThreadLocalThread}s are often used in event-loops, where blocking calls are forbidden in order to
187      * prevent event-loop stalls, so this method returns {@code false} by default.
188      * <p>
189      * Subclasses of {@link FastThreadLocalThread} can override this method if they are not meant to be used for
190      * running event-loops.
191      *
192      * @return {@code false}, unless overriden by a subclass.
193      */
194     public boolean permitBlockingCalls() {
195         return false;
196     }
197 
198     /**
199      * Immutable, thread-safe helper class that wraps {@link LongLongHashMap}
200      */
201     private static final class FallbackThreadSet {
202         static final FallbackThreadSet EMPTY = new FallbackThreadSet();
203         private static final long EMPTY_VALUE = 0L;
204 
205         private final LongLongHashMap map;
206 
207         private FallbackThreadSet() {
208             this.map = new LongLongHashMap(EMPTY_VALUE);
209         }
210 
211         private FallbackThreadSet(LongLongHashMap map) {
212             this.map = map;
213         }
214 
215         public boolean contains(long threadId) {
216             long key = threadId >>> 6;
217             long bit = 1L << (threadId & 63);
218 
219             long bitmap = map.get(key);
220             return (bitmap & bit) != 0;
221         }
222 
223         public FallbackThreadSet add(long threadId) {
224             long key = threadId >>> 6;
225             long bit = 1L << (threadId & 63);
226 
227             LongLongHashMap newMap = new LongLongHashMap(map);
228             long oldBitmap = newMap.get(key);
229             long newBitmap = oldBitmap | bit;
230             newMap.put(key, newBitmap);
231 
232             return new FallbackThreadSet(newMap);
233         }
234 
235         public FallbackThreadSet remove(long threadId) {
236             long key = threadId >>> 6;
237             long bit = 1L << (threadId & 63);
238 
239             long oldBitmap = map.get(key);
240             if ((oldBitmap & bit) == 0) {
241                 return this;
242             }
243 
244             LongLongHashMap newMap = new LongLongHashMap(map);
245             long newBitmap = oldBitmap & ~bit;
246 
247             if (newBitmap != EMPTY_VALUE) {
248                 newMap.put(key, newBitmap);
249             } else {
250                 newMap.remove(key);
251             }
252 
253             return new FallbackThreadSet(newMap);
254         }
255     }
256 }