/** * Defines the status of an operation by providing a standard {@link Code} in conjunction with an * optional descriptive message. Instances of {@code Status} are created by starting with the * template for the appropriate {@link Status.Code} and supplementing it with additional * information: {@code Status.NOT_FOUND.withDescription("Could not find 'important_file.txt'");} * * <p>For clients, every remote call will return a status on completion. In the case of errors this * status may be propagated to blocking stubs as a {@link RuntimeException} or to a listener as an * explicit parameter. * * <p>Similarly servers can report a status by throwing {@link StatusRuntimeException} or by passing * the status to a callback. * * <p>Utility functions are provided to convert a status to an exception and to extract them back * out. */ @Immutable public final class Status { /** * The set of canonical status codes. If new codes are added over time they must choose a * numerical value that does not collide with any previously used value. */ public enum Code { /** The operation completed successfully. */ OK(0), /** The operation was cancelled (typically by the caller). */ CANCELLED(1), /** * Unknown error. An example of where this error may be returned is if a Status value received * from another address space belongs to an error-space that is not known in this address space. * Also errors raised by APIs that do not return enough error information may be converted to * this error. */ UNKNOWN(2), /** * Client specified an invalid argument. Note that this differs from FAILED_PRECONDITION. * INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the * system (e.g., a malformed file name). */ INVALID_ARGUMENT(3), /** * Deadline expired before operation could complete. For operations that change the state of the * system, this error may be returned even if the operation has completed successfully. For * example, a successful response from a server could have been delayed long enough for the * deadline to expire. */ DEADLINE_EXCEEDED(4), /** Some requested entity (e.g., file or directory) was not found. */ NOT_FOUND(5), /** Some entity that we attempted to create (e.g., file or directory) already exists. */ ALREADY_EXISTS(6), /** * The caller does not have permission to execute the specified operation. PERMISSION_DENIED * must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED * instead for those errors). PERMISSION_DENIED must not be used if the caller cannot be * identified (use UNAUTHENTICATED instead for those errors). */ PERMISSION_DENIED(7), /** * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system * is out of space. */ RESOURCE_EXHAUSTED(8), /** * Operation was rejected because the system is not in a state required for the operation's * execution. For example, directory to be deleted may be non-empty, an rmdir operation is * applied to a non-directory, etc. * * <p>A litmus test that may help a service implementor in deciding between FAILED_PRECONDITION, * ABORTED, and UNAVAILABLE: (a) Use UNAVAILABLE if the client can retry just the failing call. * (b) Use ABORTED if the client should retry at a higher-level (e.g., restarting a * read-modify-write sequence). (c) Use FAILED_PRECONDITION if the client should not retry until * the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory * is non-empty, FAILED_PRECONDITION should be returned since the client should not retry unless * they have first fixed up the directory by deleting files from it. */ FAILED_PRECONDITION(9), /** * The operation was aborted, typically due to a concurrency issue like sequencer check * failures, transaction aborts, etc. * * <p>See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE. */ ABORTED(10), /** * Operation was attempted past the valid range. E.g., seeking or reading past end of file. * * <p>Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system * state changes. For example, a 32-bit file system will generate INVALID_ARGUMENT if asked to * read at an offset that is not in the range [0,2^32-1], but it will generate OUT_OF_RANGE if * asked to read from an offset past the current file size. * * <p>There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE. We recommend * using OUT_OF_RANGE (the more specific error) when it applies so that callers who are * iterating through a space can easily look for an OUT_OF_RANGE error to detect when they are * done. */ OUT_OF_RANGE(11), /** Operation is not implemented or not supported/enabled in this service. */ UNIMPLEMENTED(12), /** * Internal errors. Means some invariants expected by underlying system has been broken. If you * see one of these errors, something is very broken. */ INTERNAL(13), /** * The service is currently unavailable. This is a most likely a transient condition and may be * corrected by retrying with a backoff. * * <p>See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE. */ UNAVAILABLE(14), /** Unrecoverable data loss or corruption. */ DATA_LOSS(15), /** The request does not have valid authentication credentials for the operation. */ UNAUTHENTICATED(16); private final int value; private final String valueAscii; private Code(int value) { this.value = value; this.valueAscii = Integer.toString(value); } /** The numerical value of the code. */ public int value() { return value; } private Status status() { return STATUS_LIST.get(value); } private String valueAscii() { return valueAscii; } } // Create the canonical list of Status instances indexed by their code values. private static final List<Status> STATUS_LIST; static { TreeMap<Integer, Status> canonicalizer = new TreeMap<Integer, Status>(); for (Code code : Code.values()) { Status replaced = canonicalizer.put(code.value(), new Status(code)); if (replaced != null) { throw new IllegalStateException( "Code value duplication between " + replaced.getCode().name() + " & " + code.name()); } } STATUS_LIST = new ArrayList<Status>(canonicalizer.values()); } // A pseudo-enum of Status instances mapped 1:1 with values in Code. This simplifies construction // patterns for derived instances of Status. /** The operation completed successfully. */ public static final Status OK = Code.OK.status(); /** The operation was cancelled (typically by the caller). */ public static final Status CANCELLED = Code.CANCELLED.status(); /** Unknown error. See {@link Code#UNKNOWN}. */ public static final Status UNKNOWN = Code.UNKNOWN.status(); /** Client specified an invalid argument. See {@link Code#INVALID_ARGUMENT}. */ public static final Status INVALID_ARGUMENT = Code.INVALID_ARGUMENT.status(); /** Deadline expired before operation could complete. See {@link Code#DEADLINE_EXCEEDED}. */ public static final Status DEADLINE_EXCEEDED = Code.DEADLINE_EXCEEDED.status(); /** Some requested entity (e.g., file or directory) was not found. */ public static final Status NOT_FOUND = Code.NOT_FOUND.status(); /** Some entity that we attempted to create (e.g., file or directory) already exists. */ public static final Status ALREADY_EXISTS = Code.ALREADY_EXISTS.status(); /** * The caller does not have permission to execute the specified operation. See {@link * Code#PERMISSION_DENIED}. */ public static final Status PERMISSION_DENIED = Code.PERMISSION_DENIED.status(); /** The request does not have valid authentication credentials for the operation. */ public static final Status UNAUTHENTICATED = Code.UNAUTHENTICATED.status(); /** * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system * is out of space. */ public static final Status RESOURCE_EXHAUSTED = Code.RESOURCE_EXHAUSTED.status(); /** * Operation was rejected because the system is not in a state required for the operation's * execution. See {@link Code#FAILED_PRECONDITION}. */ public static final Status FAILED_PRECONDITION = Code.FAILED_PRECONDITION.status(); /** * The operation was aborted, typically due to a concurrency issue like sequencer check failures, * transaction aborts, etc. See {@link Code#ABORTED}. */ public static final Status ABORTED = Code.ABORTED.status(); /** Operation was attempted past the valid range. See {@link Code#OUT_OF_RANGE}. */ public static final Status OUT_OF_RANGE = Code.OUT_OF_RANGE.status(); /** Operation is not implemented or not supported/enabled in this service. */ public static final Status UNIMPLEMENTED = Code.UNIMPLEMENTED.status(); /** Internal errors. See {@link Code#INTERNAL}. */ public static final Status INTERNAL = Code.INTERNAL.status(); /** The service is currently unavailable. See {@link Code#UNAVAILABLE}. */ public static final Status UNAVAILABLE = Code.UNAVAILABLE.status(); /** Unrecoverable data loss or corruption. */ public static final Status DATA_LOSS = Code.DATA_LOSS.status(); /** Return a {@link Status} given a canonical error {@link Code} value. */ public static Status fromCodeValue(int codeValue) { if (codeValue < 0 || codeValue > STATUS_LIST.size()) { return UNKNOWN.withDescription("Unknown code " + codeValue); } else { return STATUS_LIST.get(codeValue); } } /** Key to bind status code to trailing metadata. */ @Internal public static final Metadata.Key<Status> CODE_KEY = Metadata.Key.of("grpc-status", new StatusCodeMarshaller()); /** Key to bind status message to trailing metadata. */ @Internal public static final Metadata.Key<String> MESSAGE_KEY = Metadata.Key.of("grpc-message", Metadata.ASCII_STRING_MARSHALLER); /** Extract an error {@link Status} from the causal chain of a {@link Throwable}. */ public static Status fromThrowable(Throwable t) { for (Throwable cause : Throwables.getCausalChain(t)) { if (cause instanceof StatusException) { return ((StatusException) cause).getStatus(); } else if (cause instanceof StatusRuntimeException) { return ((StatusRuntimeException) cause).getStatus(); } } // Couldn't find a cause with a Status return UNKNOWN.withCause(t); } static String formatThrowableMessage(Status status) { if (status.description == null) { return status.code.toString(); } else { return status.code + ": " + status.description; } } private final Code code; private final String description; private final Throwable cause; private Status(Code code) { this(code, null, null); } private Status(Code code, @Nullable String description, @Nullable Throwable cause) { this.code = Preconditions.checkNotNull(code); this.description = description; this.cause = cause; } /** Create a derived instance of {@link Status} with the given cause. */ public Status withCause(Throwable cause) { if (Objects.equal(this.cause, cause)) { return this; } return new Status(this.code, this.description, cause); } /** Create a derived instance of {@link Status} with the given description. */ public Status withDescription(String description) { if (Objects.equal(this.description, description)) { return this; } return new Status(this.code, description, this.cause); } /** * Create a derived instance of {@link Status} augmenting the current description with additional * detail. */ public Status augmentDescription(String additionalDetail) { if (additionalDetail == null) { return this; } else if (this.description == null) { return new Status(this.code, additionalDetail, this.cause); } else { return new Status(this.code, this.description + "\n" + additionalDetail, this.cause); } } /** The canonical status code. */ public Code getCode() { return code; } /** A description of this status for human consumption. */ @Nullable public String getDescription() { return description; } /** The underlying cause of an error. */ @Nullable public Throwable getCause() { return cause; } /** Is this status OK, i.e., not an error. */ public boolean isOk() { return Code.OK == code; } /** * Convert this {@link Status} to a {@link RuntimeException}. Use {@link #fromThrowable} to * recover this {@link Status} instance when the returned exception is in the causal chain. */ public StatusRuntimeException asRuntimeException() { return new StatusRuntimeException(this); } /** * Convert this {@link Status} to an {@link Exception}. Use {@link #fromThrowable} to recover this * {@link Status} instance when the returned exception is in the causal chain. */ public StatusException asException() { return new StatusException(this); } /** A string representation of the status useful for debugging. */ @Override public String toString() { return MoreObjects.toStringHelper(this) .add("code", code.name()) .add("description", description) .add("cause", cause) .toString(); } private static class StatusCodeMarshaller implements Metadata.AsciiMarshaller<Status> { @Override public String toAsciiString(Status status) { return status.getCode().valueAscii(); } @Override public Status parseAsciiString(String serialized) { return fromCodeValue(Integer.valueOf(serialized)); } } /** * Equality on Statuses is not well defined. Instead, do comparison based on their Code with * {@link #getCode}. The description and cause of the Status are unlikely to be stable, and * additional fields may be added to Status in the future. */ @Override public boolean equals(Object obj) { return super.equals(obj); } /** * Hash codes on Statuses are not well defined. * * @see #equals */ @Override public int hashCode() { return super.hashCode(); } }
/** A communication channel for making outgoing RPCs. */ @ThreadSafe public final class ChannelImpl extends Channel { private static final Logger log = Logger.getLogger(ChannelImpl.class.getName()); private final ClientTransportFactory transportFactory; private final ExecutorService executor; private final String userAgent; private final Object lock = new Object(); /** Executor that runs deadline timers for requests. */ private ScheduledExecutorService scheduledExecutor; // TODO(carl-mastrangelo): Allow clients to pass this in private final BackoffPolicy.Provider backoffPolicyProvider = new ExponentialBackoffPolicy.Provider(); /** * We delegate to this channel, so that we can have interceptors as necessary. If there aren't any * interceptors this will just be {@link RealChannel}. */ private final Channel interceptorChannel; /** * All transports that are not stopped. At the very least {@link #activeTransport} will be * present, but previously used transports that still have streams or are stopping may also be * present. */ @GuardedBy("lock") private Collection<ClientTransport> transports = new ArrayList<ClientTransport>(); /** * The transport for new outgoing requests. 'this' lock must be held when assigning to * activeTransport. */ private volatile ClientTransport activeTransport; @GuardedBy("lock") private boolean shutdown; @GuardedBy("lock") private boolean terminated; private Runnable terminationRunnable; private long reconnectTimeMillis; private BackoffPolicy reconnectPolicy; private final ClientTransportProvider transportProvider = new ClientTransportProvider() { @Override public ClientTransport get() { return obtainActiveTransport(); } }; ChannelImpl( ClientTransportFactory transportFactory, ExecutorService executor, @Nullable String userAgent, List<ClientInterceptor> interceptors) { this.transportFactory = transportFactory; this.executor = executor; this.userAgent = userAgent; this.interceptorChannel = ClientInterceptors.intercept(new RealChannel(), interceptors); scheduledExecutor = SharedResourceHolder.get(TIMER_SERVICE); } /** Hack to allow executors to auto-shutdown. Not for general use. */ // TODO(ejona86): Replace with a real API. void setTerminationRunnable(Runnable runnable) { this.terminationRunnable = runnable; } /** * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately * cancelled. */ public ChannelImpl shutdown() { ClientTransport savedActiveTransport; synchronized (lock) { if (shutdown) { return this; } shutdown = true; // After shutdown there are no new calls, so no new cancellation tasks are needed scheduledExecutor = SharedResourceHolder.release(TIMER_SERVICE, scheduledExecutor); savedActiveTransport = activeTransport; if (savedActiveTransport != null) { activeTransport = null; } else if (transports.isEmpty()) { terminated = true; lock.notifyAll(); if (terminationRunnable != null) { terminationRunnable.run(); } } } if (savedActiveTransport != null) { savedActiveTransport.shutdown(); } return this; } /** * Initiates a forceful shutdown in which preexisting and new calls are cancelled. Although * forceful, the shutdown process is still not instantaneous; {@link #isTerminated()} will likely * return {@code false} immediately after this method returns. * * <p>NOT YET IMPLEMENTED. This method currently behaves identically to shutdown(). */ // TODO(ejona86): cancel preexisting calls. public ChannelImpl shutdownNow() { shutdown(); return this; } /** * Returns whether the channel is shutdown. Shutdown channels immediately cancel any new calls, * but may still have some calls being processed. * * @see #shutdown() * @see #isTerminated() */ public boolean isShutdown() { synchronized (lock) { return shutdown; } } /** * Waits for the channel to become terminated, giving up if the timeout is reached. * * @return whether the channel is terminated, as would be done by {@link #isTerminated()}. */ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { synchronized (lock) { long timeoutNanos = unit.toNanos(timeout); long endTimeNanos = System.nanoTime() + timeoutNanos; while (!terminated && (timeoutNanos = endTimeNanos - System.nanoTime()) > 0) { TimeUnit.NANOSECONDS.timedWait(lock, timeoutNanos); } return terminated; } } /** * Returns whether the channel is terminated. Terminated channels have no running calls and * relevant resources released (like TCP connections). * * @see #isShutdown() */ public boolean isTerminated() { synchronized (lock) { return terminated; } } /** * Pings the remote endpoint to verify that the transport is still active. When an acknowledgement * is received, the given callback will be invoked using the given executor. * * <p>If the underlying transport has no mechanism by when to send a ping, this method may throw * an {@link UnsupportedOperationException}. The operation may {@linkplain * PingCallback#pingFailed(Throwable) fail} due to transient transport errors. In that case, * trying again may succeed. * * @see ClientTransport#ping(PingCallback, Executor) */ public void ping(final PingCallback callback, final Executor executor) { try { obtainActiveTransport().ping(callback, executor); } catch (final RuntimeException ex) { executor.execute( new Runnable() { @Override public void run() { callback.pingFailed(ex); } }); } } /* * Creates a new outgoing call on the channel. */ @Override public <ReqT, RespT> ClientCall<ReqT, RespT> newCall( MethodDescriptor<ReqT, RespT> method, CallOptions callOptions) { return interceptorChannel.newCall(method, callOptions); } private ClientTransport obtainActiveTransport() { ClientTransport savedActiveTransport = activeTransport; // If we know there is an active transport and we are not in backoff mode, return quickly. if (savedActiveTransport != null && !(savedActiveTransport instanceof InactiveTransport)) { return savedActiveTransport; } synchronized (lock) { if (shutdown) { return null; } savedActiveTransport = activeTransport; if (savedActiveTransport instanceof InactiveTransport) { if (System.nanoTime() > TimeUnit.MILLISECONDS.toNanos(reconnectTimeMillis)) { // The timeout expired, clear the inactive transport and update the shutdown status to // something that is retryable. activeTransport = null; savedActiveTransport = activeTransport; } else { // We are still in backoff mode, just return the inactive transport. return savedActiveTransport; } } if (savedActiveTransport != null) { return savedActiveTransport; } // There is no active transport, or we just finished backoff. Create a new transport. ClientTransport newActiveTransport = transportFactory.newClientTransport(); transports.add(newActiveTransport); boolean failed = true; try { newActiveTransport.start(new TransportListener(newActiveTransport)); failed = false; } finally { if (failed) { transports.remove(newActiveTransport); } } // It's possible that start() called transportShutdown() and transportTerminated(). If so, we // wouldn't want to make it the active transport. if (transports.contains(newActiveTransport)) { // start() must return before we set activeTransport, since activeTransport is accessed // without a lock. activeTransport = newActiveTransport; } return newActiveTransport; } } private class RealChannel extends Channel { @Override public <ReqT, RespT> ClientCall<ReqT, RespT> newCall( MethodDescriptor<ReqT, RespT> method, CallOptions callOptions) { return new ClientCallImpl<ReqT, RespT>( method, new SerializingExecutor(executor), callOptions, transportProvider, scheduledExecutor) .setUserAgent(userAgent); } } private class TransportListener implements ClientTransport.Listener { private final ClientTransport transport; public TransportListener(ClientTransport transport) { this.transport = transport; } @Override public void transportReady() { synchronized (lock) { if (activeTransport == transport) { reconnectPolicy = null; } } } @Override public void transportShutdown(Status s) { synchronized (lock) { if (activeTransport == transport) { activeTransport = null; // This transport listener was attached to the active transport. if (s.isOk()) { return; } // Alright, something bad has happened. if (reconnectPolicy == null) { // This happens the first time something bad has happened. reconnectPolicy = backoffPolicyProvider.get(); reconnectTimeMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); } activeTransport = new InactiveTransport(s); reconnectTimeMillis += reconnectPolicy.nextBackoffMillis(); } } } @Override public void transportTerminated() { synchronized (lock) { if (activeTransport == transport) { log.warning("transportTerminated called without previous transportShutdown"); activeTransport = null; } // TODO(notcarl): replace this with something more meaningful transportShutdown(Status.UNKNOWN.withDescription("transport shutdown for unknown reason")); transports.remove(transport); if (shutdown && transports.isEmpty()) { if (terminated) { log.warning("transportTerminated called after already terminated"); } terminated = true; lock.notifyAll(); if (terminationRunnable != null) { terminationRunnable.run(); } } } } } /** Intended for internal use only. */ // TODO(johnbcoughlin) make this package private when we can do so with the tests. @VisibleForTesting public static final Metadata.Key<Long> TIMEOUT_KEY = Metadata.Key.of(HttpUtil.TIMEOUT, new TimeoutMarshaller()); /** * Marshals a microseconds representation of the timeout to and from a string representation, * consisting of an ASCII decimal representation of a number with at most 8 digits, followed by a * unit: u = microseconds m = milliseconds S = seconds M = minutes H = hours * * <p>The representation is greedy with respect to precision. That is, 2 seconds will be * represented as `2000000u`. * * <p>See <a href="https://github.com/grpc/grpc-common/blob/master/PROTOCOL-HTTP2.md#requests">the * request header definition</a> */ @VisibleForTesting static class TimeoutMarshaller implements Metadata.AsciiMarshaller<Long> { @Override public String toAsciiString(Long timeoutMicros) { Preconditions.checkArgument(timeoutMicros >= 0, "Negative timeout"); long timeout; String timeoutUnit; // the smallest integer with 9 digits int cutoff = 100000000; if (timeoutMicros < cutoff) { timeout = timeoutMicros; timeoutUnit = "u"; } else if (timeoutMicros / 1000 < cutoff) { timeout = timeoutMicros / 1000; timeoutUnit = "m"; } else if (timeoutMicros / (1000 * 1000) < cutoff) { timeout = timeoutMicros / (1000 * 1000); timeoutUnit = "S"; } else if (timeoutMicros / (60 * 1000 * 1000) < cutoff) { timeout = timeoutMicros / (60 * 1000 * 1000); timeoutUnit = "M"; } else if (timeoutMicros / (60L * 60L * 1000L * 1000L) < cutoff) { timeout = timeoutMicros / (60L * 60L * 1000L * 1000L); timeoutUnit = "H"; } else { throw new IllegalArgumentException("Timeout too large"); } return Long.toString(timeout) + timeoutUnit; } @Override public Long parseAsciiString(String serialized) { String valuePart = serialized.substring(0, serialized.length() - 1); char unit = serialized.charAt(serialized.length() - 1); long factor; switch (unit) { case 'u': factor = 1; break; case 'm': factor = 1000L; break; case 'S': factor = 1000L * 1000L; break; case 'M': factor = 60L * 1000L * 1000L; break; case 'H': factor = 60L * 60L * 1000L * 1000L; break; default: throw new IllegalArgumentException(String.format("Invalid timeout unit: %s", unit)); } return Long.parseLong(valuePart) * factor; } } static final SharedResourceHolder.Resource<ScheduledExecutorService> TIMER_SERVICE = new SharedResourceHolder.Resource<ScheduledExecutorService>() { @Override public ScheduledExecutorService create() { return Executors.newSingleThreadScheduledExecutor( new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); return thread; } }); } @Override public void close(ScheduledExecutorService instance) { instance.shutdown(); } }; private static final class InactiveTransport implements ClientTransport { private final Status shutdownStatus; private InactiveTransport(Status s) { shutdownStatus = s; } @Override public ClientStream newStream( MethodDescriptor<?, ?> method, Headers headers, ClientStreamListener listener) { listener.closed(shutdownStatus, new Metadata.Trailers()); return new ClientCallImpl.NoopClientStream(); } @Override public void start(Listener listener) { throw new IllegalStateException(); } @Override public void ping(final PingCallback callback, Executor executor) { executor.execute( new Runnable() { @Override public void run() { callback.pingFailed(shutdownStatus.asException()); } }); } @Override public void shutdown() { // no-op } } }