@Override public void closed(Status status, Metadata trailers) { Deadline deadline = context.getDeadline(); if (status.getCode() == Status.Code.CANCELLED && deadline != null) { // When the server's deadline expires, it can only reset the stream with CANCEL and no // description. Since our timer may be delayed in firing, we double-check the deadline and // turn the failure into the likely more helpful DEADLINE_EXCEEDED status. if (deadline.isExpired()) { status = DEADLINE_EXCEEDED; // Replace trailers to prevent mixing sources of status and trailers. trailers = new Metadata(); } } final Status savedStatus = status; final Metadata savedTrailers = trailers; callExecutor.execute( new ContextRunnable(context) { @Override public final void runInContext() { try { closed = true; contextListenerShouldBeRemoved = true; observer.onClose(savedStatus, savedTrailers); } finally { context.removeListener(ClientCallImpl.this); } } }); }
private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline deadline1) { if (deadline0 == null) { return deadline1; } if (deadline1 == null) { return deadline0; } return deadline0.minimum(deadline1); }
/** Based on the deadline, calculate and set the timeout to the given headers. */ private static void updateTimeoutHeaders( @Nullable Deadline effectiveDeadline, @Nullable Deadline callDeadline, @Nullable Deadline outerCallDeadline, Metadata headers) { headers.removeAll(TIMEOUT_KEY); if (effectiveDeadline == null) { return; } long effectiveTimeout = max(0, effectiveDeadline.timeRemaining(TimeUnit.NANOSECONDS)); headers.put(TIMEOUT_KEY, effectiveTimeout); logIfContextNarrowedTimeout( effectiveTimeout, effectiveDeadline, outerCallDeadline, callDeadline); }
@Test public void expiredDeadlineCancelsStream_CallOptions() { fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS); ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( DESCRIPTOR, MoreExecutors.directExecutor(), CallOptions.DEFAULT.withDeadline(Deadline.after(1000, TimeUnit.MILLISECONDS)), provider, deadlineCancellationExecutor); call.start(callListener, new Metadata()); fakeClock.forwardMillis(1001); verify(stream, times(1)).cancel(statusCaptor.capture()); assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode()); }
@Test public void streamCancelAbortsDeadlineTimer() { fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS); ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>( DESCRIPTOR, MoreExecutors.directExecutor(), CallOptions.DEFAULT.withDeadline(Deadline.after(1000, TimeUnit.MILLISECONDS)), provider, deadlineCancellationExecutor); call.start(callListener, new Metadata()); call.cancel("canceled", null); // Run the deadline timer, which should have been cancelled by the previous call to cancel() fakeClock.forwardMillis(1001); verify(stream, times(1)).cancel(statusCaptor.capture()); assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode()); }
private static void logIfContextNarrowedTimeout( long effectiveTimeout, Deadline effectiveDeadline, @Nullable Deadline outerCallDeadline, @Nullable Deadline callDeadline) { if (!log.isLoggable(Level.INFO) || outerCallDeadline != effectiveDeadline) { return; } StringBuilder builder = new StringBuilder(); builder.append( String.format("Call timeout set to '%d' ns, due to context deadline.", effectiveTimeout)); if (callDeadline == null) { builder.append(" Explicit call timeout was not set."); } else { long callTimeout = callDeadline.timeRemaining(TimeUnit.NANOSECONDS); builder.append(String.format(" Explicit call timeout was '%d' ns.", callTimeout)); } log.info(builder.toString()); }
@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); } }