| /* |
| * Copyright 2015, Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package io.grpc; |
| |
| import static org.hamcrest.core.IsInstanceOf.instanceOf; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNotSame; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import com.google.common.util.concurrent.MoreExecutors; |
| import com.google.common.util.concurrent.SettableFuture; |
| import java.util.ArrayDeque; |
| import java.util.Queue; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.ScheduledThreadPoolExecutor; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.logging.Handler; |
| import java.util.logging.Level; |
| import java.util.logging.LogRecord; |
| import java.util.logging.Logger; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Tests for {@link Context}. |
| */ |
| @RunWith(JUnit4.class) |
| public class ContextTest { |
| |
| private static final Context.Key<String> PET = Context.key("pet"); |
| private static final Context.Key<String> FOOD = Context.keyWithDefault("food", "lasagna"); |
| private static final Context.Key<String> COLOR = Context.key("color"); |
| private static final Context.Key<Object> FAVORITE = Context.key("favorite"); |
| private static final Context.Key<Integer> LUCKY = Context.key("lucky"); |
| |
| private Context listenerNotifedContext; |
| private CountDownLatch deadlineLatch = new CountDownLatch(1); |
| private Context.CancellationListener cancellationListener = new Context.CancellationListener() { |
| @Override |
| public void cancelled(Context context) { |
| listenerNotifedContext = context; |
| deadlineLatch.countDown(); |
| } |
| }; |
| |
| private Context observed; |
| private Runnable runner = new Runnable() { |
| @Override |
| public void run() { |
| observed = Context.current(); |
| } |
| }; |
| private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); |
| |
| @Before |
| public void setUp() throws Exception { |
| Context.ROOT.attach(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| scheduler.shutdown(); |
| } |
| |
| @Test |
| public void defaultContext() throws Exception { |
| final SettableFuture<Context> contextOfNewThread = SettableFuture.create(); |
| Context contextOfThisThread = Context.ROOT.withValue(PET, "dog"); |
| contextOfThisThread.attach(); |
| new Thread(new Runnable() { |
| @Override |
| public void run() { |
| contextOfNewThread.set(Context.current()); |
| } |
| }).start(); |
| assertNotNull(contextOfNewThread.get(5, TimeUnit.SECONDS)); |
| assertNotSame(contextOfThisThread, contextOfNewThread.get()); |
| assertSame(contextOfThisThread, Context.current()); |
| } |
| |
| @Test |
| public void rootCanBeAttached() { |
| Context fork = Context.ROOT.fork(); |
| fork.attach(); |
| Context.ROOT.attach(); |
| assertTrue(Context.ROOT.isCurrent()); |
| fork.attach(); |
| assertTrue(fork.isCurrent()); |
| } |
| |
| @Test |
| public void rootCanNeverHaveAListener() { |
| Context root = Context.current(); |
| root.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertEquals(0, root.listenerCount()); |
| } |
| |
| @Test |
| public void rootIsNotCancelled() { |
| assertFalse(Context.ROOT.isCancelled()); |
| assertNull(Context.ROOT.cancellationCause()); |
| } |
| |
| @Test |
| public void attachedCancellableContextCannotBeCastFromCurrent() { |
| Context initial = Context.current(); |
| Context.CancellableContext base = initial.withCancellation(); |
| base.attach(); |
| assertFalse(Context.current() instanceof Context.CancellableContext); |
| assertNotSame(base, Context.current()); |
| assertNotSame(initial, Context.current()); |
| base.detachAndCancel(initial, null); |
| assertSame(initial, Context.current()); |
| } |
| |
| @Test |
| public void attachingNonCurrentReturnsCurrent() { |
| Context initial = Context.current(); |
| Context base = initial.withValue(PET, "dog"); |
| assertSame(initial, base.attach()); |
| assertSame(base, initial.attach()); |
| } |
| |
| @Test |
| public void detachingNonCurrentLogsSevereMessage() { |
| final AtomicReference<LogRecord> logRef = new AtomicReference<LogRecord>(); |
| Handler handler = new Handler() { |
| @Override |
| public void publish(LogRecord record) { |
| logRef.set(record); |
| } |
| |
| @Override |
| public void flush() { |
| } |
| |
| @Override |
| public void close() throws SecurityException { |
| } |
| }; |
| Logger logger = Logger.getLogger(Context.storage().getClass().getName()); |
| try { |
| logger.addHandler(handler); |
| Context initial = Context.current(); |
| Context base = initial.withValue(PET, "dog"); |
| // Base is not attached |
| base.detach(initial); |
| assertSame(initial, Context.current()); |
| assertNotNull(logRef.get()); |
| assertEquals(Level.SEVERE, logRef.get().getLevel()); |
| } finally { |
| logger.removeHandler(handler); |
| } |
| } |
| |
| @Test |
| public void valuesAndOverrides() { |
| Context base = Context.current().withValue(PET, "dog"); |
| Context child = base.withValues(PET, null, FOOD, "cheese"); |
| |
| base.attach(); |
| |
| assertEquals("dog", PET.get()); |
| assertEquals("lasagna", FOOD.get()); |
| assertNull(COLOR.get()); |
| |
| child.attach(); |
| |
| assertNull(PET.get()); |
| assertEquals("cheese", FOOD.get()); |
| assertNull(COLOR.get()); |
| |
| child.detach(base); |
| |
| // Should have values from base |
| assertEquals("dog", PET.get()); |
| assertEquals("lasagna", FOOD.get()); |
| assertNull(COLOR.get()); |
| |
| base.detach(Context.ROOT); |
| |
| assertNull(PET.get()); |
| assertEquals("lasagna", FOOD.get()); |
| assertNull(COLOR.get()); |
| } |
| |
| @Test |
| public void withValuesThree() { |
| Object fav = new Object(); |
| Context base = Context.current().withValues(PET, "dog", COLOR, "blue"); |
| Context child = base.withValues(PET, "cat", FOOD, "cheese", FAVORITE, fav); |
| |
| child.attach(); |
| |
| assertEquals("cat", PET.get()); |
| assertEquals("cheese", FOOD.get()); |
| assertEquals("blue", COLOR.get()); |
| assertEquals(fav, FAVORITE.get()); |
| |
| base.attach(); |
| } |
| |
| @Test |
| public void withValuesFour() { |
| Object fav = new Object(); |
| Context base = Context.current().withValues(PET, "dog", COLOR, "blue"); |
| Context child = base.withValues(PET, "cat", FOOD, "cheese", FAVORITE, fav, LUCKY, 7); |
| |
| child.attach(); |
| |
| assertEquals("cat", PET.get()); |
| assertEquals("cheese", FOOD.get()); |
| assertEquals("blue", COLOR.get()); |
| assertEquals(fav, FAVORITE.get()); |
| assertEquals(7, (int) LUCKY.get()); |
| |
| base.attach(); |
| } |
| |
| @Test |
| public void cancelReturnsFalseIfAlreadyCancelled() { |
| Context.CancellableContext base = Context.current().withCancellation(); |
| assertTrue(base.cancel(null)); |
| assertTrue(base.isCancelled()); |
| assertFalse(base.cancel(null)); |
| } |
| |
| @Test |
| public void notifyListenersOnCancel() { |
| class SetContextCancellationListener implements Context.CancellationListener { |
| private final AtomicReference<Context> observed; |
| |
| public SetContextCancellationListener(AtomicReference<Context> observed) { |
| this.observed = observed; |
| } |
| |
| @Override |
| public void cancelled(Context context) { |
| observed.set(context); |
| } |
| } |
| |
| Context.CancellableContext base = Context.current().withCancellation(); |
| final AtomicReference<Context> observed1 = new AtomicReference<Context>(); |
| base.addListener(new SetContextCancellationListener(observed1), MoreExecutors.directExecutor()); |
| final AtomicReference<Context> observed2 = new AtomicReference<Context>(); |
| base.addListener(new SetContextCancellationListener(observed2), MoreExecutors.directExecutor()); |
| assertNull(observed1.get()); |
| assertNull(observed2.get()); |
| base.cancel(null); |
| assertSame(base, observed1.get()); |
| assertSame(base, observed2.get()); |
| |
| final AtomicReference<Context> observed3 = new AtomicReference<Context>(); |
| base.addListener(new SetContextCancellationListener(observed3), MoreExecutors.directExecutor()); |
| assertSame(base, observed3.get()); |
| } |
| |
| @Test |
| public void exceptionOfExecutorDoesntThrow() { |
| final AtomicReference<Throwable> loggedThrowable = new AtomicReference<Throwable>(); |
| Handler logHandler = new Handler() { |
| @Override |
| public void publish(LogRecord record) { |
| Throwable thrown = record.getThrown(); |
| if (thrown != null) { |
| if (loggedThrowable.get() == null) { |
| loggedThrowable.set(thrown); |
| } else { |
| loggedThrowable.set(new RuntimeException("Too many exceptions", thrown)); |
| } |
| } |
| } |
| |
| @Override |
| public void close() {} |
| |
| @Override |
| public void flush() {} |
| }; |
| Logger logger = Logger.getLogger(Context.class.getName()); |
| logger.addHandler(logHandler); |
| try { |
| Context.CancellableContext base = Context.current().withCancellation(); |
| final AtomicReference<Runnable> observed1 = new AtomicReference<Runnable>(); |
| final Error err = new Error(); |
| base.addListener(cancellationListener, new Executor() { |
| @Override |
| public void execute(Runnable runnable) { |
| observed1.set(runnable); |
| throw err; |
| } |
| }); |
| assertNull(observed1.get()); |
| assertNull(loggedThrowable.get()); |
| base.cancel(null); |
| assertNotNull(observed1.get()); |
| assertSame(err, loggedThrowable.get()); |
| |
| final Error err2 = new Error(); |
| loggedThrowable.set(null); |
| final AtomicReference<Runnable> observed2 = new AtomicReference<Runnable>(); |
| base.addListener(cancellationListener, new Executor() { |
| @Override |
| public void execute(Runnable runnable) { |
| observed2.set(runnable); |
| throw err2; |
| } |
| }); |
| assertNotNull(observed2.get()); |
| assertSame(err2, loggedThrowable.get()); |
| } finally { |
| logger.removeHandler(logHandler); |
| } |
| } |
| |
| @Test |
| public void cascadingCancellationNotifiesChild() { |
| // Root is not cancellable so we can't cascade from it |
| Context.CancellableContext base = Context.current().withCancellation(); |
| assertEquals(0, base.listenerCount()); |
| Context child = base.withValue(FOOD, "lasagna"); |
| assertEquals(0, child.listenerCount()); |
| child.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertEquals(1, child.listenerCount()); |
| assertEquals(1, base.listenerCount()); // child is now listening to base |
| assertFalse(base.isCancelled()); |
| assertFalse(child.isCancelled()); |
| IllegalStateException cause = new IllegalStateException(); |
| base.cancel(cause); |
| assertTrue(base.isCancelled()); |
| assertSame(cause, base.cancellationCause()); |
| assertSame(child, listenerNotifedContext); |
| assertTrue(child.isCancelled()); |
| assertSame(cause, child.cancellationCause()); |
| assertEquals(0, base.listenerCount()); |
| assertEquals(0, child.listenerCount()); |
| } |
| |
| @Test |
| public void cascadingCancellationWithoutListener() { |
| Context.CancellableContext base = Context.current().withCancellation(); |
| Context child = base.withCancellation(); |
| Throwable t = new Throwable(); |
| base.cancel(t); |
| assertTrue(child.isCancelled()); |
| assertSame(t, child.cancellationCause()); |
| } |
| |
| @Test |
| public void cancellableContextIsAttached() { |
| Context.CancellableContext base = Context.current().withValue(FOOD, "fish").withCancellation(); |
| assertFalse(base.isCurrent()); |
| base.attach(); |
| |
| Context attached = Context.current(); |
| assertSame("fish", FOOD.get()); |
| assertFalse(attached.isCancelled()); |
| assertNull(attached.cancellationCause()); |
| assertTrue(attached.canBeCancelled()); |
| assertTrue(attached.isCurrent()); |
| assertTrue(base.isCurrent()); |
| |
| attached.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| Throwable t = new Throwable(); |
| base.cancel(t); |
| assertTrue(attached.isCancelled()); |
| assertSame(t, attached.cancellationCause()); |
| assertSame(attached, listenerNotifedContext); |
| |
| Context.ROOT.attach(); |
| } |
| |
| @Test |
| public void cancellableContextCascadesFromCancellableParent() { |
| // Root is not cancellable so we can't cascade from it |
| Context.CancellableContext base = Context.current().withCancellation(); |
| Context child = base.withCancellation(); |
| child.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertFalse(base.isCancelled()); |
| assertFalse(child.isCancelled()); |
| IllegalStateException cause = new IllegalStateException(); |
| base.cancel(cause); |
| assertTrue(base.isCancelled()); |
| assertSame(cause, base.cancellationCause()); |
| assertSame(child, listenerNotifedContext); |
| assertTrue(child.isCancelled()); |
| assertSame(cause, child.cancellationCause()); |
| assertEquals(0, base.listenerCount()); |
| assertEquals(0, child.listenerCount()); |
| } |
| |
| @Test |
| public void nonCascadingCancellationDoesNotNotifyForked() { |
| Context.CancellableContext base = Context.current().withCancellation(); |
| Context fork = base.fork(); |
| fork.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertEquals(0, base.listenerCount()); |
| assertEquals(0, fork.listenerCount()); |
| assertTrue(base.cancel(new Throwable())); |
| assertNull(listenerNotifedContext); |
| assertFalse(fork.isCancelled()); |
| assertNull(fork.cancellationCause()); |
| } |
| |
| @Test |
| public void testWrapRunnable() throws Exception { |
| Context base = Context.current().withValue(PET, "cat"); |
| Context current = Context.current().withValue(PET, "fish"); |
| current.attach(); |
| |
| base.wrap(runner).run(); |
| assertSame(base, observed); |
| assertSame(current, Context.current()); |
| |
| current.wrap(runner).run(); |
| assertSame(current, observed); |
| assertSame(current, Context.current()); |
| |
| final Error err = new Error(); |
| try { |
| base.wrap(new Runnable() { |
| @Override |
| public void run() { |
| throw err; |
| } |
| }).run(); |
| fail("Expected exception"); |
| } catch (Error ex) { |
| assertSame(err, ex); |
| } |
| assertSame(current, Context.current()); |
| |
| current.detach(Context.ROOT); |
| } |
| |
| @Test |
| public void testWrapCallable() throws Exception { |
| Context base = Context.current().withValue(PET, "cat"); |
| Context current = Context.current().withValue(PET, "fish"); |
| current.attach(); |
| |
| final Object ret = new Object(); |
| Callable<Object> callable = new Callable<Object>() { |
| @Override |
| public Object call() { |
| runner.run(); |
| return ret; |
| } |
| }; |
| |
| assertSame(ret, base.wrap(callable).call()); |
| assertSame(base, observed); |
| assertSame(current, Context.current()); |
| |
| assertSame(ret, current.wrap(callable).call()); |
| assertSame(current, observed); |
| assertSame(current, Context.current()); |
| |
| final Error err = new Error(); |
| try { |
| base.wrap(new Callable<Object>() { |
| @Override |
| public Object call() { |
| throw err; |
| } |
| }).call(); |
| fail("Excepted exception"); |
| } catch (Error ex) { |
| assertSame(err, ex); |
| } |
| assertSame(current, Context.current()); |
| |
| current.detach(Context.ROOT); |
| } |
| |
| @Test |
| public void currentContextExecutor() throws Exception { |
| QueuedExecutor queuedExecutor = new QueuedExecutor(); |
| Executor executor = Context.currentContextExecutor(queuedExecutor); |
| Context base = Context.current().withValue(PET, "cat"); |
| Context previous = base.attach(); |
| try { |
| executor.execute(runner); |
| } finally { |
| base.detach(previous); |
| } |
| assertEquals(1, queuedExecutor.runnables.size()); |
| queuedExecutor.runnables.remove().run(); |
| assertSame(base, observed); |
| } |
| |
| @Test |
| public void fixedContextExecutor() throws Exception { |
| Context base = Context.current().withValue(PET, "cat"); |
| QueuedExecutor queuedExecutor = new QueuedExecutor(); |
| base.fixedContextExecutor(queuedExecutor).execute(runner); |
| assertEquals(1, queuedExecutor.runnables.size()); |
| queuedExecutor.runnables.remove().run(); |
| assertSame(base, observed); |
| } |
| |
| @Test |
| public void typicalTryFinallyHandling() throws Exception { |
| Context base = Context.current().withValue(COLOR, "blue"); |
| Context previous = base.attach(); |
| try { |
| assertTrue(base.isCurrent()); |
| // Do something |
| } finally { |
| base.detach(previous); |
| } |
| assertFalse(base.isCurrent()); |
| } |
| |
| @Test |
| public void typicalCancellableTryCatchFinallyHandling() throws Exception { |
| Context.CancellableContext base = Context.current().withCancellation(); |
| Context previous = base.attach(); |
| try { |
| // Do something |
| throw new IllegalStateException("Argh"); |
| } catch (IllegalStateException ise) { |
| base.cancel(ise); |
| } finally { |
| base.detachAndCancel(previous, null); |
| } |
| assertTrue(base.isCancelled()); |
| assertNotNull(base.cancellationCause()); |
| } |
| |
| @Test |
| public void rootHasNoDeadline() { |
| assertNull(Context.ROOT.getDeadline()); |
| } |
| |
| @Test |
| public void contextWithDeadlineHasDeadline() { |
| Context.CancellableContext cancellableContext = |
| Context.ROOT.withDeadlineAfter(1, TimeUnit.SECONDS, scheduler); |
| assertNotNull(cancellableContext.getDeadline()); |
| } |
| |
| @Test |
| public void earlierParentDeadlineTakesPrecedenceOverLaterChildDeadline() throws Exception { |
| final Deadline sooner = Deadline.after(100, TimeUnit.MILLISECONDS); |
| final Deadline later = Deadline.after(1, TimeUnit.MINUTES); |
| Context.CancellableContext parent = Context.ROOT.withDeadline(sooner, scheduler); |
| Context.CancellableContext child = parent.withDeadline(later, scheduler); |
| assertSame(parent.getDeadline(), sooner); |
| assertSame(child.getDeadline(), sooner); |
| final CountDownLatch latch = new CountDownLatch(1); |
| final AtomicReference<Exception> error = new AtomicReference<Exception>(); |
| child.addListener(new Context.CancellationListener() { |
| @Override |
| public void cancelled(Context context) { |
| try { |
| assertTrue(sooner.isExpired()); |
| assertFalse(later.isExpired()); |
| } catch (Exception e) { |
| error.set(e); |
| } |
| latch.countDown(); |
| } |
| }, MoreExecutors.directExecutor()); |
| latch.await(3, TimeUnit.SECONDS); |
| if (error.get() != null) { |
| throw error.get(); |
| } |
| } |
| |
| @Test |
| public void earlierChldDeadlineTakesPrecedenceOverLaterParentDeadline() { |
| Deadline sooner = Deadline.after(1, TimeUnit.HOURS); |
| Deadline later = Deadline.after(1, TimeUnit.DAYS); |
| Context.CancellableContext parent = Context.ROOT.withDeadline(later, scheduler); |
| Context.CancellableContext child = parent.withDeadline(sooner, scheduler); |
| assertSame(parent.getDeadline(), later); |
| assertSame(child.getDeadline(), sooner); |
| } |
| |
| @Test |
| public void forkingContextDoesNotCarryDeadline() { |
| Deadline deadline = Deadline.after(1, TimeUnit.HOURS); |
| Context.CancellableContext parent = Context.ROOT.withDeadline(deadline, scheduler); |
| Context fork = parent.fork(); |
| assertNull(fork.getDeadline()); |
| } |
| |
| @Test |
| public void cancellationDoesNotExpireDeadline() { |
| Deadline deadline = Deadline.after(1, TimeUnit.HOURS); |
| Context.CancellableContext parent = Context.ROOT.withDeadline(deadline, scheduler); |
| parent.cancel(null); |
| assertFalse(deadline.isExpired()); |
| } |
| |
| @Test |
| public void absoluteDeadlineTriggersAndPropagates() throws Exception { |
| Context base = Context.current().withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler); |
| Context child = base.withValue(FOOD, "lasagna"); |
| child.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertFalse(base.isCancelled()); |
| assertFalse(child.isCancelled()); |
| assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS)); |
| assertTrue(base.isCancelled()); |
| assertTrue(base.cancellationCause() instanceof TimeoutException); |
| assertSame(child, listenerNotifedContext); |
| assertTrue(child.isCancelled()); |
| assertSame(base.cancellationCause(), child.cancellationCause()); |
| } |
| |
| @Test |
| public void relativeDeadlineTriggersAndPropagates() throws Exception { |
| Context base = Context.current().withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler); |
| Context child = base.withValue(FOOD, "lasagna"); |
| child.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertFalse(base.isCancelled()); |
| assertFalse(child.isCancelled()); |
| assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS)); |
| assertTrue(base.isCancelled()); |
| assertTrue(base.cancellationCause() instanceof TimeoutException); |
| assertSame(child, listenerNotifedContext); |
| assertTrue(child.isCancelled()); |
| assertSame(base.cancellationCause(), child.cancellationCause()); |
| } |
| |
| @Test |
| public void innerDeadlineCompletesBeforeOuter() throws Exception { |
| Context base = Context.current().withDeadline(Deadline.after(2, TimeUnit.SECONDS), scheduler); |
| Context child = base.withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler); |
| child.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertFalse(base.isCancelled()); |
| assertFalse(child.isCancelled()); |
| assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS)); |
| assertFalse(base.isCancelled()); |
| assertSame(child, listenerNotifedContext); |
| assertTrue(child.isCancelled()); |
| assertTrue(child.cancellationCause() instanceof TimeoutException); |
| |
| deadlineLatch = new CountDownLatch(1); |
| base.addListener(cancellationListener, MoreExecutors.directExecutor()); |
| assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS)); |
| assertTrue(base.isCancelled()); |
| assertTrue(base.cancellationCause() instanceof TimeoutException); |
| assertNotSame(base.cancellationCause(), child.cancellationCause()); |
| } |
| |
| @Test |
| public void cancellationCancelsScheduledTask() { |
| ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); |
| try { |
| assertEquals(0, executor.getQueue().size()); |
| Context.CancellableContext base |
| = Context.current().withDeadline(Deadline.after(1, TimeUnit.DAYS), executor); |
| assertEquals(1, executor.getQueue().size()); |
| base.cancel(null); |
| executor.purge(); |
| assertEquals(0, executor.getQueue().size()); |
| } finally { |
| executor.shutdown(); |
| } |
| } |
| |
| private static class QueuedExecutor implements Executor { |
| private final Queue<Runnable> runnables = new ArrayDeque<Runnable>(); |
| |
| @Override |
| public void execute(Runnable r) { |
| runnables.add(r); |
| } |
| } |
| |
| @Test |
| public void childContextListenerNotifiedAfterParentListener() { |
| Context.CancellableContext parent = Context.current().withCancellation(); |
| Context child = parent.withValue(COLOR, "red"); |
| final AtomicBoolean childAfterParent = new AtomicBoolean(); |
| final AtomicBoolean parentCalled = new AtomicBoolean(); |
| child.addListener(new Context.CancellationListener() { |
| @Override |
| public void cancelled(Context context) { |
| if (parentCalled.get()) { |
| childAfterParent.set(true); |
| } |
| } |
| }, MoreExecutors.directExecutor()); |
| parent.addListener(new Context.CancellationListener() { |
| @Override |
| public void cancelled(Context context) { |
| parentCalled.set(true); |
| } |
| }, MoreExecutors.directExecutor()); |
| parent.cancel(null); |
| assertTrue(parentCalled.get()); |
| assertTrue(childAfterParent.get()); |
| } |
| |
| @Test |
| public void expiredDeadlineShouldCancelContextImmediately() { |
| Context parent = Context.current(); |
| assertFalse(parent.isCancelled()); |
| |
| Context.CancellableContext context = parent.withDeadlineAfter(0, TimeUnit.SECONDS, scheduler); |
| assertTrue(context.isCancelled()); |
| assertThat(context.cancellationCause(), instanceOf(TimeoutException.class)); |
| |
| assertFalse(parent.isCancelled()); |
| Deadline deadline = Deadline.after(-10, TimeUnit.SECONDS); |
| assertTrue(deadline.isExpired()); |
| context = parent.withDeadline(deadline, scheduler); |
| assertTrue(context.isCancelled()); |
| assertThat(context.cancellationCause(), instanceOf(TimeoutException.class)); |
| } |
| } |