/** * Cancel this context and detach it as the current context from the thread. * * @param toAttach context to make current. * @param cause of cancellation, can be {@code null}. */ public void detachAndCancel(Context toAttach, @Nullable Throwable cause) { try { detach(toAttach); } finally { cancel(cause); } }
@Test public void contextCancellationCancelsStream() throws Exception { // Attach the context which is recorded when the call is created Context.CancellableContext cancellableContext = Context.current().withCancellation(); Context previous = cancellableContext.attach(); ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( DESCRIPTOR, new SerializingExecutor(Executors.newSingleThreadExecutor()), CallOptions.DEFAULT, provider, deadlineCancellationExecutor) .setDecompressorRegistry(decompressorRegistry); previous.attach(); call.start(callListener, new Metadata()); Throwable t = new Throwable(); cancellableContext.cancel(t); verify(stream, times(1)).cancel(statusArgumentCaptor.capture()); verify(stream, times(1)).cancel(statusCaptor.capture()); assertEquals(Status.Code.CANCELLED, statusCaptor.getValue().getCode()); }
@Test public void statusFromCancelled_returnCancelledIfCauseIsNull() { Context.CancellableContext cancellableContext = Context.current().withCancellation(); cancellableContext.cancel(null); assertTrue(cancellableContext.isCancelled()); Status status = statusFromCancelled(cancellableContext); assertNotNull(status); assertEquals(Status.Code.CANCELLED, status.getCode()); }
@Test public void statusFromCancelled_returnStatusAsSetOnCtx() { Context.CancellableContext cancellableContext = Context.current().withCancellation(); cancellableContext.cancel(Status.DEADLINE_EXCEEDED.withDescription("foo bar").asException()); Status status = statusFromCancelled(cancellableContext); assertNotNull(status); assertEquals(Status.Code.DEADLINE_EXCEEDED, status.getCode()); assertEquals("foo bar", status.getDescription()); }
@Test public void statusFromCancelled_shouldReturnStatusWithCauseAttached() { Context.CancellableContext cancellableContext = Context.current().withCancellation(); Throwable t = new Throwable(); cancellableContext.cancel(t); Status status = statusFromCancelled(cancellableContext); assertNotNull(status); assertEquals(Status.Code.CANCELLED, status.getCode()); assertSame(t, status.getCause()); }
/** This is a whitebox test, to verify a special case of the implementation. */ @Test public void statusFromCancelled_StatusUnknownShouldWork() { Context.CancellableContext cancellableContext = Context.current().withCancellation(); Exception e = Status.UNKNOWN.asException(); cancellableContext.cancel(e); assertTrue(cancellableContext.isCancelled()); Status status = statusFromCancelled(cancellableContext); assertNotNull(status); assertEquals(Status.Code.UNKNOWN, status.getCode()); assertSame(e, status.getCause()); }
@Test public void contextAlreadyCancelledNotifiesImmediately() throws Exception { // Attach the context which is recorded when the call is created Context.CancellableContext cancellableContext = Context.current().withCancellation(); Throwable cause = new Throwable(); cancellableContext.cancel(cause); Context previous = cancellableContext.attach(); ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( DESCRIPTOR, new SerializingExecutor(Executors.newSingleThreadExecutor()), CallOptions.DEFAULT, provider, deadlineCancellationExecutor) .setDecompressorRegistry(decompressorRegistry); previous.attach(); final SettableFuture<Status> statusFuture = SettableFuture.create(); call.start( new ClientCall.Listener<Void>() { @Override public void onClose(Status status, Metadata trailers) { statusFuture.set(status); } }, new Metadata()); // Caller should receive onClose callback. Status status = statusFuture.get(5, TimeUnit.SECONDS); assertEquals(Status.Code.CANCELLED, status.getCode()); assertSame(cause, status.getCause()); // Following operations should be no-op. call.request(1); call.sendMessage(null); call.halfClose(); // Stream should never be created. verifyZeroInteractions(transport); try { call.sendMessage(null); fail("Call has been cancelled"); } catch (IllegalStateException ise) { // expected } }
@Override public boolean isCancelled() { synchronized (this) { if (cancelled) { return true; } } // Detect cancellation of parent in the case where we have no listeners and // record it. if (super.isCancelled()) { cancel(super.cancellationCause()); return true; } return false; }
/** Create a cancellable context that has a deadline. */ private CancellableContext( Context parent, Deadline deadline, ScheduledExecutorService scheduler) { super(parent, deriveDeadline(parent, deadline), true); if (DEADLINE_KEY.get(this) == deadline) { final TimeoutException cause = new TimeoutException("context timed out"); if (!deadline.isExpired()) { // The parent deadline was after the new deadline so we need to install a listener // on the new earlier deadline to trigger expiration for this context. pendingDeadline = deadline.runOnExpiration( new Runnable() { @Override public void run() { cancel(cause); } }, scheduler); } else { // Cancel immediately if the deadline is already expired. cancel(cause); } } uncancellableSurrogate = new Context(this, EMPTY_ENTRIES); }