io.grpc.Context Maven / Gradle / Ivy
/*
* Copyright 2015 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc;
import io.grpc.Context.CheckReturnValue;
import io.grpc.PersistentHashArrayMappedTrie.Node;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A context propagation mechanism which can carry scoped-values across API boundaries and between
* threads. Examples of state propagated via context include:
*
* - Security principals and credentials.
* - Local and distributed tracing information.
*
*
* A Context object can be {@link #attach attached} to the {@link Storage}, which effectively
* forms a scope for the context. The scope is bound to the current thread. Within a scope,
* its Context is accessible even across API boundaries, through {@link #current}. The scope is
* later exited by {@link #detach detaching} the Context.
*
*
Context objects are immutable and inherit state from their parent. To add or overwrite the
* current state a new context object must be created and then attached, replacing the previously
* bound context. For example:
*
*
* Context withCredential = Context.current().withValue(CRED_KEY, cred);
* withCredential.run(new Runnable() {
* public void run() {
* readUserRecords(userId, CRED_KEY.get());
* }
* });
*
*
* Contexts are also used to represent a scoped unit of work. When the unit of work is done the
* context must be cancelled. This cancellation will cascade to all descendant contexts. You can
* add a {@link CancellationListener} to a context to be notified when it or one of its ancestors
* has been cancelled. Cancellation does not release the state stored by a context and it's
* perfectly valid to {@link #attach()} an already cancelled context to make it current. To cancel a
* context (and its descendants) you first create a {@link CancellableContext} and when you need to
* signal cancellation call {@link CancellableContext#cancel} or {@link
* CancellableContext#detachAndCancel}.
*
*
Contexts can also be created with a timeout relative to the system nano clock which will
* cause it to automatically cancel at the desired time.
*
*
*
Notes and cautions on use:
*
* - Every {@code attach()} should have a {@code detach()} in the same method. And every
* CancellableContext should be cancelled at some point. Breaking these rules may lead to memory
* leaks.
*
- While Context objects are immutable they do not place such a restriction on the state
* they store.
* - Context is not intended for passing optional parameters to an API and developers should
* take care to avoid excessive dependence on context when designing an API.
* - Do not mock this class. Use {@link #ROOT} for a non-null instance.
*
*/
/* @DoNotMock("Use ROOT for a non-null Context") // commented out to avoid dependencies */
@CheckReturnValue
public class Context {
static final Logger log = Logger.getLogger(Context.class.getName());
// Long chains of contexts are suspicious and usually indicate a misuse of Context.
// The threshold is arbitrarily chosen.
// VisibleForTesting
static final int CONTEXT_DEPTH_WARN_THRESH = 1000;
/**
* The logical root context which is the ultimate ancestor of all contexts. This context
* is not cancellable and so will not cascade cancellation or retain listeners.
*
* Never assume this is the default context for new threads, because {@link Storage} may define
* a default context that is different from ROOT.
*/
public static final Context ROOT = new Context();
// Visible For testing
static Storage storage() {
return LazyStorage.storage;
}
// Lazy-loaded storage. Delaying storage initialization until after class initialization makes it
// much easier to avoid circular loading since there can still be references to Context as long as
// they don't depend on storage, like key() and currentContextExecutor(). It also makes it easier
// to handle exceptions.
private static final class LazyStorage {
static final Storage storage;
static {
AtomicReference deferredStorageFailure = new AtomicReference<>();
storage = createStorage(deferredStorageFailure);
Throwable failure = deferredStorageFailure.get();
// Logging must happen after storage has been set, as loggers may use Context.
if (failure != null) {
log.log(Level.FINE, "Storage override doesn't exist. Using default", failure);
}
}
private static Storage createStorage(
AtomicReference super ClassNotFoundException> deferredStorageFailure) {
try {
Class> clazz = Class.forName("io.grpc.override.ContextStorageOverride");
// The override's constructor is prohibited from triggering any code that can loop back to
// Context
return clazz.asSubclass(Storage.class).getConstructor().newInstance();
} catch (ClassNotFoundException e) {
deferredStorageFailure.set(e);
return new ThreadLocalContextStorage();
} catch (Exception e) {
throw new RuntimeException("Storage override failed to initialize", e);
}
}
}
/**
* Create a {@link Key} with the given debug name.
*
* @param debugString a name intended for debugging purposes and does not impact behavior.
* Multiple different keys may have the same debugString.
* The value should be not null.
*/
public static Key key(String debugString) {
return new Key<>(debugString);
}
/**
* Create a {@link Key} with the given debug name and default value.
*
* @param debugString a name intended for debugging purposes and does not impact behavior.
* Multiple different keys may have the same debugString.
* The value should be not null.
*/
public static Key keyWithDefault(String debugString, T defaultValue) {
return new Key<>(debugString, defaultValue);
}
/**
* Return the context associated with the current scope, will never return {@code null}.
*
* Will never return {@link CancellableContext} even if one is attached, instead a
* {@link Context} is returned with the same properties and lifetime. This is to avoid
* code stealing the ability to cancel arbitrarily.
*/
public static Context current() {
Context current = storage().current();
if (current == null) {
return ROOT;
}
return current;
}
final CancellableContext cancellableAncestor;
final Node, Object> keyValueEntries;
// The number parents between this context and the root context.
final int generation;
/**
* Construct a context that cannot be cancelled and will not cascade cancellation from its parent.
*/
private Context(Node, Object> keyValueEntries, int generation) {
this.cancellableAncestor = null;
this.keyValueEntries = keyValueEntries;
this.generation = generation;
validateGeneration(generation);
}
/**
* Construct a context that cannot be cancelled but will cascade cancellation from its parent if
* it is cancellable.
*/
private Context(Context parent, Node, Object> keyValueEntries) {
this.cancellableAncestor = cancellableAncestor(parent);
this.keyValueEntries = keyValueEntries;
this.generation = parent.generation + 1;
validateGeneration(generation);
}
/**
* Construct for {@link #ROOT}.
*/
private Context() {
this.cancellableAncestor = null;
this.keyValueEntries = null;
this.generation = 0;
validateGeneration(generation);
}
/**
* Create a new context which is independently cancellable and also cascades cancellation from
* its parent. Callers must ensure that either {@link
* CancellableContext#cancel(Throwable)} or {@link CancellableContext#detachAndCancel(Context,
* Throwable)} are called at a later point, in order to allow this context to be garbage
* collected.
*
* Sample usage:
*
* Context.CancellableContext withCancellation = Context.current().withCancellation();
* try {
* withCancellation.run(new Runnable() {
* public void run() {
* Context current = Context.current();
* while (!current.isCancelled()) {
* keepWorking();
* }
* }
* });
* } finally {
* withCancellation.cancel(null);
* }
*
*/
public CancellableContext withCancellation() {
return new CancellableContext(this);
}
/**
* Create a new context which will cancel itself after the given {@code duration} from now.
* The returned context will cascade cancellation of its parent. Callers may explicitly cancel
* the returned context prior to the deadline just as for {@link #withCancellation()}. If the unit
* of work completes before the deadline, the context should be explicitly cancelled to allow
* it to be garbage collected.
*
* Sample usage:
*
* Context.CancellableContext withDeadline = Context.current()
* .withDeadlineAfter(5, TimeUnit.SECONDS, scheduler);
* try {
* withDeadline.run(new Runnable() {
* public void run() {
* Context current = Context.current();
* while (!current.isCancelled()) {
* keepWorking();
* }
* }
* });
* } finally {
* withDeadline.cancel(null);
* }
*
*/
public CancellableContext withDeadlineAfter(long duration, TimeUnit unit,
ScheduledExecutorService scheduler) {
return withDeadline(Deadline.after(duration, unit), scheduler);
}
/**
* Create a new context which will cancel itself at the given {@link Deadline}.
* The returned context will cascade cancellation of its parent. Callers may explicitly cancel
* the returned context prior to the deadline just as for {@link #withCancellation()}. If the unit
* of work completes before the deadline, the context should be explicitly cancelled to allow
* it to be garbage collected.
*
* Sample usage:
*
* Context.CancellableContext withDeadline = Context.current()
* .withDeadline(someReceivedDeadline, scheduler);
* try {
* withDeadline.run(new Runnable() {
* public void run() {
* Context current = Context.current();
* while (!current.isCancelled() && moreWorkToDo()) {
* keepWorking();
* }
* }
* });
* } finally {
* withDeadline.cancel(null);
* }
*
*/
public CancellableContext withDeadline(Deadline newDeadline, ScheduledExecutorService scheduler) {
checkNotNull(newDeadline, "deadline");
checkNotNull(scheduler, "scheduler");
Deadline existingDeadline = getDeadline();
boolean scheduleDeadlineCancellation = true;
if (existingDeadline != null && existingDeadline.compareTo(newDeadline) <= 0) {
// The new deadline won't have an effect, so ignore it
newDeadline = existingDeadline;
scheduleDeadlineCancellation = false;
}
CancellableContext newCtx = new CancellableContext(this, newDeadline);
if (scheduleDeadlineCancellation) {
newCtx.setUpDeadlineCancellation(newDeadline, scheduler);
}
return newCtx;
}
/**
* Create a new context with the given key value set. The new context will cascade cancellation
* from its parent.
*
* Context withCredential = Context.current().withValue(CRED_KEY, cred);
* withCredential.run(new Runnable() {
* public void run() {
* readUserRecords(userId, CRED_KEY.get());
* }
* });
*
*
* Note that multiple calls to {@link #withValue} can be chained together.
* That is,
*
*
* context.withValues(K1, V1, K2, V2);
* // is the same as
* context.withValue(K1, V1).withValue(K2, V2);
*
*
* Nonetheless, {@link Context} should not be treated like a general purpose
* map with a large number of keys and values — combine multiple related items
* together into a single key instead of separating them. But if the items
* are unrelated, have separate keys for them.
*/
public Context withValue(Key k1, V v1) {
Node, Object> newKeyValueEntries =
PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1);
return new Context(this, newKeyValueEntries);
}
/**
* Create a new context with the given key value set. The new context will cascade cancellation
* from its parent.
*/
public Context withValues(Key k1, V1 v1, Key k2, V2 v2) {
Node, Object> newKeyValueEntries =
PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1);
newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k2, v2);
return new Context(this, newKeyValueEntries);
}
/**
* Create a new context with the given key value set. The new context will cascade cancellation
* from its parent.
*/
public Context withValues(Key k1, V1 v1, Key k2, V2 v2, Key k3, V3 v3) {
Node, Object> newKeyValueEntries =
PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1);
newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k2, v2);
newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k3, v3);
return new Context(this, newKeyValueEntries);
}
/**
* Create a new context with the given key value set. The new context will cascade cancellation
* from its parent.
*
* For more than 4 key-value pairs, note that multiple calls to
* {@link #withValue} can be chained together. That is,
*
*
* context.withValues(K1, V1, K2, V2);
* // is the same as
* context.withValue(K1, V1).withValue(K2, V2);
*
*
* Nonetheless, {@link Context} should not be treated like a general purpose
* map with a large number of keys and values — combine multiple related items
* together into a single key instead of separating them. But if the items
* are unrelated, have separate keys for them.
*/
public Context withValues(Key k1, V1 v1, Key k2, V2 v2,
Key k3, V3 v3, Key k4, V4 v4) {
Node, Object> newKeyValueEntries =
PersistentHashArrayMappedTrie.put(keyValueEntries, k1, v1);
newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k2, v2);
newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k3, v3);
newKeyValueEntries = PersistentHashArrayMappedTrie.put(newKeyValueEntries, k4, v4);
return new Context(this, newKeyValueEntries);
}
/**
* Create a new context which propagates the values of this context but does not cascade its
* cancellation.
*/
public Context fork() {
return new Context(keyValueEntries, generation + 1);
}
/**
* Attach this context, thus enter a new scope within which this context is {@link #current}. The
* previously current context is returned. It is allowed to attach contexts where {@link
* #isCancelled()} is {@code true}.
*
* Instead of using {@code attach()} and {@link #detach(Context)} most use-cases are better
* served by using the {@link #run(Runnable)} or {@link #call(java.util.concurrent.Callable)} to
* execute work immediately within a context's scope. If work needs to be done in other threads it
* is recommended to use the 'wrap' methods or to use a propagating executor.
*
*
All calls to {@code attach()} should have a corresponding {@link #detach(Context)} within
* the same method:
*
{@code Context previous = someContext.attach();
* try {
* // Do work
* } finally {
* someContext.detach(previous);
* }}
*/
public Context attach() {
Context prev = storage().doAttach(this);
if (prev == null) {
return ROOT;
}
return prev;
}
/**
* Reverse an {@code attach()}, restoring the previous context and exiting the current scope.
*
* This context should be the same context that was previously {@link #attach attached}. The
* provided replacement should be what was returned by the same {@link #attach attach()} call. If
* an {@code attach()} and a {@code detach()} meet above requirements, they match.
*
*
It is expected that between any pair of matching {@code attach()} and {@code detach()}, all
* {@code attach()}es and {@code detach()}es are called in matching pairs. If this method finds
* that this context is not {@link #current current}, either you or some code in-between are not
* detaching correctly, and a SEVERE message will be logged but the context to attach will still
* be bound. Never use {@code Context.current().detach()}, as this will
* compromise this error-detecting mechanism.
*/
public void detach(Context toAttach) {
checkNotNull(toAttach, "toAttach");
storage().detach(this, toAttach);
}
// Visible for testing
boolean isCurrent() {
return current() == this;
}
/**
* Is this context cancelled.
*/
public boolean isCancelled() {
if (cancellableAncestor == null) {
return false;
} else {
return cancellableAncestor.isCancelled();
}
}
/**
* 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}.
*
*
The cancellation cause is provided for informational purposes only and implementations
* should generally assume that it has already been handled and logged properly.
*/
public Throwable cancellationCause() {
if (cancellableAncestor == null) {
return null;
} else {
return cancellableAncestor.cancellationCause();
}
}
/**
* A context may have an associated {@link Deadline} at which it will be automatically cancelled.
* @return A {@link io.grpc.Deadline} or {@code null} if no deadline is set.
*/
public Deadline getDeadline() {
if (cancellableAncestor == null) {
return null;
}
return cancellableAncestor.getDeadline();
}
/**
* Add a listener that will be notified when the context becomes cancelled.
*/
public void addListener(final CancellationListener cancellationListener,
final Executor executor) {
checkNotNull(cancellationListener, "cancellationListener");
checkNotNull(executor, "executor");
if (cancellableAncestor == null) {
return;
}
cancellableAncestor.addListenerInternal(
new ExecutableListener(executor, cancellationListener, this));
}
/**
* Remove a {@link CancellationListener}.
*/
public void removeListener(CancellationListener cancellationListener) {
if (cancellableAncestor == null) {
return;
}
cancellableAncestor.removeListenerInternal(cancellationListener, this);
}
// Used in tests to ensure that listeners are defined and released when cancellation cascades.
// It's very important to ensure that we do not accidentally retain listeners.
int listenerCount() {
if (cancellableAncestor == null) {
return 0;
}
return cancellableAncestor.listenerCount();
}
/**
* Immediately run a {@link Runnable} with this context as the {@link #current} context.
* @param r {@link Runnable} to run.
*/
public void run(Runnable r) {
Context previous = attach();
try {
r.run();
} finally {
detach(previous);
}
}
/**
* Immediately call a {@link Callable} with this context as the {@link #current} context.
* @param c {@link Callable} to call.
* @return result of call.
*/
@CanIgnoreReturnValue
public V call(Callable c) throws Exception {
Context previous = attach();
try {
return c.call();
} finally {
detach(previous);
}
}
/**
* Wrap a {@link Runnable} so that it executes with this context as the {@link #current} context.
*/
public Runnable wrap(final Runnable r) {
return new Runnable() {
@Override
public void run() {
Context previous = attach();
try {
r.run();
} finally {
detach(previous);
}
}
};
}
/**
* Wrap a {@link Callable} so that it executes with this context as the {@link #current} context.
*/
public Callable wrap(final Callable c) {
return new Callable() {
@Override
public C call() throws Exception {
Context previous = attach();
try {
return c.call();
} finally {
detach(previous);
}
}
};
}
/**
* Wrap an {@link Executor} so that it always executes with this context as the {@link #current}
* context. It is generally expected that {@link #currentContextExecutor(Executor)} would be
* used more commonly than this method.
*
* One scenario in which this executor may be useful is when a single thread is sharding work
* to multiple threads.
*
* @see #currentContextExecutor(Executor)
*/
public Executor fixedContextExecutor(final Executor e) {
final class FixedContextExecutor implements Executor {
@Override
public void execute(Runnable r) {
e.execute(wrap(r));
}
}
return new FixedContextExecutor();
}
/**
* Create an executor that propagates the {@link #current} context when {@link Executor#execute}
* is called as the {@link #current} context of the {@code Runnable} scheduled. Note that this
* is a static method.
*
* @see #fixedContextExecutor(Executor)
*/
public static Executor currentContextExecutor(final Executor e) {
final class CurrentContextExecutor implements Executor {
@Override
public void execute(Runnable r) {
e.execute(Context.current().wrap(r));
}
}
return new CurrentContextExecutor();
}
/**
* A context which inherits cancellation from its parent but which can also be independently
* cancelled and which will propagate cancellation to its descendants. To avoid leaking memory,
* every CancellableContext must have a defined lifetime, after which it is guaranteed to be
* cancelled.
*
*
This class must be cancelled by either calling {@link #close} or {@link #cancel}.
* {@link #close} is equivalent to calling {@code cancel(null)}. It is safe to call the methods
* more than once, but only the first call will have any effect. Because it's safe to call the
* methods multiple times, users are encouraged to always call {@link #close} at the end of
* the operation, and disregard whether {@link #cancel} was already called somewhere else.
*
*
Blocking code can use the try-with-resources idiom:
*
* try (CancellableContext c = Context.current()
* .withDeadlineAfter(100, TimeUnit.MILLISECONDS, executor)) {
* Context toRestore = c.attach();
* try {
* // do some blocking work
* } finally {
* c.detach(toRestore);
* }
* }
*
* Asynchronous code will have to manually track the end of the CancellableContext's lifetime,
* and cancel the context at the appropriate time.
*/
public static final class CancellableContext extends Context implements Closeable {
private final Deadline deadline;
private final Context uncancellableSurrogate;
private ArrayList listeners;
// parentListener is initialized when listeners is initialized (only if there is a
// cancellable ancestor), and uninitialized when listeners is uninitialized.
private CancellationListener parentListener;
private Throwable cancellationCause;
private ScheduledFuture> pendingDeadline;
private boolean cancelled;
/**
* Create a cancellable context that does not have a deadline.
*/
private CancellableContext(Context parent) {
super(parent, parent.keyValueEntries);
deadline = parent.getDeadline();
// Create a surrogate that inherits from this to attach so that you cannot retrieve a
// cancellable context from Context.current()
uncancellableSurrogate = new Context(this, keyValueEntries);
}
/**
* Create a cancellable context that has a deadline.
*/
private CancellableContext(Context parent, Deadline deadline) {
super(parent, parent.keyValueEntries);
this.deadline = deadline;
this.uncancellableSurrogate = new Context(this, keyValueEntries);
}
private void setUpDeadlineCancellation(Deadline deadline, ScheduledExecutorService scheduler) {
if (!deadline.isExpired()) {
final class CancelOnExpiration implements Runnable {
@Override
public void run() {
try {
cancel(new TimeoutException("context timed out"));
} catch (Throwable t) {
log.log(Level.SEVERE, "Cancel threw an exception, which should not happen", t);
}
}
}
synchronized (this) {
pendingDeadline = deadline.runOnExpiration(new CancelOnExpiration(), scheduler);
}
} else {
// Cancel immediately if the deadline is already expired.
cancel(new TimeoutException("context timed out"));
}
}
@Override
public Context attach() {
return uncancellableSurrogate.attach();
}
@Override
public void detach(Context toAttach) {
uncancellableSurrogate.detach(toAttach);
}
@Override
public void addListener(
final CancellationListener cancellationListener, final Executor executor) {
checkNotNull(cancellationListener, "cancellationListener");
checkNotNull(executor, "executor");
addListenerInternal(new ExecutableListener(executor, cancellationListener, this));
}
private void addListenerInternal(ExecutableListener executableListener) {
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<>();
listeners.add(executableListener);
if (cancellableAncestor != null) {
parentListener =
new CancellationListener() {
@Override
public void cancelled(Context context) {
CancellableContext.this.cancel(context.cancellationCause());
}
};
cancellableAncestor.addListenerInternal(
new ExecutableListener(DirectExecutor.INSTANCE, parentListener, this));
}
} else {
listeners.add(executableListener);
}
}
}
}
@Override
public void removeListener(CancellationListener cancellationListener) {
removeListenerInternal(cancellationListener, this);
}
private void removeListenerInternal(CancellationListener cancellationListener,
Context context) {
synchronized (this) {
if (listeners != null) {
for (int i = listeners.size() - 1; i >= 0; i--) {
ExecutableListener executableListener = listeners.get(i);
if (executableListener.listener == cancellationListener
&& executableListener.context == context) {
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()) {
if (cancellableAncestor != null) {
cancellableAncestor.removeListener(parentListener);
}
parentListener = null;
listeners = null;
}
}
}
}
/**
* Returns true if the Context is the current context.
*
* @deprecated This method violates some GRPC class encapsulation and should not be used.
* If you must know whether a Context is the current context, check whether it is the same
* object returned by {@link Context#current()}.
*/
//TODO(spencerfang): The superclass's method is package-private, so this should really match.
@Override
@Deprecated
public boolean isCurrent() {
return uncancellableSurrogate.isCurrent();
}
/**
* Cancel this context and optionally provide a cause (can be {@code null}) for the
* cancellation. This will trigger notification of listeners. It is safe to call this method
* multiple times. Only the first call will have any effect.
*
* Calling {@code cancel(null)} is the same as calling {@link #close}.
*
* @return {@code true} if this context cancelled the context and notified listeners,
* {@code false} if the context was already cancelled.
*/
@CanIgnoreReturnValue
public boolean cancel(Throwable cause) {
boolean triggeredCancel = false;
ScheduledFuture> localPendingDeadline = null;
synchronized (this) {
if (!cancelled) {
cancelled = true;
if (pendingDeadline != null) {
// If we have a scheduled cancellation pending attempt to cancel it.
localPendingDeadline = pendingDeadline;
pendingDeadline = null;
}
this.cancellationCause = cause;
triggeredCancel = true;
}
}
if (localPendingDeadline != null) {
localPendingDeadline.cancel(false);
}
if (triggeredCancel) {
notifyAndClearListeners();
}
return triggeredCancel;
}
/**
* Notify all listeners that this context has been cancelled and immediately release
* any reference to them so that they may be garbage collected.
*/
private void notifyAndClearListeners() {
ArrayList tmpListeners;
CancellationListener tmpParentListener;
synchronized (this) {
if (listeners == null) {
return;
}
tmpParentListener = parentListener;
parentListener = null;
tmpListeners = listeners;
listeners = null;
}
// Deliver events to this 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 (ExecutableListener tmpListener : tmpListeners) {
if (tmpListener.context == this) {
tmpListener.deliver();
}
}
for (ExecutableListener tmpListener : tmpListeners) {
if (!(tmpListener.context == this)) {
tmpListener.deliver();
}
}
if (cancellableAncestor != null) {
cancellableAncestor.removeListener(tmpParentListener);
}
}
@Override
int listenerCount() {
synchronized (this) {
return listeners == null ? 0 : listeners.size();
}
}
/**
* Cancel this context and detach it as the current context.
*
* @param toAttach context to make current.
* @param cause of cancellation, can be {@code null}.
*/
public void detachAndCancel(Context toAttach, Throwable cause) {
try {
detach(toAttach);
} finally {
cancel(cause);
}
}
@Override
public boolean isCancelled() {
synchronized (this) {
if (cancelled) {
return true;
}
}
// Detect cancellation of parent in the case where we have no listeners and
// record it.
if (super.isCancelled()) {
cancel(super.cancellationCause());
return true;
}
return false;
}
@Override
public Throwable cancellationCause() {
if (isCancelled()) {
return cancellationCause;
}
return null;
}
@Override
public Deadline getDeadline() {
return deadline;
}
/**
* Cleans up this object by calling {@code cancel(null)}.
*/
@Override
public void close() {
cancel(null);
}
}
/**
* A listener notified on context cancellation.
*/
public interface CancellationListener {
/**
* Notifies that a context was cancelled.
*
* @param context the newly cancelled context.
*/
void cancelled(Context context);
}
/**
* Key for indexing values stored in a context. Keys use reference equality and Context does not
* provide a mechanism to loop over Keys. This means there is no way to access a Key's value from
* a Context without having access to the Key instance itself. This allows strong control over
* what code can get/set a key in the Context. For example, you might manage access to Key similar
* to a ThreadLocal using Java visibility (private/protected). Generally Keys are stored in static
* fields.
*/
public static final class Key {
private final String name;
private final T defaultValue;
Key(String name) {
this(name, null);
}
Key(String name, T defaultValue) {
this.name = checkNotNull(name, "name");
this.defaultValue = defaultValue;
}
/**
* Get the value from the {@link #current()} context for this key.
*/
public T get() {
return get(Context.current());
}
/**
* Get the value from the specified context for this key.
*/
@SuppressWarnings("unchecked")
public T get(Context context) {
T value = (T) PersistentHashArrayMappedTrie.get(context.keyValueEntries, this);
return value == null ? defaultValue : value;
}
@Override
public String toString() {
return name;
}
}
/**
* Defines the mechanisms for attaching and detaching the "current" context. The constructor for
* extending classes must not trigger any activity that can use Context, which includes
* logging, otherwise it can trigger an infinite initialization loop. Extending classes must not
* assume that only one instance will be created; Context guarantees it will only use one
* instance, but it may create multiple and then throw away all but one.
*
* The default implementation will put the current context in a {@link ThreadLocal}. If an
* alternative implementation named {@code io.grpc.override.ContextStorageOverride} exists in the
* classpath, it will be used instead of the default implementation.
*
*
This API is experimental and
* subject to change.
*/
public abstract static class Storage {
/**
* @deprecated This is an old API that is no longer used.
*/
@Deprecated
public void attach(Context toAttach) {
throw new UnsupportedOperationException("Deprecated. Do not call.");
}
/**
* Implements {@link io.grpc.Context#attach}.
*
*
Caution: {@link Context#attach()} interprets a return value of {@code null} to mean
* the same thing as {@link Context#ROOT}.
*
*
See also: {@link #current()}.
* @param toAttach the context to be attached
* @return A {@link Context} that should be passed back into {@link #detach(Context, Context)}
* as the {@code toRestore} parameter. {@code null} is a valid return value, but see
* caution note.
*/
public Context doAttach(Context toAttach) {
// This is a default implementation to help migrate existing Storage implementations that
// have an attach() method but no doAttach() method.
Context current = current();
attach(toAttach);
return current;
}
/**
* Implements {@link io.grpc.Context#detach}
*
* @param toDetach the context to be detached. Should be, or be equivalent to, the current
* context of the current scope
* @param toRestore the context to be the current. Should be, or be equivalent to, the context
* of the outer scope
*/
public abstract void detach(Context toDetach, Context toRestore);
/**
* Implements {@link io.grpc.Context#current}.
*
*
Caution: {@link Context} interprets a return value of {@code null} to mean the same
* thing as {@code Context{@link #ROOT}}.
*
*
See also {@link #doAttach(Context)}.
*
* @return The context of the current scope. {@code null} is a valid return value, but see
* caution note.
*/
public abstract Context current();
}
/**
* Stores listener and executor pair.
*/
private static final class ExecutableListener implements Runnable {
private final Executor executor;
final CancellationListener listener;
private final Context context;
ExecutableListener(Executor executor, CancellationListener listener, Context context) {
this.executor = executor;
this.listener = listener;
this.context = context;
}
void deliver() {
try {
executor.execute(this);
} catch (Throwable t) {
log.log(Level.INFO, "Exception notifying context listener", t);
}
}
@Override
public void run() {
listener.cancelled(context);
}
}
@CanIgnoreReturnValue
static T checkNotNull(T reference, Object errorMessage) {
if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
}
return reference;
}
private enum DirectExecutor implements Executor {
INSTANCE;
@Override
public void execute(Runnable command) {
command.run();
}
@Override
public String toString() {
return "Context.DirectExecutor";
}
}
/**
* Returns {@code parent} if it is a {@link CancellableContext}, otherwise returns the parent's
* {@link #cancellableAncestor}.
*/
static CancellableContext cancellableAncestor(Context parent) {
if (parent instanceof CancellableContext) {
return (CancellableContext) parent;
}
// The parent simply cascades cancellations.
// Bypass the parent and reference the ancestor directly (may be null).
return parent.cancellableAncestor;
}
/**
* If the ancestry chain length is unreasonably long, then print an error to the log and record
* the stack trace.
*/
private static void validateGeneration(int generation) {
if (generation == CONTEXT_DEPTH_WARN_THRESH) {
log.log(
Level.SEVERE,
"Context ancestry chain length is abnormally long. "
+ "This suggests an error in application code. "
+ "Length exceeded: " + CONTEXT_DEPTH_WARN_THRESH,
new Exception());
}
}
// Not using the standard com.google.errorprone.annotations.CheckReturnValue because that will
// introduce dependencies that some io.grpc.Context API consumers may not want.
@interface CheckReturnValue {}
@interface CanIgnoreReturnValue {}
}