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