4949 * <li>Local and distributed tracing information.</li>
5050 * </ul>
5151 *
52- * <p>Context objects make their state available by being attached to the executing thread using
53- * a {@link ThreadLocal}. The context object bound to a thread is considered {@link #current()}.
54- * Context objects are immutable and inherit state from their parent. To add or overwrite the
55- * current state a new context object must be created and then attached to the thread replacing the
56- * previously bound context. For example:
52+ * <p>A Context object can be {@link #attach attached} to the {@link Storage}, which effectively
53+ * forms a <b>scope</b> for the context. The scope is bound to the current thread. Within a scope,
54+ * its Context is accessible even across API boundaries, through {@link #current}. The scope is
55+ * later exited by {@link #detach detaching} the Context.
56+ *
57+ * <p>Context objects are immutable and inherit state from their parent. To add or overwrite the
58+ * current state a new context object must be created and then attached, replacing the previously
59+ * bound context. For example:
60+ *
5761 * <pre>
5862 * Context withCredential = Context.current().withValue(CRED_KEY, cred);
5963 * executorService.execute(withCredential.wrap(new Runnable() {
6064 * public void run() {
6165 * readUserRecords(userId, CRED_KEY.get());
6266 * }
6367 * }));
64-
6568 * </pre>
6669 *
67- *
68- * <p>Contexts are also used to represent a scoped unit of work. When the unit of work is
69- * done the context can be cancelled. This cancellation will also cascade to all descendant
70- * contexts. You can add a {@link CancellationListener} to a context to be notified when it or
71- * one of its ancestors has been cancelled. Cancellation does not release the state stored by
72- * a context and it's perfectly valid to {@link #attach()} an already cancelled context to a
73- * thread to make it current. To cancel a context (and its descendants) you first create a
74- * {@link CancellableContext} and when you need to signal cancellation call
75- * {@link CancellableContext#cancel} or {@link CancellableContext#detachAndCancel}. For example:
70+ * <p>Contexts are also used to represent a scoped unit of work. When the unit of work is done the
71+ * context can be cancelled. This cancellation will also cascade to all descendant contexts. You can
72+ * add a {@link CancellationListener} to a context to be notified when it or one of its ancestors
73+ * has been cancelled. Cancellation does not release the state stored by a context and it's
74+ * perfectly valid to {@link #attach()} an already cancelled context to make it current. To cancel a
75+ * context (and its descendants) you first create a {@link CancellableContext} and when you need to
76+ * signal cancellation call {@link CancellableContext#cancel} or {@link
77+ * CancellableContext#detachAndCancel}. For example:
7678 * <pre>
7779 * CancellableContext withCancellation = Context.current().withCancellation();
7880 * try {
@@ -110,20 +112,42 @@ public class Context {
110112 private static final Key <Deadline > DEADLINE_KEY = new Key <Deadline >("deadline" );
111113
112114 /**
113- * The logical root context which is {@link #current()} if no other context is bound . This context
115+ * The logical root context which is the ultimate ancestor of all contexts . This context
114116 * is not cancellable and so will not cascade cancellation or retain listeners.
117+ *
118+ * <p>Never assume this is the default context for new threads, because {@link Storage} may define
119+ * a default context that is different from ROOT.
115120 */
116121 public static final Context ROOT = new Context (null );
117122
118- /**
119- * Currently bound context.
120- */
121- private static final ThreadLocal <Context > localContext = new ThreadLocal <Context >() {
122- @ Override
123- protected Context initialValue () {
124- return ROOT ;
123+ private static Storage storage ;
124+
125+ private static synchronized Storage initializeStorage () {
126+ if (storage != null ) {
127+ return storage ;
128+ }
129+ try {
130+ Class <?> clazz = Class .forName ("io.grpc.ContextStorageOverride" );
131+ storage = (Storage ) clazz .newInstance ();
132+ return storage ;
133+ } catch (ClassNotFoundException e ) {
134+ log .log (Level .FINE , "Storage override doesn't exist. Using default." , e );
135+ } catch (InstantiationException e ) {
136+ throw new RuntimeException ("Failed to initialize Storage implementation" , e );
137+ } catch (IllegalAccessException e ) {
138+ throw new RuntimeException ("Failed to initialize Storage implementation" , e );
139+ }
140+ storage = new ThreadLocalContextStorage ();
141+ return storage ;
142+ }
143+
144+ // For testing
145+ static Storage storage () {
146+ if (storage == null ) {
147+ return initializeStorage ();
125148 }
126- };
149+ return storage ;
150+ }
127151
128152 /**
129153 * Create a {@link Key} with the given debug name. Multiple different keys may have the same name;
@@ -142,15 +166,14 @@ public static <T> Key<T> keyWithDefault(String name, T defaultValue) {
142166 }
143167
144168 /**
145- * Return the context associated with the current thread, will never return {@code null} as
146- * the {@link #ROOT} context is implicitly associated with all threads.
169+ * Return the context associated with the current scope, will never return {@code null}.
147170 *
148171 * <p>Will never return {@link CancellableContext} even if one is attached, instead a
149172 * {@link Context} is returned with the same properties and lifetime. This is to avoid
150173 * code stealing the ability to cancel arbitrarily.
151174 */
152175 public static Context current () {
153- Context current = localContext . get ();
176+ Context current = storage (). current ();
154177 if (current == null ) {
155178 return ROOT ;
156179 }
@@ -324,34 +347,29 @@ boolean canBeCancelled() {
324347 }
325348
326349 /**
327- * Attach this context to the thread and make it {@link #current}, the previously current context
328- * is returned. It is allowed to attach contexts where {@link #isCancelled()} is {@code true}.
350+ * Attach this context, thus enter a new scope within which this context is {@link #current}. The
351+ * previously current context is returned. It is allowed to attach contexts where {@link
352+ * #isCancelled()} is {@code true}.
329353 *
330354 * <p>Instead of using {@link #attach()} & {@link #detach(Context)} most use-cases are better
331- * served by using the {@link #run(Runnable)} or {@link #call(java.util.concurrent.Callable)}
332- * to execute work immediately within a context. If work needs to be done in other threads
333- * it is recommended to use the 'wrap' methods or to use a propagating executor.
355+ * served by using the {@link #run(Runnable)} or {@link #call(java.util.concurrent.Callable)} to
356+ * execute work immediately within a context's scope . If work needs to be done in other threads it
357+ * is recommended to use the 'wrap' methods or to use a propagating executor.
334358 */
335359 public Context attach () {
336360 Context previous = current ();
337- localContext . set (this );
361+ storage (). attach (this );
338362 return previous ;
339363 }
340364
341365 /**
342- * Detach the current context from the thread and attach the provided replacement. If this
343- * context is not {@link #current()} a SEVERE message will be logged but the context to attach
344- * will still be bound.
366+ * Detach the current context and attach the provided replacement which should be the context of
367+ * the outer scope, thus exit the current scope. If this context is not {@link #current()} a
368+ * SEVERE message will be logged but the context to attach will still be bound.
345369 */
346370 public void detach (Context toAttach ) {
347371 checkNotNull (toAttach , "toAttach" );
348- if (toAttach .attach () != this ) {
349- // Log a severe message instead of throwing an exception as the context to attach is assumed
350- // to be the correct one and the unbalanced state represents a coding mistake in a lower
351- // layer in the stack that cannot be recovered from here.
352- log .log (Level .SEVERE , "Context was not attached when detaching" ,
353- new Throwable ().fillInStackTrace ());
354- }
372+ storage ().detach (this , toAttach );
355373 }
356374
357375 // Visible for testing
@@ -709,7 +727,7 @@ public boolean cancel(Throwable cause) {
709727 }
710728
711729 /**
712- * Cancel this context and detach it as the current context from the thread .
730+ * Cancel this context and detach it as the current context.
713731 *
714732 * @param toAttach context to make current.
715733 * @param cause of cancellation, can be {@code null}.
@@ -798,7 +816,41 @@ public String toString() {
798816 }
799817
800818 /**
801- * Stores listener & executor pair.
819+ * Defines the mechanisms for attaching and detaching the "current" context.
820+ *
821+ * <p>The default implementation will put the current context in a {@link ThreadLocal}. If an
822+ * alternative implementation named {@code io.grpc.ContextStorageOverride} exists in the
823+ * classpath, it will be used instead of the default implementation.
824+ *
825+ * <p>This API is <a href="https://github.com/grpc/grpc-java/issues/2462">experimental</a> and
826+ * subject to change.
827+ */
828+ public abstract static class Storage {
829+ /**
830+ * Implements {@link io.grpc.Context#attach}.
831+ *
832+ * @param toAttach the context to be attached
833+ */
834+ public abstract void attach (Context toAttach );
835+
836+ /**
837+ * Implements {@link io.grpc.Context#detach}
838+ *
839+ * @param toDetach the context to be detached. Should be, or be equivalent to, the current
840+ * context of the current scope
841+ * @param toRestore the context to be the current. Should be, or be equivalent to, the context
842+ * of the outer scope
843+ */
844+ public abstract void detach (Context toDetach , Context toRestore );
845+
846+ /**
847+ * Implements {@link io.grpc.Context#current}. Returns the context of the current scope.
848+ */
849+ public abstract Context current ();
850+ }
851+
852+ /**
853+ * Stores listener and executor pair.
802854 */
803855 private class ExecutableListener implements Runnable {
804856 private final Executor executor ;
0 commit comments