예제 #1
0
 @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);
           }
         }
       });
 }
예제 #2
0
 private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline deadline1) {
   if (deadline0 == null) {
     return deadline1;
   }
   if (deadline1 == null) {
     return deadline0;
   }
   return deadline0.minimum(deadline1);
 }
예제 #3
0
  /** 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);
  }
예제 #4
0
  @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());
  }
예제 #5
0
  @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());
  }
예제 #6
0
  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());
  }
예제 #7
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);
    }
  }