@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 contextDeadlineShouldNotOverrideSmallerMetadataTimeout() {
    long deadlineNanos = TimeUnit.SECONDS.toNanos(2);
    Context context =
        Context.current()
            .withDeadlineAfter(deadlineNanos, TimeUnit.NANOSECONDS, deadlineCancellationExecutor);
    context.attach();

    CallOptions callOpts = CallOptions.DEFAULT.withDeadlineAfter(1, TimeUnit.SECONDS);
    ClientCallImpl<Void, Void> call =
        new ClientCallImpl<Void, Void>(
            DESCRIPTOR,
            MoreExecutors.directExecutor(),
            callOpts,
            provider,
            deadlineCancellationExecutor);

    Metadata headers = new Metadata();

    call.start(callListener, headers);

    assertTrue(headers.containsKey(GrpcUtil.TIMEOUT_KEY));
    Long timeout = headers.get(GrpcUtil.TIMEOUT_KEY);
    assertNotNull(timeout);

    long callOptsNanos = TimeUnit.SECONDS.toNanos(1);
    long deltaNanos = TimeUnit.MILLISECONDS.toNanos(400);
    assertTimeoutBetween(timeout, callOptsNanos - deltaNanos, callOptsNanos);
  }
  @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
    }
  }
Example #4
0
 @Override
 public void cancel(@Nullable String message, @Nullable Throwable cause) {
   if (cancelCalled) {
     return;
   }
   cancelCalled = true;
   try {
     // Cancel is called in exception handling cases, so it may be the case that the
     // stream was never successfully created.
     if (stream != null) {
       Status status = Status.CANCELLED;
       if (message != null) {
         status = status.withDescription(message);
       }
       if (cause != null) {
         status = status.withCause(cause);
       }
       if (message == null && cause == null) {
         // TODO(zhangkun83): log a warning with this exception once cancel() has been deleted from
         // ClientCall.
         status =
             status.withCause(
                 new CancellationException("Client called cancel() without any detail"));
       }
       stream.cancel(status);
     }
   } finally {
     if (context != null) {
       context.removeListener(ClientCallImpl.this);
     }
   }
 }
Example #5
0
 /** Is this context cancelled. */
 public boolean isCancelled() {
   if (parent == null || !cascadesCancellation) {
     return false;
   } else {
     return parent.isCancelled();
   }
 }
Example #6
0
 /**
  * Notify all listeners that this context has been cancelled and immediately release any reference
  * to them so that they may be garbage collected.
  */
 void notifyAndClearListeners() {
   if (!canBeCancelled) {
     return;
   }
   ArrayList<ExecutableListener> tmpListeners;
   synchronized (this) {
     if (listeners == null) {
       return;
     }
     tmpListeners = listeners;
     listeners = null;
   }
   // Deliver events to non-child context listeners before we notify child contexts. We do this
   // to cancel higher level units of work before child units. This allows for a better error
   // handling paradigm where the higher level unit of work knows it is cancelled and so can
   // ignore errors that bubble up as a result of cancellation of lower level units.
   for (int i = 0; i < tmpListeners.size(); i++) {
     if (!(tmpListeners.get(i).listener instanceof ParentListener)) {
       tmpListeners.get(i).deliver();
     }
   }
   for (int i = 0; i < tmpListeners.size(); i++) {
     if (tmpListeners.get(i).listener instanceof ParentListener) {
       tmpListeners.get(i).deliver();
     }
   }
   parent.removeListener(parentListener);
 }
Example #7
0
 /**
  * If a context {@link #isCancelled()} then return the cause of the cancellation or {@code null}
  * if context was cancelled without a cause. If the context is not yet cancelled will always
  * return {@code null}.
  *
  * <p>The cancellation cause is provided for informational purposes only and implementations
  * should generally assume that it has already been handled and logged properly.
  */
 @Nullable
 public Throwable cancellationCause() {
   if (parent == null || !cascadesCancellation) {
     return null;
   } else {
     return parent.cancellationCause();
   }
 }
Example #8
0
 @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());
 }
Example #9
0
 @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());
 }
Example #10
0
 @Override
 public void cancelled(Context context) {
   if (Context.this instanceof CancellableContext) {
     // Record cancellation with its cancellationCause.
     ((CancellableContext) Context.this).cancel(context.cancellationCause());
   } else {
     notifyAndClearListeners();
   }
 }
Example #11
0
 @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());
 }
Example #12
0
 @Test
 public void interceptCall_restoresIfNextThrows() {
   Context origContext = Context.current();
   try {
     interceptCall(
         uniqueContext,
         call,
         headers,
         new ServerCallHandler<Object, Object>() {
           @Override
           public ServerCall.Listener<Object> startCall(
               ServerCall<Object, Object> call, Metadata headers) {
             throw new RuntimeException();
           }
         });
     fail("Expected exception");
   } catch (RuntimeException expected) {
   }
   assertSame(origContext, Context.current());
 }
Example #13
0
 /** Lookup the value for a key in the context inheritance chain. */
 private Object lookup(Key<?> key) {
   for (int i = 0; i < keyValueEntries.length; i++) {
     if (key.equals(keyValueEntries[i][0])) {
       return keyValueEntries[i][1];
     }
   }
   if (parent == null) {
     return null;
   }
   return parent.lookup(key);
 }
Example #14
0
 /**
  * Detach the current context from the thread and attach the provided replacement. If this context
  * is not {@link #current()} a SEVERE message will be logged but the context to attach will still
  * be bound.
  */
 public void detach(Context toAttach) {
   Preconditions.checkNotNull(toAttach);
   if (toAttach.attach() != this) {
     // Log a severe message instead of throwing an exception as the context to attach is assumed
     // to be the correct one and the unbalanced state represents a coding mistake in a lower
     // layer in the stack that cannot be recovered from here.
     LOG.log(
         Level.SEVERE,
         "Context was not attached when detaching",
         new Throwable().fillInStackTrace());
   }
 }
Example #15
0
  /** 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());
  }
Example #16
0
  @Test
  public void statusFromCancelled_TimeoutExceptionShouldMapToDeadlineExceeded() {
    FakeClock fakeClock = new FakeClock();
    Context.CancellableContext cancellableContext =
        Context.current()
            .withDeadlineAfter(100, TimeUnit.MILLISECONDS, fakeClock.scheduledExecutorService);
    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);
    fakeClock.forwardMillis(100);

    assertTrue(cancellableContext.isCancelled());
    assertThat(cancellableContext.cancellationCause(), instanceOf(TimeoutException.class));

    Status status = statusFromCancelled(cancellableContext);
    assertNotNull(status);
    assertEquals(Status.Code.DEADLINE_EXCEEDED, status.getCode());
    assertEquals("context timed out", status.getDescription());
  }
Example #17
0
 /** Remove a {@link CancellationListener}. */
 public void removeListener(CancellationListener cancellationListener) {
   if (!canBeCancelled) {
     return;
   }
   synchronized (this) {
     if (listeners != null) {
       for (int i = listeners.size() - 1; i >= 0; i--) {
         if (listeners.get(i).listener == cancellationListener) {
           listeners.remove(i);
           // Just remove the first matching listener, given that we allow duplicate
           // adds we should allow for duplicates after remove.
           break;
         }
       }
       // We have no listeners so no need to listen to our parent
       if (listeners.isEmpty()) {
         parent.removeListener(parentListener);
         listeners = null;
       }
     }
   }
 }
Example #18
0
 ClientCallImpl(
     MethodDescriptor<ReqT, RespT> method,
     Executor executor,
     CallOptions callOptions,
     ClientTransportProvider clientTransportProvider,
     ScheduledExecutorService deadlineCancellationExecutor) {
   this.method = method;
   // If we know that the executor is a direct executor, we don't need to wrap it with a
   // SerializingExecutor. This is purely for performance reasons.
   // See https://github.com/grpc/grpc-java/issues/368
   this.callExecutor =
       executor == directExecutor()
           ? new SerializeReentrantCallsDirectExecutor()
           : new SerializingExecutor(executor);
   // Propagate the context from the thread which initiated the call to all callbacks.
   this.parentContext = Context.current();
   this.unaryRequest =
       method.getType() == MethodType.UNARY || method.getType() == MethodType.SERVER_STREAMING;
   this.callOptions = callOptions;
   this.clientTransportProvider = clientTransportProvider;
   this.deadlineCancellationExecutor = deadlineCancellationExecutor;
 }
  @Test
  public void expiredDeadlineCancelsStream_Context() {
    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);

    Context.current()
        .withDeadlineAfter(1000, TimeUnit.MILLISECONDS, deadlineCancellationExecutor)
        .attach();

    ClientCallImpl<Void, Void> call =
        new ClientCallImpl<Void, Void>(
            DESCRIPTOR,
            MoreExecutors.directExecutor(),
            CallOptions.DEFAULT,
            provider,
            deadlineCancellationExecutor);

    call.start(callListener, new Metadata());

    fakeClock.forwardMillis(TimeUnit.SECONDS.toMillis(1001));

    verify(stream, times(1)).cancel(statusCaptor.capture());
    assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode());
  }
Example #20
0
 /** Add a listener that will be notified when the context becomes cancelled. */
 public void addListener(
     final CancellationListener cancellationListener, final Executor executor) {
   Preconditions.checkNotNull(cancellationListener);
   Preconditions.checkNotNull(executor);
   if (canBeCancelled) {
     ExecutableListener executableListener =
         new ExecutableListener(executor, cancellationListener);
     synchronized (this) {
       if (isCancelled()) {
         executableListener.deliver();
       } else {
         if (listeners == null) {
           // Now that we have a listener we need to listen to our parent so
           // we can cascade listener notification.
           listeners = new ArrayList<ExecutableListener>();
           listeners.add(executableListener);
           parent.addListener(parentListener, MoreExecutors.directExecutor());
         } else {
           listeners.add(executableListener);
         }
       }
     }
   }
 }
  @Test
  public void callerContextPropagatedToListener() throws Exception {
    // Attach the context which is recorded when the call is created
    final Context.Key<String> testKey = Context.key("testing");
    Context.current().withValue(testKey, "testValue").attach();

    ClientCallImpl<Void, Void> call =
        new ClientCallImpl<Void, Void>(
                DESCRIPTOR,
                new SerializingExecutor(Executors.newSingleThreadExecutor()),
                CallOptions.DEFAULT,
                provider,
                deadlineCancellationExecutor)
            .setDecompressorRegistry(decompressorRegistry);

    Context.ROOT.attach();

    // Override the value after creating the call, this should not be seen by callbacks
    Context.current().withValue(testKey, "badValue").attach();

    final AtomicBoolean onHeadersCalled = new AtomicBoolean();
    final AtomicBoolean onMessageCalled = new AtomicBoolean();
    final AtomicBoolean onReadyCalled = new AtomicBoolean();
    final AtomicBoolean observedIncorrectContext = new AtomicBoolean();
    final CountDownLatch latch = new CountDownLatch(1);

    call.start(
        new ClientCall.Listener<Void>() {
          @Override
          public void onHeaders(Metadata headers) {
            onHeadersCalled.set(true);
            checkContext();
          }

          @Override
          public void onMessage(Void message) {
            onMessageCalled.set(true);
            checkContext();
          }

          @Override
          public void onClose(Status status, Metadata trailers) {
            checkContext();
            latch.countDown();
          }

          @Override
          public void onReady() {
            onReadyCalled.set(true);
            checkContext();
          }

          private void checkContext() {
            if (!"testValue".equals(testKey.get())) {
              observedIncorrectContext.set(true);
            }
          }
        },
        new Metadata());

    verify(stream).start(listenerArgumentCaptor.capture());
    ClientStreamListener listener = listenerArgumentCaptor.getValue();
    listener.onReady();
    listener.headersRead(new Metadata());
    listener.messageRead(new ByteArrayInputStream(new byte[0]));
    listener.messageRead(new ByteArrayInputStream(new byte[0]));
    listener.closed(Status.OK, new Metadata());

    assertTrue(latch.await(5, TimeUnit.SECONDS));

    assertTrue(onHeadersCalled.get());
    assertTrue(onMessageCalled.get());
    assertTrue(onReadyCalled.get());
    assertFalse(observedIncorrectContext.get());
  }
Example #22
0
 /** Get the value from the specified context for this key. */
 @SuppressWarnings("unchecked")
 public T get(Context context) {
   T value = (T) context.lookup(this);
   return value == null ? defaultValue : value;
 }
Example #23
0
 /** Get the value from the {@link #current()} context for this key. */
 @SuppressWarnings("unchecked")
 public T get() {
   return get(Context.current());
 }
Example #24
0
 @Override
 public boolean isCurrent() {
   return uncancellableSurrogate.isCurrent();
 }
Example #25
0
 @Override
 public void detach(Context toAttach) {
   uncancellableSurrogate.detach(toAttach);
 }
Example #26
0
 @Override
 public Context attach() {
   return uncancellableSurrogate.attach();
 }
Example #27
0
  @Override
  public void start(final Listener<RespT> observer, Metadata headers) {
    checkState(stream == null, "Already started");
    checkNotNull(observer, "observer");
    checkNotNull(headers, "headers");

    // Create the context
    final Deadline effectiveDeadline = min(callOptions.getDeadline(), parentContext.getDeadline());
    if (effectiveDeadline != parentContext.getDeadline()) {
      context = parentContext.withDeadline(effectiveDeadline, deadlineCancellationExecutor);
    } else {
      context = parentContext.withCancellation();
    }

    if (context.isCancelled()) {
      // Context is already cancelled so no need to create a real stream, just notify the observer
      // of cancellation via callback on the executor
      stream = NoopClientStream.INSTANCE;
      callExecutor.execute(
          new ContextRunnable(context) {
            @Override
            public void runInContext() {
              observer.onClose(statusFromCancelled(context), new Metadata());
            }
          });
      return;
    }
    final String compressorName = callOptions.getCompressor();
    Compressor compressor = null;
    if (compressorName != null) {
      compressor = compressorRegistry.lookupCompressor(compressorName);
      if (compressor == null) {
        stream = NoopClientStream.INSTANCE;
        callExecutor.execute(
            new ContextRunnable(context) {
              @Override
              public void runInContext() {
                observer.onClose(
                    Status.INTERNAL.withDescription(
                        String.format("Unable to find compressor by name %s", compressorName)),
                    new Metadata());
              }
            });
        return;
      }
    } else {
      compressor = Codec.Identity.NONE;
    }

    prepareHeaders(headers, callOptions, userAgent, decompressorRegistry, compressor);

    final boolean deadlineExceeded = effectiveDeadline != null && effectiveDeadline.isExpired();
    if (!deadlineExceeded) {
      updateTimeoutHeaders(
          effectiveDeadline, callOptions.getDeadline(), parentContext.getDeadline(), headers);
      ClientTransport transport = clientTransportProvider.get(callOptions);
      stream = transport.newStream(method, headers);
    } else {
      stream = new FailingClientStream(DEADLINE_EXCEEDED);
    }

    if (callOptions.getAuthority() != null) {
      stream.setAuthority(callOptions.getAuthority());
    }
    stream.setCompressor(compressor);

    stream.start(new ClientStreamListenerImpl(observer));
    if (compressor != Codec.Identity.NONE) {
      stream.setMessageCompression(true);
    }

    // Delay any sources of cancellation after start(), because most of the transports are broken if
    // they receive cancel before start. Issue #1343 has more details

    // Propagate later Context cancellation to the remote side.
    context.addListener(this, directExecutor());
    if (contextListenerShouldBeRemoved) {
      // Race detected! ClientStreamListener.closed may have been called before
      // deadlineCancellationFuture was set, thereby preventing the future from being cancelled.
      // Go ahead and cancel again, just to be sure it was cancelled.
      context.removeListener(this);
    }
  }
Example #28
0
 @Override
 public void execute(Runnable r) {
   e.execute(Context.current().wrap(r));
 }
Example #29
0
  @Test
  public void interceptCall_basic() {
    Context origContext = Context.current();
    final Object message = new Object();
    final List<Integer> methodCalls = new ArrayList<Integer>();
    final ServerCall.Listener<Object> listener =
        new ServerCall.Listener<Object>() {
          @Override
          public void onMessage(Object messageIn) {
            assertSame(message, messageIn);
            assertSame(uniqueContext, Context.current());
            methodCalls.add(1);
          }

          @Override
          public void onHalfClose() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(2);
          }

          @Override
          public void onCancel() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(3);
          }

          @Override
          public void onComplete() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(4);
          }

          @Override
          public void onReady() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(5);
          }
        };
    ServerCall.Listener<Object> wrapped =
        interceptCall(
            uniqueContext,
            call,
            headers,
            new ServerCallHandler<Object, Object>() {
              @Override
              public ServerCall.Listener<Object> startCall(
                  ServerCall<Object, Object> call, Metadata headers) {
                assertSame(ContextsTest.this.method, method);
                assertSame(ContextsTest.this.call, call);
                assertSame(ContextsTest.this.headers, headers);
                assertSame(uniqueContext, Context.current());
                return listener;
              }
            });
    assertSame(origContext, Context.current());

    wrapped.onMessage(message);
    wrapped.onHalfClose();
    wrapped.onCancel();
    wrapped.onComplete();
    wrapped.onReady();
    assertEquals(Arrays.asList(1, 2, 3, 4, 5), methodCalls);
    assertSame(origContext, Context.current());
  }
Example #30
0
/** Tests for {@link Contexts}. */
@RunWith(JUnit4.class)
public class ContextsTest {
  private static Context.Key<Object> contextKey = Context.key("key");
  /** For use in comparing context by reference. */
  private Context uniqueContext = Context.ROOT.withValue(contextKey, new Object());

  @SuppressWarnings("unchecked")
  private MethodDescriptor<Object, Object> method = mock(MethodDescriptor.class);

  @SuppressWarnings("unchecked")
  private ServerCall<Object, Object> call = mock(ServerCall.class);

  private Metadata headers = new Metadata();

  @Test
  public void interceptCall_basic() {
    Context origContext = Context.current();
    final Object message = new Object();
    final List<Integer> methodCalls = new ArrayList<Integer>();
    final ServerCall.Listener<Object> listener =
        new ServerCall.Listener<Object>() {
          @Override
          public void onMessage(Object messageIn) {
            assertSame(message, messageIn);
            assertSame(uniqueContext, Context.current());
            methodCalls.add(1);
          }

          @Override
          public void onHalfClose() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(2);
          }

          @Override
          public void onCancel() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(3);
          }

          @Override
          public void onComplete() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(4);
          }

          @Override
          public void onReady() {
            assertSame(uniqueContext, Context.current());
            methodCalls.add(5);
          }
        };
    ServerCall.Listener<Object> wrapped =
        interceptCall(
            uniqueContext,
            call,
            headers,
            new ServerCallHandler<Object, Object>() {
              @Override
              public ServerCall.Listener<Object> startCall(
                  ServerCall<Object, Object> call, Metadata headers) {
                assertSame(ContextsTest.this.method, method);
                assertSame(ContextsTest.this.call, call);
                assertSame(ContextsTest.this.headers, headers);
                assertSame(uniqueContext, Context.current());
                return listener;
              }
            });
    assertSame(origContext, Context.current());

    wrapped.onMessage(message);
    wrapped.onHalfClose();
    wrapped.onCancel();
    wrapped.onComplete();
    wrapped.onReady();
    assertEquals(Arrays.asList(1, 2, 3, 4, 5), methodCalls);
    assertSame(origContext, Context.current());
  }

  @Test
  public void interceptCall_restoresIfNextThrows() {
    Context origContext = Context.current();
    try {
      interceptCall(
          uniqueContext,
          call,
          headers,
          new ServerCallHandler<Object, Object>() {
            @Override
            public ServerCall.Listener<Object> startCall(
                ServerCall<Object, Object> call, Metadata headers) {
              throw new RuntimeException();
            }
          });
      fail("Expected exception");
    } catch (RuntimeException expected) {
    }
    assertSame(origContext, Context.current());
  }

  @Test
  public void interceptCall_restoresIfListenerThrows() {
    Context origContext = Context.current();
    final ServerCall.Listener<Object> listener =
        new ServerCall.Listener<Object>() {
          @Override
          public void onMessage(Object messageIn) {
            throw new RuntimeException();
          }

          @Override
          public void onHalfClose() {
            throw new RuntimeException();
          }

          @Override
          public void onCancel() {
            throw new RuntimeException();
          }

          @Override
          public void onComplete() {
            throw new RuntimeException();
          }

          @Override
          public void onReady() {
            throw new RuntimeException();
          }
        };
    ServerCall.Listener<Object> wrapped =
        interceptCall(
            uniqueContext,
            call,
            headers,
            new ServerCallHandler<Object, Object>() {
              @Override
              public ServerCall.Listener<Object> startCall(
                  ServerCall<Object, Object> call, Metadata headers) {
                return listener;
              }
            });

    try {
      wrapped.onMessage(new Object());
      fail("Exception expected");
    } catch (RuntimeException expected) {
    }
    try {
      wrapped.onHalfClose();
      fail("Exception expected");
    } catch (RuntimeException expected) {
    }
    try {
      wrapped.onCancel();
      fail("Exception expected");
    } catch (RuntimeException expected) {
    }
    try {
      wrapped.onComplete();
      fail("Exception expected");
    } catch (RuntimeException expected) {
    }
    try {
      wrapped.onReady();
      fail("Exception expected");
    } catch (RuntimeException expected) {
    }
    assertSame(origContext, Context.current());
  }

  @Test
  public void statusFromCancelled_returnNullIfCtxNotCancelled() {
    Context context = Context.current();
    assertFalse(context.isCancelled());
    assertNull(statusFromCancelled(context));
  }

  @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());
  }

  @Test
  public void statusFromCancelled_TimeoutExceptionShouldMapToDeadlineExceeded() {
    FakeClock fakeClock = new FakeClock();
    Context.CancellableContext cancellableContext =
        Context.current()
            .withDeadlineAfter(100, TimeUnit.MILLISECONDS, fakeClock.scheduledExecutorService);
    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);
    fakeClock.forwardMillis(100);

    assertTrue(cancellableContext.isCancelled());
    assertThat(cancellableContext.cancellationCause(), instanceOf(TimeoutException.class));

    Status status = statusFromCancelled(cancellableContext);
    assertNotNull(status);
    assertEquals(Status.Code.DEADLINE_EXCEEDED, status.getCode());
    assertEquals("context timed out", status.getDescription());
  }

  @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());
  }

  /** 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 statusFromCancelled_shouldThrowIfCtxIsNull() {
    try {
      statusFromCancelled(null);
      fail("NPE expected");
    } catch (NullPointerException npe) {
      assertEquals("context must not be null", npe.getMessage());
    }
  }
}