1919import nl .talsmasoftware .context .api .Context ;
2020import nl .talsmasoftware .context .api .ContextManager ;
2121import nl .talsmasoftware .context .api .ContextSnapshot ;
22+ import nl .talsmasoftware .context .api .ContextSnapshot .Reactivation ;
2223
24+ import java .util .logging .Level ;
2325import java .util .logging .Logger ;
2426
2527import static io .grpc .Context .ROOT ;
3840 */
3941public class GrpcContextManager extends io .grpc .Context .Storage implements ContextManager <io .grpc .Context > {
4042 private static final GrpcContextManager INSTANCE = new GrpcContextManager ();
41- private static final Key <ContextSnapshot > GRPC_SNAPSHOT_KEY = key ("contextsnapshot-over-grpc" );
42- private static final Key <ContextSnapshot .Reactivation > GRPC_REACTIVATION_KEY = key ("contextsnapshot-reactivation" );
43+
4344 private static final Logger LOGGER = Logger .getLogger (GrpcContextManager .class .getName ());
44- private static final ThreadLocal <io .grpc .Context > STORAGE = new ThreadLocal <io .grpc .Context >() {
45- @ Override
46- public void set (io .grpc .Context value ) {
47- if (ROOT .equals (value )) { // prevent ROOT from being set
48- value = null ;
49- }
50- if (value != null ) {
51- // do not store ContextSnapshot or its reactivation in the threadlocal!
52- if (GRPC_SNAPSHOT_KEY .get (value ) != null ) {
53- value = value .withValue (GRPC_SNAPSHOT_KEY , null );
54- }
55- if (GRPC_REACTIVATION_KEY .get (value ) != null ) {
56- value = value .withValue (GRPC_REACTIVATION_KEY , null );
57- }
58- }
59- super .set (value );
60- }
61- };
45+ private static final Key <ContextSnapshot > GRPC_SNAPSHOT_KEY = key ("contextsnapshot-over-grpc" );
46+ private static final Key <Reactivation > GRPC_REACTIVATION_KEY = key ("contextsnapshot-reactivation" );
47+ private static final ThreadLocal <GrpcContextManager > CAPTURING = new ThreadLocal <>();
48+ private static final io .grpc .Context DUMMY = io .grpc .Context .ROOT .withValue (key ("dummy" ), "dummy" );
49+
50+ private static final ThreadLocal <io .grpc .Context > STORAGE = new ThreadLocal <>();
6251
6352 /**
6453 * Default constructor for java8 ServiceLoader.
@@ -76,22 +65,6 @@ public static GrpcContextManager provider() {
7665 return INSTANCE ;
7766 }
7867
79- /**
80- * The current gRPC context.
81- *
82- * <p>
83- * This is the managed gRPC context, which does <em>not</em> contain a {@link ContextSnapshot}.
84- * This method is accessed by the {@link ContextSnapshot} implementation itself to include the gRPC context in the
85- * captured {@link ContextSnapshot}.
86- *
87- * @return The current gRPC context if available, or {@code null} otherwise.
88- * @see #current()
89- */
90- @ Override
91- public io .grpc .Context getActiveContextValue () {
92- return STORAGE .get ();
93- }
94-
9568 /**
9669 * The current gRPC context.
9770 *
@@ -105,26 +78,20 @@ public io.grpc.Context getActiveContextValue() {
10578 */
10679 @ Override
10780 public io .grpc .Context current () {
108- io .grpc .Context current = getActiveContextValue ();
109- return (current == null ? ROOT : current ).withValue (GRPC_SNAPSHOT_KEY , ContextSnapshot .capture ());
110- }
111-
112- /**
113- * Activate the given gRPC context.
114- *
115- * <p>
116- * This stores the given gRPC context as the current gRPC context. The previous gRPC context will be restored
117- * when the returned {@link Context} is closed.
118- *
119- * @param value The gRPC context to activate.
120- * @return A context that restores the previous gRPC context when closed.
121- * @see #doAttach(io.grpc.Context)
122- */
123- @ Override
124- public Context activate (io .grpc .Context value ) {
125- io .grpc .Context previous = STORAGE .get ();
126- STORAGE .set (value );
127- return () -> STORAGE .set (previous );
81+ if (CAPTURING .get () != null ) {
82+ LOGGER .finest ("gRPC current(): Returning ROOT context because already capturing." );
83+ return ROOT ;
84+ }
85+ try {
86+ LOGGER .finest ("--> gRPC current(): Capturing gRPC context." );
87+ CAPTURING .set (this );
88+ ContextSnapshot snapshot = ContextSnapshot .capture ();
89+ io .grpc .Context current = nullToRoot (STORAGE .get ()).withValue (GRPC_SNAPSHOT_KEY , snapshot );
90+ LOGGER .finest (() -> "<-- gRPC current(): Returning current gRPC context " + current + " with captured " + snapshot + "." );
91+ return current ;
92+ } finally {
93+ CAPTURING .remove ();
94+ }
12895 }
12996
13097 /**
@@ -137,24 +104,19 @@ public Context activate(io.grpc.Context value) {
137104 * It <em>must</em> be restored in the same thread, which then closes the reactivated ContextSnapshot.
138105 *
139106 * @param toAttach the context to be attached
140- * @return The previous gRPC context to be restored in the corresponding detach call.
107+ * @return The previous gRPC context to be restored in the corresponding ' detach' call.
141108 * @see #activate(io.grpc.Context)
142109 * @see #detach(io.grpc.Context, io.grpc.Context)
143110 */
144111 @ Override
145112 public io .grpc .Context doAttach (io .grpc .Context toAttach ) {
146- io .grpc .Context toRestore = STORAGE .get ();
147- if (toRestore == null ) {
148- toRestore = ROOT ;
149- }
150- STORAGE .set (toAttach );
151-
113+ io .grpc .Context toRestore = nullToRoot (STORAGE .get ());
152114 ContextSnapshot snapshot = toAttach == null ? null : GRPC_SNAPSHOT_KEY .get (toAttach );
153115 if (snapshot != null ) {
116+ toAttach = toAttach .withValue (GRPC_SNAPSHOT_KEY , null );
154117 toRestore = toRestore .withValue (GRPC_REACTIVATION_KEY , snapshot .reactivate ());
155- } else {
156- LOGGER .warning (() -> "No context snapshot found in gRPC context to be attached: " + toAttach );
157118 }
119+ STORAGE .set (rootOrDummyToNull (toAttach ));
158120 return toRestore ;
159121 }
160122
@@ -172,14 +134,16 @@ public io.grpc.Context doAttach(io.grpc.Context toAttach) {
172134 */
173135 @ Override
174136 public void detach (io .grpc .Context toDetach , io .grpc .Context toRestore ) {
175- ContextSnapshot .Reactivation reactivation = GRPC_REACTIVATION_KEY .get (toRestore );
176- if (reactivation == null ) {
177- // should this ever happen?
178- LOGGER .warning (() -> "No snapshot reactivation present in gRPC context to be restored: " + toRestore );
179- } else {
137+ LOGGER .log (Level .FINEST , "--> gRPC detach(): Detaching gRPC context by restoring {0}." , toRestore );
138+ Reactivation reactivation = toRestore == null ? null : GRPC_REACTIVATION_KEY .get (toRestore );
139+ if (reactivation != null ) {
140+ LOGGER .finest (() -> "--> grpc detach(): Closing reactivation from toRestore: " + reactivation + "." );
180141 reactivation .close ();
142+ toRestore = toRestore .withValue (GRPC_REACTIVATION_KEY , null );
143+ LOGGER .finest (() -> "--- gRPC detach(): Reactivation from toRestore closed: " + reactivation + "." );
181144 }
182- STORAGE .set (toRestore );
145+ STORAGE .set (rootOrDummyToNull (toRestore ));
146+ LOGGER .log (Level .FINEST , "<-- gRPC detach(): Detached gRPC context by restoring {0}." , toRestore );
183147 }
184148
185149 /**
@@ -188,5 +152,66 @@ public void detach(io.grpc.Context toDetach, io.grpc.Context toRestore) {
188152 @ Override
189153 public void clear () {
190154 STORAGE .remove ();
155+ LOGGER .finest ("<-> clear(): Cleared current gRPC context." );
156+ }
157+
158+ /**
159+ * The current gRPC context.
160+ *
161+ * <p>
162+ * This is the managed gRPC context, which does <em>not</em> contain a {@link ContextSnapshot}.
163+ * This method is accessed by the {@link ContextSnapshot} implementation itself to include the gRPC context in the
164+ * captured {@link ContextSnapshot}.
165+ *
166+ * @return The current gRPC context if available, or {@code null} otherwise.
167+ * @see #current()
168+ */
169+ @ Override
170+ public io .grpc .Context getActiveContextValue () {
171+ if (CAPTURING .get () != null ) {
172+ LOGGER .finest ("<-> getActiveContextValue(): Returning DUMMY context because already capturing." );
173+ return DUMMY ;
174+ }
175+ try {
176+ LOGGER .finest ("--> getActiveContextValue(): Capturing active context value." );
177+ CAPTURING .set (this );
178+ io .grpc .Context current = STORAGE .get ();
179+ LOGGER .finest (() -> "<-- getActiveContextValue() Returning current gRPC context " + current + " from storage." );
180+ return current ;
181+ } finally {
182+ CAPTURING .remove ();
183+ }
184+ }
185+
186+ /**
187+ * Activate the given gRPC context.
188+ *
189+ * <p>
190+ * This stores the given gRPC context as the current gRPC context. The previous gRPC context will be restored
191+ * when the returned {@link Context} is closed.
192+ *
193+ * @param value The gRPC context to activate.
194+ * @return A context that restores the previous gRPC context when closed.
195+ * @see #doAttach(io.grpc.Context)
196+ */
197+ @ Override
198+ public Context activate (io .grpc .Context value ) {
199+ if (value == DUMMY ) {
200+ LOGGER .finest ("<-> activate(DUMMY): Returning no-op context." );
201+ return () -> {
202+ };
203+ }
204+ LOGGER .finest (() -> "--> activate(" + value + "): Attaching gRPC context " + value + " as current context." );
205+ final io .grpc .Context toRestore = doAttach (value );
206+ LOGGER .finest (() -> "<-- activate(" + value + "): Returning context that restores " + toRestore + "." );
207+ return () -> detach (value , toRestore );
208+ }
209+
210+ private static io .grpc .Context nullToRoot (io .grpc .Context context ) {
211+ return context == null ? ROOT : context ;
212+ }
213+
214+ private static io .grpc .Context rootOrDummyToNull (io .grpc .Context context ) {
215+ return context == ROOT || context == DUMMY ? null : context ;
191216 }
192217}
0 commit comments