• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc;
18 
19 import static com.google.common.truth.TruthJUnit.assume;
20 import static io.grpc.Context.cancellableAncestor;
21 import static org.hamcrest.MatcherAssert.assertThat;
22 import static org.hamcrest.core.IsInstanceOf.instanceOf;
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertNotSame;
27 import static org.junit.Assert.assertNull;
28 import static org.junit.Assert.assertSame;
29 import static org.junit.Assert.assertTrue;
30 import static org.junit.Assert.fail;
31 
32 import com.google.common.util.concurrent.MoreExecutors;
33 import com.google.common.util.concurrent.SettableFuture;
34 import java.lang.reflect.Field;
35 import java.lang.reflect.Modifier;
36 import java.util.ArrayDeque;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Queue;
41 import java.util.concurrent.Callable;
42 import java.util.concurrent.CountDownLatch;
43 import java.util.concurrent.Executor;
44 import java.util.concurrent.Executors;
45 import java.util.concurrent.Future;
46 import java.util.concurrent.ScheduledExecutorService;
47 import java.util.concurrent.ScheduledThreadPoolExecutor;
48 import java.util.concurrent.TimeUnit;
49 import java.util.concurrent.TimeoutException;
50 import java.util.concurrent.atomic.AtomicBoolean;
51 import java.util.concurrent.atomic.AtomicReference;
52 import java.util.logging.Handler;
53 import java.util.logging.Level;
54 import java.util.logging.LogRecord;
55 import java.util.logging.Logger;
56 import java.util.regex.Pattern;
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 import org.junit.runners.JUnit4;
62 
63 /**
64  * Tests for {@link Context}.
65  */
66 @RunWith(JUnit4.class)
67 @SuppressWarnings("CheckReturnValue") // false-positive in test for current ver errorprone plugin
68 public class ContextTest {
69 
70   private static final Context.Key<String> PET = Context.key("pet");
71   private static final Context.Key<String> FOOD = Context.keyWithDefault("food", "lasagna");
72   private static final Context.Key<String> COLOR = Context.key("color");
73   private static final Context.Key<Object> FAVORITE = Context.key("favorite");
74   private static final Context.Key<Integer> LUCKY = Context.key("lucky");
75 
76   private Context listenerNotifedContext;
77   private CountDownLatch deadlineLatch = new CountDownLatch(1);
78   private final Context.CancellationListener cancellationListener =
79       new Context.CancellationListener() {
80         @Override
81         public void cancelled(Context context) {
82           listenerNotifedContext = context;
83           deadlineLatch.countDown();
84         }
85       };
86 
87   private Context observed;
88   private final Runnable runner = new Runnable() {
89     @Override
90     public void run() {
91       observed = Context.current();
92     }
93   };
94   private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
95 
96   @Before
setUp()97   public void setUp() {
98     Context.ROOT.attach();
99   }
100 
101   @After
tearDown()102   public void tearDown()  {
103     scheduler.shutdown();
104     assertEquals(Context.ROOT, Context.current());
105   }
106 
107   @Test
defaultContext()108   public void defaultContext() throws Exception {
109     final SettableFuture<Context> contextOfNewThread = SettableFuture.create();
110     Context contextOfThisThread = Context.ROOT.withValue(PET, "dog");
111     Context toRestore = contextOfThisThread.attach();
112     new Thread(new Runnable() {
113       @Override
114       public void run() {
115         contextOfNewThread.set(Context.current());
116       }
117       }).start();
118     assertNotNull(contextOfNewThread.get(5, TimeUnit.SECONDS));
119     assertNotSame(contextOfThisThread, contextOfNewThread.get());
120     assertSame(contextOfThisThread, Context.current());
121     contextOfThisThread.detach(toRestore);
122   }
123 
124   @Test
rootCanBeAttached()125   public void rootCanBeAttached() {
126     Context fork = Context.ROOT.fork();
127     Context toRestore1 = fork.attach();
128     Context toRestore2 = Context.ROOT.attach();
129     assertTrue(Context.ROOT.isCurrent());
130 
131     Context toRestore3 = fork.attach();
132     assertTrue(fork.isCurrent());
133 
134     fork.detach(toRestore3);
135     Context.ROOT.detach(toRestore2);
136     fork.detach(toRestore1);
137   }
138 
139   @Test
rootCanNeverHaveAListener()140   public void rootCanNeverHaveAListener() {
141     Context root = Context.current();
142     root.addListener(cancellationListener, MoreExecutors.directExecutor());
143     assertEquals(0, root.listenerCount());
144   }
145 
146   @Test
rootIsNotCancelled()147   public void rootIsNotCancelled() {
148     assertFalse(Context.ROOT.isCancelled());
149     assertNull(Context.ROOT.cancellationCause());
150   }
151 
152   @Test
attachedCancellableContextCannotBeCastFromCurrent()153   public void attachedCancellableContextCannotBeCastFromCurrent() {
154     Context initial = Context.current();
155     Context.CancellableContext base = initial.withCancellation();
156     base.attach();
157     assertFalse(Context.current() instanceof Context.CancellableContext);
158     assertNotSame(base, Context.current());
159     assertNotSame(initial, Context.current());
160     base.detachAndCancel(initial, null);
161     assertSame(initial, Context.current());
162   }
163 
164   @Test
attachingNonCurrentReturnsCurrent()165   public void attachingNonCurrentReturnsCurrent() {
166     Context initial = Context.current();
167     Context base = initial.withValue(PET, "dog");
168     assertSame(initial, base.attach());
169     assertSame(base, initial.attach());
170   }
171 
172   @Test
detachingNonCurrentLogsSevereMessage()173   public void detachingNonCurrentLogsSevereMessage() {
174     final AtomicReference<LogRecord> logRef = new AtomicReference<>();
175     Handler handler = new Handler() {
176       @Override
177       public void publish(LogRecord record) {
178         logRef.set(record);
179       }
180 
181       @Override
182       public void flush() {
183       }
184 
185       @Override
186       public void close() throws SecurityException {
187       }
188     };
189     Logger logger = Logger.getLogger(Context.storage().getClass().getName());
190     try {
191       logger.addHandler(handler);
192       Context initial = Context.current();
193       Context base = initial.withValue(PET, "dog");
194       // Base is not attached
195       base.detach(initial);
196       assertSame(initial, Context.current());
197       assertNotNull(logRef.get());
198       assertEquals(Level.SEVERE, logRef.get().getLevel());
199     } finally {
200       logger.removeHandler(handler);
201     }
202   }
203 
204   @Test
valuesAndOverrides()205   public void valuesAndOverrides() {
206     Context base = Context.current().withValue(PET, "dog");
207     Context child = base.withValues(PET, null, FOOD, "cheese");
208 
209     base.attach();
210 
211     assertEquals("dog", PET.get());
212     assertEquals("lasagna", FOOD.get());
213     assertNull(COLOR.get());
214 
215     child.attach();
216 
217     assertNull(PET.get());
218     assertEquals("cheese", FOOD.get());
219     assertNull(COLOR.get());
220 
221     child.detach(base);
222 
223     // Should have values from base
224     assertEquals("dog", PET.get());
225     assertEquals("lasagna", FOOD.get());
226     assertNull(COLOR.get());
227 
228     base.detach(Context.ROOT);
229 
230     assertNull(PET.get());
231     assertEquals("lasagna", FOOD.get());
232     assertNull(COLOR.get());
233   }
234 
235   @Test
withValuesThree()236   public void withValuesThree() {
237     Object fav = new Object();
238     Context base = Context.current().withValues(PET, "dog", COLOR, "blue");
239     Context child = base.withValues(PET, "cat", FOOD, "cheese", FAVORITE, fav);
240 
241     Context toRestore = child.attach();
242 
243     assertEquals("cat", PET.get());
244     assertEquals("cheese", FOOD.get());
245     assertEquals("blue", COLOR.get());
246     assertEquals(fav, FAVORITE.get());
247 
248     child.detach(toRestore);
249   }
250 
251   @Test
withValuesFour()252   public void withValuesFour() {
253     Object fav = new Object();
254     Context base = Context.current().withValues(PET, "dog", COLOR, "blue");
255     Context child = base.withValues(PET, "cat", FOOD, "cheese", FAVORITE, fav, LUCKY, 7);
256 
257     Context toRestore = child.attach();
258 
259     assertEquals("cat", PET.get());
260     assertEquals("cheese", FOOD.get());
261     assertEquals("blue", COLOR.get());
262     assertEquals(fav, FAVORITE.get());
263     assertEquals(7, (int) LUCKY.get());
264 
265     child.detach(toRestore);
266   }
267 
268   @Test
cancelReturnsFalseIfAlreadyCancelled()269   public void cancelReturnsFalseIfAlreadyCancelled() {
270     Context.CancellableContext base = Context.current().withCancellation();
271     assertTrue(base.cancel(null));
272     assertTrue(base.isCancelled());
273     assertFalse(base.cancel(null));
274   }
275 
276   @Test
notifyListenersOnCancel()277   public void notifyListenersOnCancel() {
278     class SetContextCancellationListener implements Context.CancellationListener {
279       private final AtomicReference<Context> observed;
280 
281       public SetContextCancellationListener(AtomicReference<Context> observed) {
282         this.observed = observed;
283       }
284 
285       @Override
286       public void cancelled(Context context) {
287         observed.set(context);
288       }
289     }
290 
291     Context.CancellableContext base = Context.current().withCancellation();
292     final AtomicReference<Context> observed1 = new AtomicReference<>();
293     base.addListener(new SetContextCancellationListener(observed1), MoreExecutors.directExecutor());
294     final AtomicReference<Context> observed2 = new AtomicReference<>();
295     base.addListener(new SetContextCancellationListener(observed2), MoreExecutors.directExecutor());
296     assertNull(observed1.get());
297     assertNull(observed2.get());
298     base.cancel(null);
299     assertSame(base, observed1.get());
300     assertSame(base, observed2.get());
301 
302     final AtomicReference<Context> observed3 = new AtomicReference<>();
303     base.addListener(new SetContextCancellationListener(observed3), MoreExecutors.directExecutor());
304     assertSame(base, observed3.get());
305   }
306 
307   @Test
removeListenersFromContextAndChildContext()308   public void removeListenersFromContextAndChildContext() {
309     class SetContextCancellationListener implements Context.CancellationListener {
310       private final List<Context> observedContexts;
311 
312       SetContextCancellationListener() {
313         this.observedContexts = Collections.synchronizedList(new ArrayList<Context>());
314       }
315 
316       @Override
317       public void cancelled(Context context) {
318         observedContexts.add(context);
319       }
320     }
321 
322     Context.CancellableContext base = Context.current().withCancellation();
323     Context child = base.withValue(PET, "tiger");
324     Context childOfChild = base.withValue(PET, "lion");
325     final SetContextCancellationListener listener = new SetContextCancellationListener();
326     base.addListener(listener, MoreExecutors.directExecutor());
327     child.addListener(listener, MoreExecutors.directExecutor());
328     childOfChild.addListener(listener, MoreExecutors.directExecutor());
329     base.removeListener(listener);
330     childOfChild.removeListener(listener);
331     base.cancel(null);
332     assertEquals(1, listener.observedContexts.size());
333     assertSame(child, listener.observedContexts.get(0));
334   }
335 
336   @Test
exceptionOfExecutorDoesntThrow()337   public void exceptionOfExecutorDoesntThrow() {
338     final AtomicReference<Throwable> loggedThrowable = new AtomicReference<>();
339     Handler logHandler = new Handler() {
340       @Override
341       public void publish(LogRecord record) {
342         Throwable thrown = record.getThrown();
343         if (thrown != null) {
344           if (loggedThrowable.get() == null) {
345             loggedThrowable.set(thrown);
346           } else {
347             loggedThrowable.set(new RuntimeException("Too many exceptions", thrown));
348           }
349         }
350       }
351 
352       @Override
353       public void close() {}
354 
355       @Override
356       public void flush() {}
357     };
358     Logger logger = Logger.getLogger(Context.class.getName());
359     logger.addHandler(logHandler);
360     try {
361       Context.CancellableContext base = Context.current().withCancellation();
362       final AtomicReference<Runnable> observed1 = new AtomicReference<>();
363       final Error err = new Error();
364       base.addListener(cancellationListener, new Executor() {
365         @Override
366         public void execute(Runnable runnable) {
367           observed1.set(runnable);
368           throw err;
369         }
370       });
371       assertNull(observed1.get());
372       assertNull(loggedThrowable.get());
373       base.cancel(null);
374       assertNotNull(observed1.get());
375       assertSame(err, loggedThrowable.get());
376 
377       final Error err2 = new Error();
378       loggedThrowable.set(null);
379       final AtomicReference<Runnable> observed2 = new AtomicReference<>();
380       base.addListener(cancellationListener, new Executor() {
381         @Override
382         public void execute(Runnable runnable) {
383           observed2.set(runnable);
384           throw err2;
385         }
386       });
387       assertNotNull(observed2.get());
388       assertSame(err2, loggedThrowable.get());
389     } finally {
390       logger.removeHandler(logHandler);
391     }
392   }
393 
394   @Test
cascadingCancellationNotifiesChild()395   public void cascadingCancellationNotifiesChild() {
396     // Root is not cancellable so we can't cascade from it
397     Context.CancellableContext base = Context.current().withCancellation();
398     assertEquals(0, base.listenerCount());
399     Context child = base.withValue(FOOD, "lasagna");
400     assertEquals(0, child.listenerCount());
401     child.addListener(cancellationListener, MoreExecutors.directExecutor());
402     assertEquals(1, child.listenerCount());
403     assertEquals(1, base.listenerCount()); // child is now listening to base
404     assertFalse(base.isCancelled());
405     assertFalse(child.isCancelled());
406     IllegalStateException cause = new IllegalStateException();
407     base.cancel(cause);
408     assertTrue(base.isCancelled());
409     assertSame(cause, base.cancellationCause());
410     assertSame(child, listenerNotifedContext);
411     assertTrue(child.isCancelled());
412     assertSame(cause, child.cancellationCause());
413     assertEquals(0, base.listenerCount());
414     assertEquals(0, child.listenerCount());
415   }
416 
417   @Test
cascadingCancellationWithoutListener()418   public void cascadingCancellationWithoutListener() {
419     Context.CancellableContext base = Context.current().withCancellation();
420     Context child = base.withCancellation();
421     Throwable t = new Throwable();
422     base.cancel(t);
423     assertTrue(child.isCancelled());
424     assertSame(t, child.cancellationCause());
425   }
426 
427   // Context#isCurrent() and Context.CancellableContext#isCurrent() are intended
428   // to be visible only for testing. The deprecation is meant for users.
429   @SuppressWarnings("deprecation")
430   @Test
cancellableContextIsAttached()431   public void cancellableContextIsAttached() {
432     Context.CancellableContext base = Context.current().withValue(FOOD, "fish").withCancellation();
433     assertFalse(base.isCurrent());
434     Context toRestore = base.attach();
435 
436     Context attached = Context.current();
437     assertSame("fish", FOOD.get());
438     assertFalse(attached.isCancelled());
439     assertNull(attached.cancellationCause());
440     assertTrue(attached.isCurrent());
441     assertTrue(base.isCurrent());
442 
443     attached.addListener(cancellationListener, MoreExecutors.directExecutor());
444     Throwable t = new Throwable();
445     base.cancel(t);
446     assertTrue(attached.isCancelled());
447     assertSame(t, attached.cancellationCause());
448     assertSame(attached, listenerNotifedContext);
449 
450     base.detach(toRestore);
451   }
452 
453   @Test
cancellableContextCascadesFromCancellableParent()454   public void cancellableContextCascadesFromCancellableParent() {
455     // Root is not cancellable so we can't cascade from it
456     Context.CancellableContext base = Context.current().withCancellation();
457     Context child = base.withCancellation();
458     child.addListener(cancellationListener, MoreExecutors.directExecutor());
459     assertFalse(base.isCancelled());
460     assertFalse(child.isCancelled());
461     IllegalStateException cause = new IllegalStateException();
462     base.cancel(cause);
463     assertTrue(base.isCancelled());
464     assertSame(cause, base.cancellationCause());
465     assertSame(child, listenerNotifedContext);
466     assertTrue(child.isCancelled());
467     assertSame(cause, child.cancellationCause());
468     assertEquals(0, base.listenerCount());
469     assertEquals(0, child.listenerCount());
470   }
471 
472   @Test
nonCascadingCancellationDoesNotNotifyForked()473   public void nonCascadingCancellationDoesNotNotifyForked() {
474     Context.CancellableContext base = Context.current().withCancellation();
475     Context fork = base.fork();
476     fork.addListener(cancellationListener, MoreExecutors.directExecutor());
477     assertEquals(0, base.listenerCount());
478     assertEquals(0, fork.listenerCount());
479     assertTrue(base.cancel(new Throwable()));
480     assertNull(listenerNotifedContext);
481     assertFalse(fork.isCancelled());
482     assertNull(fork.cancellationCause());
483   }
484 
485   @Test
testWrapRunnable()486   public void testWrapRunnable() {
487     Context base = Context.current().withValue(PET, "cat");
488     Context current = Context.current().withValue(PET, "fish");
489     current.attach();
490 
491     base.wrap(runner).run();
492     assertSame(base, observed);
493     assertSame(current, Context.current());
494 
495     current.wrap(runner).run();
496     assertSame(current, observed);
497     assertSame(current, Context.current());
498 
499     final TestError err = new TestError();
500     try {
501       base.wrap(new Runnable() {
502         @Override
503         public void run() {
504           throw err;
505         }
506       }).run();
507       fail("Expected exception");
508     } catch (TestError ex) {
509       assertSame(err, ex);
510     }
511     assertSame(current, Context.current());
512 
513     current.detach(Context.ROOT);
514   }
515 
516   @Test
testWrapCallable()517   public void testWrapCallable() throws Exception {
518     Context base = Context.current().withValue(PET, "cat");
519     Context current = Context.current().withValue(PET, "fish");
520     current.attach();
521 
522     final Object ret = new Object();
523     Callable<Object> callable = new Callable<Object>() {
524       @Override
525       public Object call() {
526         runner.run();
527         return ret;
528       }
529     };
530 
531     assertSame(ret, base.wrap(callable).call());
532     assertSame(base, observed);
533     assertSame(current, Context.current());
534 
535     assertSame(ret, current.wrap(callable).call());
536     assertSame(current, observed);
537     assertSame(current, Context.current());
538 
539     final TestError err = new TestError();
540     try {
541       base.wrap(new Callable<Object>() {
542         @Override
543         public Object call() {
544           throw err;
545         }
546       }).call();
547       fail("Excepted exception");
548     } catch (TestError ex) {
549       assertSame(err, ex);
550     }
551     assertSame(current, Context.current());
552 
553     current.detach(Context.ROOT);
554   }
555 
556   @Test
currentContextExecutor()557   public void currentContextExecutor() {
558     QueuedExecutor queuedExecutor = new QueuedExecutor();
559     Executor executor = Context.currentContextExecutor(queuedExecutor);
560     Context base = Context.current().withValue(PET, "cat");
561     Context previous = base.attach();
562     try {
563       executor.execute(runner);
564     } finally {
565       base.detach(previous);
566     }
567     assertEquals(1, queuedExecutor.runnables.size());
568     queuedExecutor.runnables.remove().run();
569     assertSame(base, observed);
570   }
571 
572   @Test
fixedContextExecutor()573   public void fixedContextExecutor() {
574     Context base = Context.current().withValue(PET, "cat");
575     QueuedExecutor queuedExecutor = new QueuedExecutor();
576     base.fixedContextExecutor(queuedExecutor).execute(runner);
577     assertEquals(1, queuedExecutor.runnables.size());
578     queuedExecutor.runnables.remove().run();
579     assertSame(base, observed);
580   }
581 
582   @Test
typicalTryFinallyHandling()583   public void typicalTryFinallyHandling() {
584     Context base = Context.current().withValue(COLOR, "blue");
585     Context previous = base.attach();
586     try {
587       assertTrue(base.isCurrent());
588       // Do something
589     } finally {
590       base.detach(previous);
591     }
592     assertFalse(base.isCurrent());
593   }
594 
595   @Test
typicalCancellableTryCatchFinallyHandling()596   public void typicalCancellableTryCatchFinallyHandling() {
597     Context.CancellableContext base = Context.current().withCancellation();
598     Context previous = base.attach();
599     try {
600       // Do something
601       throw new IllegalStateException("Argh");
602     } catch (IllegalStateException ise) {
603       base.cancel(ise);
604     } finally {
605       base.detachAndCancel(previous, null);
606     }
607     assertTrue(base.isCancelled());
608     assertNotNull(base.cancellationCause());
609   }
610 
611   @Test
rootHasNoDeadline()612   public void rootHasNoDeadline() {
613     assertNull(Context.ROOT.getDeadline());
614   }
615 
616   @Test
contextWithDeadlineHasDeadline()617   public void contextWithDeadlineHasDeadline() {
618     Context.CancellableContext cancellableContext =
619         Context.ROOT.withDeadlineAfter(1, TimeUnit.SECONDS, scheduler);
620     assertNotNull(cancellableContext.getDeadline());
621   }
622 
623   @Test
earlierParentDeadlineTakesPrecedenceOverLaterChildDeadline()624   public void earlierParentDeadlineTakesPrecedenceOverLaterChildDeadline() throws Exception {
625     final Deadline sooner = Deadline.after(100, TimeUnit.MILLISECONDS);
626     final Deadline later = Deadline.after(1, TimeUnit.MINUTES);
627     Context.CancellableContext parent = Context.ROOT.withDeadline(sooner, scheduler);
628     Context.CancellableContext child = parent.withDeadline(later, scheduler);
629     assertSame(parent.getDeadline(), sooner);
630     assertSame(child.getDeadline(), sooner);
631     final CountDownLatch latch = new CountDownLatch(1);
632     final AtomicReference<Exception> error = new AtomicReference<>();
633     child.addListener(new Context.CancellationListener() {
634       @Override
635       public void cancelled(Context context) {
636         try {
637           assertTrue(sooner.isExpired());
638           assertFalse(later.isExpired());
639         } catch (Exception e) {
640           error.set(e);
641         }
642         latch.countDown();
643       }
644     }, MoreExecutors.directExecutor());
645     assertTrue("cancellation failed", latch.await(3, TimeUnit.SECONDS));
646     if (error.get() != null) {
647       throw error.get();
648     }
649   }
650 
651   @Test
earlierChldDeadlineTakesPrecedenceOverLaterParentDeadline()652   public void earlierChldDeadlineTakesPrecedenceOverLaterParentDeadline() {
653     Deadline sooner = Deadline.after(1, TimeUnit.HOURS);
654     Deadline later = Deadline.after(1, TimeUnit.DAYS);
655     Context.CancellableContext parent = Context.ROOT.withDeadline(later, scheduler);
656     Context.CancellableContext child = parent.withDeadline(sooner, scheduler);
657     assertSame(parent.getDeadline(), later);
658     assertSame(child.getDeadline(), sooner);
659   }
660 
661   @Test
forkingContextDoesNotCarryDeadline()662   public void forkingContextDoesNotCarryDeadline() {
663     Deadline deadline = Deadline.after(1, TimeUnit.HOURS);
664     Context.CancellableContext parent = Context.ROOT.withDeadline(deadline, scheduler);
665     Context fork = parent.fork();
666     assertNull(fork.getDeadline());
667   }
668 
669   @Test
cancellationDoesNotExpireDeadline()670   public void cancellationDoesNotExpireDeadline() {
671     Deadline deadline = Deadline.after(1, TimeUnit.HOURS);
672     Context.CancellableContext parent = Context.ROOT.withDeadline(deadline, scheduler);
673     parent.cancel(null);
674     assertFalse(deadline.isExpired());
675   }
676 
677   @Test
absoluteDeadlineTriggersAndPropagates()678   public void absoluteDeadlineTriggersAndPropagates() throws Exception {
679     Context base = Context.current().withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler);
680     Context child = base.withValue(FOOD, "lasagna");
681     child.addListener(cancellationListener, MoreExecutors.directExecutor());
682     assertFalse(base.isCancelled());
683     assertFalse(child.isCancelled());
684     assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
685     assertTrue(base.isCancelled());
686     assertTrue(base.cancellationCause() instanceof TimeoutException);
687     assertSame(child, listenerNotifedContext);
688     assertTrue(child.isCancelled());
689     assertSame(base.cancellationCause(), child.cancellationCause());
690   }
691 
692   @Test
relativeDeadlineTriggersAndPropagates()693   public void relativeDeadlineTriggersAndPropagates() throws Exception {
694     Context base = Context.current().withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler);
695     Context child = base.withValue(FOOD, "lasagna");
696     child.addListener(cancellationListener, MoreExecutors.directExecutor());
697     assertFalse(base.isCancelled());
698     assertFalse(child.isCancelled());
699     assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
700     assertTrue(base.isCancelled());
701     assertTrue(base.cancellationCause() instanceof TimeoutException);
702     assertSame(child, listenerNotifedContext);
703     assertTrue(child.isCancelled());
704     assertSame(base.cancellationCause(), child.cancellationCause());
705   }
706 
707   @Test
innerDeadlineCompletesBeforeOuter()708   public void innerDeadlineCompletesBeforeOuter() throws Exception {
709     Context base = Context.current().withDeadline(Deadline.after(2, TimeUnit.SECONDS), scheduler);
710     Context child = base.withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler);
711     child.addListener(cancellationListener, MoreExecutors.directExecutor());
712     assertFalse(base.isCancelled());
713     assertFalse(child.isCancelled());
714     assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
715     assertFalse(base.isCancelled());
716     assertSame(child, listenerNotifedContext);
717     assertTrue(child.isCancelled());
718     assertTrue(child.cancellationCause() instanceof TimeoutException);
719 
720     deadlineLatch = new CountDownLatch(1);
721     base.addListener(cancellationListener, MoreExecutors.directExecutor());
722     assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
723     assertTrue(base.isCancelled());
724     assertTrue(base.cancellationCause() instanceof TimeoutException);
725     assertNotSame(base.cancellationCause(), child.cancellationCause());
726   }
727 
728   @Test
cancellationCancelsScheduledTask()729   public void cancellationCancelsScheduledTask() {
730     ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
731     try {
732       assertEquals(0, executor.getQueue().size());
733       Context.CancellableContext base
734           = Context.current().withDeadline(Deadline.after(1, TimeUnit.DAYS), executor);
735       assertEquals(1, executor.getQueue().size());
736       base.cancel(null);
737       executor.purge();
738       assertEquals(0, executor.getQueue().size());
739     } finally {
740       executor.shutdown();
741     }
742   }
743 
744   private static class QueuedExecutor implements Executor {
745     private final Queue<Runnable> runnables = new ArrayDeque<>();
746 
747     @Override
execute(Runnable r)748     public void execute(Runnable r) {
749       runnables.add(r);
750     }
751   }
752 
753   @Test
childContextListenerNotifiedAfterParentListener()754   public void childContextListenerNotifiedAfterParentListener() {
755     Context.CancellableContext parent = Context.current().withCancellation();
756     Context child = parent.withValue(COLOR, "red");
757     final AtomicBoolean childAfterParent = new AtomicBoolean();
758     final AtomicBoolean parentCalled = new AtomicBoolean();
759     child.addListener(new Context.CancellationListener() {
760       @Override
761       public void cancelled(Context context) {
762         if (parentCalled.get()) {
763           childAfterParent.set(true);
764         }
765       }
766     }, MoreExecutors.directExecutor());
767     parent.addListener(new Context.CancellationListener() {
768       @Override
769       public void cancelled(Context context) {
770         parentCalled.set(true);
771       }
772     }, MoreExecutors.directExecutor());
773     parent.cancel(null);
774     assertTrue(parentCalled.get());
775     assertTrue(childAfterParent.get());
776   }
777 
778   @Test
expiredDeadlineShouldCancelContextImmediately()779   public void expiredDeadlineShouldCancelContextImmediately() {
780     Context parent = Context.current();
781     assertFalse(parent.isCancelled());
782 
783     Context.CancellableContext context = parent.withDeadlineAfter(0, TimeUnit.SECONDS, scheduler);
784     assertTrue(context.isCancelled());
785     assertThat(context.cancellationCause(), instanceOf(TimeoutException.class));
786 
787     assertFalse(parent.isCancelled());
788     Deadline deadline = Deadline.after(-10, TimeUnit.SECONDS);
789     assertTrue(deadline.isExpired());
790     context = parent.withDeadline(deadline, scheduler);
791     assertTrue(context.isCancelled());
792     assertThat(context.cancellationCause(), instanceOf(TimeoutException.class));
793   }
794 
795   /**
796    * Tests initializing the {@link Context} class with a custom logger which uses Context's storage
797    * when logging.
798    */
799   @Test
initContextWithCustomClassLoaderWithCustomLogger()800   public void initContextWithCustomClassLoaderWithCustomLogger() throws Exception {
801     StaticTestingClassLoader classLoader =
802         new StaticTestingClassLoader(
803             getClass().getClassLoader(), Pattern.compile("io\\.grpc\\.[^.]+"));
804     Class<?> runnable =
805         classLoader.loadClass(LoadMeWithStaticTestingClassLoader.class.getName());
806 
807     ((Runnable) runnable.getDeclaredConstructor().newInstance()).run();
808   }
809 
810   /**
811    * Ensure that newly created threads can attach/detach a context.
812    * The current test thread already has a context manually attached in {@link #setUp()}.
813    */
814   @Test
newThreadAttachContext()815   public void newThreadAttachContext() throws Exception {
816     Context parent = Context.current().withValue(COLOR, "blue");
817     parent.call(new Callable<Object>() {
818       @Override
819       public Object call() throws Exception {
820         assertEquals("blue", COLOR.get());
821 
822         final Context child = Context.current().withValue(COLOR, "red");
823         Future<String> workerThreadVal = scheduler
824             .submit(new Callable<String>() {
825               @Override
826               public String call() {
827                 Context initial = Context.current();
828                 assertNotNull(initial);
829                 Context toRestore = child.attach();
830                 try {
831                   assertNotNull(toRestore);
832                   return COLOR.get();
833                 } finally {
834                   child.detach(toRestore);
835                   assertEquals(initial, Context.current());
836                 }
837               }
838             });
839         assertEquals("red", workerThreadVal.get());
840 
841         assertEquals("blue", COLOR.get());
842         return null;
843       }
844     });
845   }
846 
847   /**
848    * Similar to {@link #newThreadAttachContext()} but without giving the new thread a specific ctx.
849    */
850   @Test
newThreadWithoutContext()851   public void newThreadWithoutContext() throws Exception {
852     Context parent = Context.current().withValue(COLOR, "blue");
853     parent.call(new Callable<Object>() {
854       @Override
855       public Object call() throws Exception {
856         assertEquals("blue", COLOR.get());
857 
858         Future<String> workerThreadVal = scheduler
859             .submit(new Callable<String>() {
860               @Override
861               public String call() {
862                 assertNotNull(Context.current());
863                 return COLOR.get();
864               }
865             });
866         assertNull(workerThreadVal.get());
867 
868         assertEquals("blue", COLOR.get());
869         return null;
870       }
871     });
872   }
873 
874   @Test
storageReturnsNullTest()875   public void storageReturnsNullTest() throws Exception {
876     // TODO(sergiitk): JDK-8210522 changes the behaviour of Java reflection to filter out
877     //   security-sensitive fields in the java.lang.reflect.Field. This prohibits
878     //   Field.class.getDeclaredFields("modifiers") call we rely on in this test.
879     //   Until we have a good solution for setting a custom storage for testing purposes,
880     //   we'll have to skip this test for JDK >= 11. Ref https://bugs.openjdk.org/browse/JDK-8210522
881     double javaVersion;
882     // Graceful version check. Run the test if the version undetermined.
883     try {
884       javaVersion = Double.parseDouble(System.getProperty("java.specification.version", "0"));
885     } catch (NumberFormatException e) {
886       javaVersion = 0;
887     }
888     assume().that(javaVersion).isLessThan(11);
889 
890     Class<?> lazyStorageClass = Class.forName("io.grpc.Context$LazyStorage");
891     Field storage = lazyStorageClass.getDeclaredField("storage");
892     assertTrue(Modifier.isFinal(storage.getModifiers()));
893     // use reflection to forcibly change the storage object to a test object
894     storage.setAccessible(true);
895     Field modifiersField = Field.class.getDeclaredField("modifiers");
896     modifiersField.setAccessible(true);
897     int storageModifiers = modifiersField.getInt(storage);
898     modifiersField.set(storage, storageModifiers & ~Modifier.FINAL);
899     Object o = storage.get(null);
900     Context.Storage originalStorage = (Context.Storage) o;
901     try {
902       storage.set(null, new Context.Storage() {
903         @Override
904         public Context doAttach(Context toAttach) {
905           return null;
906         }
907 
908         @Override
909         public void detach(Context toDetach, Context toRestore) {
910           // noop
911         }
912 
913         @Override
914         public Context current() {
915           return null;
916         }
917       });
918       // current() returning null gets transformed into ROOT
919       assertEquals(Context.ROOT, Context.current());
920 
921       // doAttach() returning null gets transformed into ROOT
922       Context blueContext = Context.current().withValue(COLOR, "blue");
923       Context toRestore = blueContext.attach();
924       assertEquals(Context.ROOT, toRestore);
925 
926       // final sanity check
927       blueContext.detach(toRestore);
928       assertEquals(Context.ROOT, Context.current());
929     } finally {
930       // undo the changes
931       storage.set(null, originalStorage);
932       storage.setAccessible(false);
933       modifiersField.set(storage, storageModifiers | Modifier.FINAL);
934       modifiersField.setAccessible(false);
935     }
936   }
937 
938   @Test
cancellableAncestorTest()939   public void cancellableAncestorTest() {
940     Context c = Context.current();
941     assertNull(cancellableAncestor(c));
942 
943     Context.CancellableContext withCancellation = c.withCancellation();
944     assertEquals(withCancellation, cancellableAncestor(withCancellation));
945 
946     Context child = withCancellation.withValue(COLOR, "blue");
947     assertFalse(child instanceof Context.CancellableContext);
948     assertEquals(withCancellation, cancellableAncestor(child));
949 
950     Context grandChild = child.withValue(COLOR, "red");
951     assertFalse(grandChild instanceof Context.CancellableContext);
952     assertEquals(withCancellation, cancellableAncestor(grandChild));
953   }
954 
955   @Test
cancellableAncestorIntegrationTest()956   public void cancellableAncestorIntegrationTest() {
957     Context base = Context.current();
958 
959     Context blue = base.withValue(COLOR, "blue");
960     assertNull(blue.cancellableAncestor);
961     Context.CancellableContext cancellable = blue.withCancellation();
962     assertNull(cancellable.cancellableAncestor);
963     Context childOfCancel = cancellable.withValue(PET, "cat");
964     assertSame(cancellable, childOfCancel.cancellableAncestor);
965     Context grandChildOfCancel = childOfCancel.withValue(FOOD, "lasagna");
966     assertSame(cancellable, grandChildOfCancel.cancellableAncestor);
967 
968     Context.CancellableContext cancellable2 = childOfCancel.withCancellation();
969     assertSame(cancellable, cancellable2.cancellableAncestor);
970     Context childOfCancellable2 = cancellable2.withValue(PET, "dog");
971     assertSame(cancellable2, childOfCancellable2.cancellableAncestor);
972   }
973 
974   @Test
cancellableAncestorFork()975   public void cancellableAncestorFork() {
976     Context.CancellableContext cancellable = Context.current().withCancellation();
977     Context fork = cancellable.fork();
978     assertNull(fork.cancellableAncestor);
979   }
980 
981   @Test
cancellableContext_closeCancelsWithNullCause()982   public void cancellableContext_closeCancelsWithNullCause() {
983     Context.CancellableContext cancellable = Context.current().withCancellation();
984     cancellable.close();
985     assertTrue(cancellable.isCancelled());
986     assertNull(cancellable.cancellationCause());
987   }
988 
989   @Test
errorWhenAncestryLengthLong()990   public void errorWhenAncestryLengthLong() {
991     final AtomicReference<LogRecord> logRef = new AtomicReference<>();
992     Handler handler = new Handler() {
993       @Override
994       public void publish(LogRecord record) {
995         logRef.set(record);
996       }
997 
998       @Override
999       public void flush() {
1000       }
1001 
1002       @Override
1003       public void close() throws SecurityException {
1004       }
1005     };
1006     Logger logger = Logger.getLogger(Context.class.getName());
1007     try {
1008       logger.addHandler(handler);
1009       Context ctx = Context.current();
1010       for (int i = 0; i < Context.CONTEXT_DEPTH_WARN_THRESH ; i++) {
1011         assertNull(logRef.get());
1012         ctx = ctx.fork();
1013       }
1014       ctx.fork();
1015       assertNotNull(logRef.get());
1016       assertNotNull(logRef.get().getThrown());
1017       assertEquals(Level.SEVERE, logRef.get().getLevel());
1018     } finally {
1019       logger.removeHandler(handler);
1020     }
1021   }
1022 
1023   // UsedReflectively
1024   public static final class LoadMeWithStaticTestingClassLoader implements Runnable {
1025     @Override
run()1026     public void run() {
1027       Logger logger = Logger.getLogger(Context.class.getName());
1028       logger.setLevel(Level.ALL);
1029       Handler handler = new Handler() {
1030         @Override
1031         public void publish(LogRecord record) {
1032           Context ctx = Context.current();
1033           Context previous = ctx.attach();
1034           ctx.detach(previous);
1035         }
1036 
1037         @Override
1038         public void flush() {
1039         }
1040 
1041         @Override
1042         public void close() throws SecurityException {
1043         }
1044       };
1045       logger.addHandler(handler);
1046 
1047       try {
1048         assertNotNull(Context.ROOT);
1049       } finally {
1050         logger.removeHandler(handler);
1051       }
1052     }
1053   }
1054 
1055   /** Allows more precise catch blocks than plain Error to avoid catching AssertionError. */
1056   private static final class TestError extends Error {}
1057 }
1058