All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.ClientCallImpl Maven / Gradle / Ivy

There is a newer version: 1.65.1
Show newest version
/*
 * Copyright 2014, 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 io.grpc.ChannelImpl.TIMEOUT_KEY;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;

import io.grpc.MethodDescriptor.MethodType;
import io.grpc.internal.ClientStream;
import io.grpc.internal.ClientStreamListener;
import io.grpc.internal.ClientTransport;
import io.grpc.internal.HttpUtil;
import io.grpc.internal.SerializingExecutor;

import java.io.InputStream;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Implementation of {@link ClientCall}.
 */
final class ClientCallImpl extends ClientCall {
  private final MethodDescriptor method;
  private final SerializingExecutor callExecutor;
  private final boolean unaryRequest;
  private final CallOptions callOptions;
  private ClientStream stream;
  private volatile ScheduledFuture deadlineCancellationFuture;
  private boolean cancelCalled;
  private boolean halfCloseCalled;
  private ClientTransportProvider clientTransportProvider;
  private String userAgent;
  private ScheduledExecutorService deadlineCancellationExecutor;

  ClientCallImpl(MethodDescriptor method, SerializingExecutor executor,
      CallOptions callOptions, ClientTransportProvider clientTransportProvider,
      ScheduledExecutorService deadlineCancellationExecutor) {
    this.method = method;
    this.callExecutor = executor;
    this.unaryRequest = method.getType() == MethodType.UNARY
        || method.getType() == MethodType.SERVER_STREAMING;
    this.callOptions = callOptions;
    this.clientTransportProvider = clientTransportProvider;
    this.deadlineCancellationExecutor = deadlineCancellationExecutor;
  }

   /**
    * Provider of {@link ClientTransport}s.
    */
  interface ClientTransportProvider {
    /**
     * @return a client transport, or null if no more transports can be created.
     */
    ClientTransport get();
  }

  ClientCallImpl setUserAgent(String userAgent) {
    this.userAgent = userAgent;
    return this;
  }

  @Override
  public void start(Listener observer, Metadata.Headers headers) {
    Preconditions.checkState(stream == null, "Already started");
    Long deadlineNanoTime = callOptions.getDeadlineNanoTime();
    ClientStreamListener listener = new ClientStreamListenerImpl(observer, deadlineNanoTime);
    ClientTransport transport;
    try {
      transport = clientTransportProvider.get();
    } catch (RuntimeException ex) {
      closeCallPrematurely(listener, Status.fromThrowable(ex));
      return;
    }
    if (transport == null) {
      closeCallPrematurely(listener, Status.UNAVAILABLE.withDescription("Channel is shutdown"));
      return;
    }

    // Fill out timeout on the headers
    headers.removeAll(TIMEOUT_KEY);
    // Convert the deadline to timeout. Timeout is more favorable than deadline on the wire
    // because timeout tolerates the clock difference between machines.
    long timeoutMicros = 0;
    if (deadlineNanoTime != null) {
      timeoutMicros = TimeUnit.NANOSECONDS.toMicros(deadlineNanoTime - System.nanoTime());
      if (timeoutMicros <= 0) {
        closeCallPrematurely(listener, Status.DEADLINE_EXCEEDED);
        return;
      }
      headers.put(TIMEOUT_KEY, timeoutMicros);
    }

    // Fill out the User-Agent header.
    headers.removeAll(HttpUtil.USER_AGENT_KEY);
    if (userAgent != null) {
      headers.put(HttpUtil.USER_AGENT_KEY, userAgent);
    }

    try {
      stream = transport.newStream(method, headers, listener);
    } catch (IllegalStateException ex) {
      // We can race with the transport and end up trying to use a terminated transport.
      // TODO(ejona86): Improve the API to remove the possibility of the race.
      closeCallPrematurely(listener, Status.fromThrowable(ex));
    }
    // Start the deadline timer after stream creation because it will close the stream
    if (deadlineNanoTime != null) {
      deadlineCancellationFuture = startDeadlineTimer(timeoutMicros);
    }
  }

  @Override
  public void request(int numMessages) {
    Preconditions.checkState(stream != null, "Not started");
    stream.request(numMessages);
  }

  @Override
  public void cancel() {
    cancelCalled = true;
    // Cancel is called in exception handling cases, so it may be the case that the
    // stream was never successfully created.
    if (stream != null) {
      stream.cancel(Status.CANCELLED);
    }
  }

  @Override
  public void halfClose() {
    Preconditions.checkState(stream != null, "Not started");
    Preconditions.checkState(!cancelCalled, "call was cancelled");
    Preconditions.checkState(!halfCloseCalled, "call already half-closed");
    halfCloseCalled = true;
    stream.halfClose();
  }

  @Override
  public void sendMessage(ReqT message) {
    Preconditions.checkState(stream != null, "Not started");
    Preconditions.checkState(!cancelCalled, "call was cancelled");
    Preconditions.checkState(!halfCloseCalled, "call was half-closed");
    boolean failed = true;
    try {
      InputStream messageIs = method.streamRequest(message);
      stream.writeMessage(messageIs);
      failed = false;
    } finally {
      // TODO(notcarl): Find out if messageIs needs to be closed.
      if (failed) {
        cancel();
      }
    }
    // For unary requests, we don't flush since we know that halfClose should be coming soon. This
    // allows us to piggy-back the END_STREAM=true on the last message frame without opening the
    // possibility of broken applications forgetting to call halfClose without noticing.
    if (!unaryRequest) {
      stream.flush();
    }
  }

  @Override
  public boolean isReady() {
    return stream.isReady();
  }

  /**
   * Close the call before the stream is created.
   */
  private void closeCallPrematurely(ClientStreamListener listener, Status status) {
    Preconditions.checkState(stream == null, "Stream already created");
    stream = new NoopClientStream();
    listener.closed(status, new Metadata.Trailers());
  }

  private ScheduledFuture startDeadlineTimer(long timeoutMicros) {
    return deadlineCancellationExecutor.schedule(new Runnable() {
      @Override
      public void run() {
        stream.cancel(Status.DEADLINE_EXCEEDED);
      }
    }, timeoutMicros, TimeUnit.MICROSECONDS);
  }

  private class ClientStreamListenerImpl implements ClientStreamListener {
    private final Listener observer;
    private final Long deadlineNanoTime;
    private boolean closed;

    public ClientStreamListenerImpl(Listener observer, Long deadlineNanoTime) {
      Preconditions.checkNotNull(observer);
      this.observer = observer;
      this.deadlineNanoTime = deadlineNanoTime;
    }

    @Override
    public void headersRead(final Metadata.Headers headers) {
      callExecutor.execute(new Runnable() {
        @Override
        public void run() {
          try {
            if (closed) {
              return;
            }

            observer.onHeaders(headers);
          } catch (Throwable t) {
            cancel();
            throw Throwables.propagate(t);
          }
        }
      });
    }

    @Override
    public void messageRead(final InputStream message) {
      callExecutor.execute(new Runnable() {
        @Override
        public void run() {
          try {
            if (closed) {
              return;
            }

            try {
              observer.onMessage(method.parseResponse(message));
            } finally {
              message.close();
            }
          } catch (Throwable t) {
            cancel();
            throw Throwables.propagate(t);
          }
        }
      });
    }

    @Override
    public void closed(Status status, Metadata.Trailers trailers) {
      if (status.getCode() == Status.Code.CANCELLED && deadlineNanoTime != 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. This is always
        // safe, but we avoid wasting resources getting the nanoTime() when unnecessary.
        if (deadlineNanoTime <= System.nanoTime()) {
          status = Status.DEADLINE_EXCEEDED;
          // Replace trailers to prevent mixing sources of status and trailers.
          trailers = new Metadata.Trailers();
        }
      }
      final Status savedStatus = status;
      final Metadata.Trailers savedTrailers = trailers;
      callExecutor.execute(new Runnable() {
        @Override
        public void run() {
          closed = true;
          // manually optimize the volatile read
          ScheduledFuture future = deadlineCancellationFuture;
          if (future != null) {
            future.cancel(false);
          }
          observer.onClose(savedStatus, savedTrailers);
        }
      });
    }

    @Override
    public void onReady() {
      callExecutor.execute(new Runnable() {
        @Override
        public void run() {
          observer.onReady();
        }
      });
    }
  }

  static class NoopClientStream implements ClientStream {
    @Override public void writeMessage(InputStream message) {}

    @Override public void flush() {}

    @Override public void cancel(Status reason) {}

    @Override public void halfClose() {}

    @Override public void request(int numMessages) {}

    /**
     * Always returns {@code false}, since this is only used when the startup of the {@link
     * ClientCall} fails (i.e. the {@link ClientCall} is closed).
     */
    @Override public boolean isReady() {
      return false;
    }
  }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy