22
33import com .google .common .base .Preconditions ;
44import com .google .common .util .concurrent .MoreExecutors ;
5+ import com .google .common .util .concurrent .ThreadFactoryBuilder ;
56
67import java .io .Closeable ;
78import java .io .IOException ;
89import java .util .ArrayDeque ;
910import java .util .ArrayList ;
1011import java .util .concurrent .Callable ;
1112import java .util .concurrent .Executor ;
12- import java .util .concurrent .ExecutorService ;
1313import java .util .concurrent .Executors ;
1414import java .util .concurrent .ScheduledExecutorService ;
15+ import java .util .concurrent .ScheduledFuture ;
16+ import java .util .concurrent .TimeUnit ;
17+ import java .util .concurrent .TimeoutException ;
1518import java .util .concurrent .atomic .AtomicIntegerFieldUpdater ;
1619
1720import javax .annotation .Nullable ;
1821
1922
2023/**
21- * --Add warning about using this too liberally--
22- * --Add warning about using this with containers that unload Classloaders--
23- * -- Add note that in the case of cascading closure that listeners to the parent are always
24- * notified before listeners on the children.
24+ * A context propagation mechanism which carries deadlines, cancellation signals,
25+ * and other scoped values across API boundaries and between threads. Examples of functionality
26+ * propagated via context include:
27+ * <ul>
28+ * <li>Deadlines for a local operation or remote call.</li>
29+ * <li>Security principals & credentials.</li>
30+ * <li>Local and distributed tracing context.</li>
31+ * </ul>
32+ *
33+ * <p>Context is not intended for passing optional parameters to an API and developers should
34+ * take care to avoid excessive dependence on context when designing an API.
35+ *
36+ * <p>If Context is being used in an environment that needs to support class unloading it is the
37+ * responsibility of the application to ensure that all contexts are properly cancelled.
2538 *
2639 */
2740public class Context {
2841
42+ /**
43+ * Use a shared resource to retain the {@link ScheduledExecutorService} used to
44+ * implement deadline based context cancellation. This allows the executor to be
45+ * shutdown if its not in use thereby allowing Context to be unloaded.
46+ */
47+ static final SharedResourceHolder .Resource <ScheduledExecutorService > SCHEDULER =
48+ new SharedResourceHolder .Resource <ScheduledExecutorService >() {
49+ private static final String name = "context-scheduler" ;
50+ @ Override
51+ public ScheduledExecutorService create () {
52+ return Executors .newScheduledThreadPool (1 , new ThreadFactoryBuilder ()
53+ .setNameFormat (name + "-%d" ).build ());
54+ }
55+
56+ @ Override
57+ public void close (ScheduledExecutorService instance ) {
58+ instance .shutdown ();
59+ }
60+
61+ @ Override
62+ public String toString () {
63+ return name ;
64+ }
65+ };
66+
2967 /**
3068 * Stack of context objects which is used to record attach & detach history on a thread.
3169 */
@@ -123,6 +161,28 @@ public CancellableContext withCancellation() {
123161 return new CancellableContext (this );
124162 }
125163
164+ /**
165+ * Create a new context which will cancel itself after an absolute deadline expressed as
166+ * nanoseconds in the {@link System#nanoTime()} clock.
167+ *
168+ * <p>It is recommended that callers only use this method when propagating a derivative of
169+ * a received existing deadline. When establishing a new deadline {@link #withDeadlineAfter}
170+ * is the better mechanism.
171+ */
172+ public CancellableContext withDeadlineNanoTime (long deadlineNanoTime ) {
173+ return withDeadlineAfter (Math .max (0 , deadlineNanoTime - System .nanoTime ()),
174+ TimeUnit .NANOSECONDS );
175+ }
176+
177+ /**
178+ * Create a new context which will cancel itself after the given {@code duration} from now.
179+ */
180+ public CancellableContext withDeadlineAfter (long duration , TimeUnit unit ) {
181+ Preconditions .checkArgument (duration >= 0 , "duration must be greater than or equal to 0" );
182+ Preconditions .checkNotNull (unit , "unit" );
183+ return new CancellableContext (this , unit .toNanos (duration ));
184+ }
185+
126186 /**
127187 * Create a new context which cannot be cancelled and will not propagate the cancellation of its
128188 * parent.
@@ -326,6 +386,7 @@ public static final class CancellableContext extends Context {
326386 volatile int cancelled ;
327387 private volatile Throwable cause ;
328388 private final Context dummy ;
389+ private ScheduledFuture <?> scheduledFuture ;
329390
330391
331392 private CancellableContext (Context parent ) {
@@ -335,6 +396,20 @@ private CancellableContext(Context parent) {
335396 dummy = new Context (this , EMPTY_ENTRIES );
336397 }
337398
399+ private CancellableContext (Context parent , long delayNanos ) {
400+ super (parent , EMPTY_ENTRIES );
401+ // Create a dummy that inherits from this to attach and detach so that you cannot retrieve a
402+ // cancellable context from Context.current()
403+ dummy = new Context (this , EMPTY_ENTRIES );
404+ scheduledFuture = SCHEDULER .create ().schedule (new Runnable () {
405+ @ Override
406+ public void run () {
407+ cancel (new TimeoutException ("context timed out" ));
408+ }
409+ }, delayNanos , TimeUnit .NANOSECONDS );
410+ }
411+
412+
338413 @ Override
339414 public void attach () {
340415 dummy .attach ();
@@ -371,6 +446,11 @@ public void close() throws IOException {
371446 */
372447 public boolean cancel (@ Nullable Throwable cause ) {
373448 if (cancelledUpdater .compareAndSet (this , 0 , 1 )) {
449+ if (scheduledFuture != null ) {
450+ // If we have a scheduled cancellation pending attempt to cancel it.
451+ scheduledFuture .cancel (false );
452+ scheduledFuture = null ;
453+ }
374454 this .cause = cause ;
375455 notifyAndClearListeners ();
376456 return true ;
0 commit comments