mirror of https://github.com/grpc/grpc-java.git
Remove notion of stack from thread-local binding, use simple swapping instead.
This allows for binding state to be reset to known-good states precisely which in turn facilitates making it safe to have 'detach' not throw exceptions and instead log a severe error that attach/detach calls were not correctly balanced.
This commit is contained in:
parent
d2046b637d
commit
49b513504c
|
|
@ -39,7 +39,6 @@ import io.grpc.internal.SharedResourceHolder;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
@ -88,7 +87,7 @@ import javax.annotation.Nullable;
|
||||||
* <pre>
|
* <pre>
|
||||||
* CancellableContext withCancellation = Context.current().withCancellation();
|
* CancellableContext withCancellation = Context.current().withCancellation();
|
||||||
* try {
|
* try {
|
||||||
* executorService.execute(withCancellation.wrap(new Runnable() {
|
* executorService.execute(withCancellation.wrap(new Runnable() {
|
||||||
* public void run() {
|
* public void run() {
|
||||||
* while (waitingForData() && !Context.current().isCancelled()) {}
|
* while (waitingForData() && !Context.current().isCancelled()) {}
|
||||||
* }
|
* }
|
||||||
|
|
@ -142,17 +141,6 @@ public class Context {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Stack of context objects which is used to record attach & detach history on a thread.
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<ArrayDeque<Context>> contextStack =
|
|
||||||
new ThreadLocal<ArrayDeque<Context>>() {
|
|
||||||
@Override
|
|
||||||
protected ArrayDeque<Context> initialValue() {
|
|
||||||
return new ArrayDeque<Context>();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Object[][] EMPTY_ENTRIES = new Object[0][2];
|
private static final Object[][] EMPTY_ENTRIES = new Object[0][2];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -161,6 +149,16 @@ public class Context {
|
||||||
*/
|
*/
|
||||||
public static final Context ROOT = new Context(null);
|
public static final Context ROOT = new Context(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently bound context.
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Context> localContext = new ThreadLocal<Context>() {
|
||||||
|
@Override
|
||||||
|
protected Context initialValue() {
|
||||||
|
return ROOT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Key} with the given name.
|
* Create a {@link Key} with the given name.
|
||||||
*/
|
*/
|
||||||
|
|
@ -184,11 +182,11 @@ public class Context {
|
||||||
* code stealing the ability to cancel arbitrarily.
|
* code stealing the ability to cancel arbitrarily.
|
||||||
*/
|
*/
|
||||||
public static Context current() {
|
public static Context current() {
|
||||||
ArrayDeque<Context> stack = contextStack.get();
|
Context current = localContext.get();
|
||||||
if (stack.isEmpty()) {
|
if (current == null) {
|
||||||
return ROOT;
|
return ROOT;
|
||||||
}
|
}
|
||||||
return stack.peekLast();
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Context parent;
|
private final Context parent;
|
||||||
|
|
@ -229,8 +227,8 @@ public class Context {
|
||||||
/**
|
/**
|
||||||
* Create a new context which is independently cancellable and also cascades cancellation from
|
* Create a new context which is independently cancellable and also cascades cancellation from
|
||||||
* its parent. Callers should ensure that either {@link CancellableContext#cancel(Throwable)}
|
* its parent. Callers should ensure that either {@link CancellableContext#cancel(Throwable)}
|
||||||
* or {@link CancellableContext#detachAndCancel(Throwable)} are called to notify listeners and
|
* or {@link CancellableContext#detachAndCancel(Context, Throwable)} are called to notify
|
||||||
* release the resources associated with them.
|
* listeners and release the resources associated with them.
|
||||||
*
|
*
|
||||||
* <p>Sample usage:
|
* <p>Sample usage:
|
||||||
* <pre>
|
* <pre>
|
||||||
|
|
@ -343,11 +341,30 @@ public class Context {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach this context to the thread and make it {@link #current}, the previously current context
|
* Attach this context to the thread and make it {@link #current}, the previously current context
|
||||||
* will be restored when detach is called. It is allowed to attach contexts where
|
* is returned. It is allowed to attach contexts where {@link #isCancelled()} is {@code true}.
|
||||||
* {@link #isCancelled()} is {@code true}.
|
|
||||||
*/
|
*/
|
||||||
public void attach() {
|
public Context attach() {
|
||||||
contextStack.get().addLast(this);
|
Context previous = current();
|
||||||
|
localContext.set(this);
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach the current context from the thread and attach the provided replacement. If this
|
||||||
|
* context is not {@link #current()} a SEVERE message will be logged but the context to attach
|
||||||
|
* will still be bound.
|
||||||
|
*/
|
||||||
|
public void detach(Context toAttach) {
|
||||||
|
Preconditions.checkNotNull(toAttach);
|
||||||
|
Context previous = current();
|
||||||
|
if (previous != this) {
|
||||||
|
// Log a severe message instead of throwing an exception as the context to attach is assumed
|
||||||
|
// to be the correct one and the unbalanced state represents a coding mistake in a lower
|
||||||
|
// layer in the stack that cannot be recovered from here.
|
||||||
|
LOG.log(Level.SEVERE, "Context was not attached when detaching",
|
||||||
|
new Throwable().fillInStackTrace());
|
||||||
|
}
|
||||||
|
localContext.set(toAttach);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing
|
// Visible for testing
|
||||||
|
|
@ -355,27 +372,6 @@ public class Context {
|
||||||
return current() == this;
|
return current() == this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detach the current context from the thread and restore the context that was previously
|
|
||||||
* attached to the thread as the 'current' context.
|
|
||||||
*
|
|
||||||
* @throws java.lang.IllegalStateException if this context is not {@link #current()}.
|
|
||||||
*/
|
|
||||||
public void detach() {
|
|
||||||
ArrayDeque<Context> stack = contextStack.get();
|
|
||||||
if (stack.isEmpty()) {
|
|
||||||
if (this == ROOT) {
|
|
||||||
throw new IllegalStateException("Cannot detach root");
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Cannot detach non-root context when root is current");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stack.peekLast() != this) {
|
|
||||||
throw new IllegalStateException("Cannot detach a context that is not current");
|
|
||||||
}
|
|
||||||
stack.removeLast();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this context cancelled.
|
* Is this context cancelled.
|
||||||
*/
|
*/
|
||||||
|
|
@ -492,11 +488,11 @@ public class Context {
|
||||||
return new Runnable() {
|
return new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
attach();
|
Context previous = attach();
|
||||||
try {
|
try {
|
||||||
r.run();
|
r.run();
|
||||||
} finally {
|
} finally {
|
||||||
detach();
|
detach(previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -509,11 +505,11 @@ public class Context {
|
||||||
return new Callable<C>() {
|
return new Callable<C>() {
|
||||||
@Override
|
@Override
|
||||||
public C call() throws Exception {
|
public C call() throws Exception {
|
||||||
attach();
|
Context previous = attach();
|
||||||
try {
|
try {
|
||||||
return c.call();
|
return c.call();
|
||||||
} finally {
|
} finally {
|
||||||
detach();
|
detach(previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -586,7 +582,7 @@ public class Context {
|
||||||
*/
|
*/
|
||||||
private CancellableContext(Context parent) {
|
private CancellableContext(Context parent) {
|
||||||
super(parent, EMPTY_ENTRIES);
|
super(parent, EMPTY_ENTRIES);
|
||||||
// Create a dummy that inherits from this to attach and detach so that you cannot retrieve a
|
// Create a dummy that inherits from this to attach so that you cannot retrieve a
|
||||||
// cancellable context from Context.current()
|
// cancellable context from Context.current()
|
||||||
dummy = new Context(this, EMPTY_ENTRIES);
|
dummy = new Context(this, EMPTY_ENTRIES);
|
||||||
}
|
}
|
||||||
|
|
@ -611,13 +607,13 @@ public class Context {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attach() {
|
public Context attach() {
|
||||||
dummy.attach();
|
return dummy.attach();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void detach() {
|
public void detach(Context toAttach) {
|
||||||
dummy.detach();
|
dummy.detach(toAttach);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -627,17 +623,17 @@ public class Context {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach this context to the thread and return a {@link AutoCloseable} that can be
|
* Attach this context to the thread and return a {@link AutoCloseable} that can be
|
||||||
* used with try-with-resource statements to properly {@link #detach} and {@link #cancel}
|
* used with try-with-resource statements to properly attach the previously bound context
|
||||||
* the context on completion.
|
* when {@link AutoCloseable#close()} is called.
|
||||||
*
|
*
|
||||||
* @return a {@link java.io.Closeable} which can be used with try-with-resource blocks.
|
* @return a {@link java.io.Closeable} which can be used with try-with-resource blocks.
|
||||||
*/
|
*/
|
||||||
public Closeable attachAsCloseable() {
|
public Closeable attachAsCloseable() {
|
||||||
attach();
|
final Context previous = attach();
|
||||||
return new Closeable() {
|
return new Closeable() {
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
detachAndCancel(null);
|
detachAndCancel(previous, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -670,14 +666,14 @@ public class Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel this context and detach it from the current context from the thread and restore the
|
* Cancel this context and detach it as the current context from the thread.
|
||||||
* context that was previously attached to the thread as the 'current' context.
|
|
||||||
*
|
*
|
||||||
* @throws java.lang.IllegalStateException if this context is not {@link #current()}.
|
* @param toAttach context to make current.
|
||||||
|
* @param cause of cancellation, can be {@code null}.
|
||||||
*/
|
*/
|
||||||
public void detachAndCancel(@Nullable Throwable cause) {
|
public void detachAndCancel(Context toAttach, @Nullable Throwable cause) {
|
||||||
try {
|
try {
|
||||||
detach();
|
detach(toAttach);
|
||||||
} finally {
|
} finally {
|
||||||
cancel(cause);
|
cancel(cause);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
import io.grpc.internal.SharedResourceHolder;
|
import io.grpc.internal.SharedResourceHolder;
|
||||||
|
|
||||||
|
|
@ -63,6 +64,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.logging.Handler;
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.LogRecord;
|
import java.util.logging.LogRecord;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
|
@ -97,10 +99,7 @@ public class ContextTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
// Detach all contexts back to the root.
|
Context.ROOT.attach();
|
||||||
while (Context.current() != Context.ROOT) {
|
|
||||||
Context.current().detach();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|
@ -114,29 +113,25 @@ public class ContextTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rootIsAlwaysBound() {
|
public void rootIsAlwaysBound() throws Exception {
|
||||||
Context root = Context.current();
|
final SettableFuture<Boolean> rootIsBound = SettableFuture.create();
|
||||||
try {
|
new Thread(new Runnable() {
|
||||||
root.detach();
|
@Override
|
||||||
} catch (IllegalStateException ise) {
|
public void run() {
|
||||||
// Expected
|
rootIsBound.set(Context.current() == Context.ROOT);
|
||||||
assertTrue(Context.ROOT.isCurrent());
|
}
|
||||||
return;
|
}).start();
|
||||||
}
|
assertTrue(rootIsBound.get(5, TimeUnit.SECONDS));
|
||||||
fail("Attempting to detach root should fail");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rootCanBeAttached() {
|
public void rootCanBeAttached() {
|
||||||
Context root = Context.current();
|
Context.CancellableContext fork = Context.ROOT.fork();
|
||||||
Context.CancellableContext fork = root.fork();
|
fork.attach();
|
||||||
|
Context.ROOT.attach();
|
||||||
|
assertTrue(Context.ROOT.isCurrent());
|
||||||
fork.attach();
|
fork.attach();
|
||||||
root.attach();
|
|
||||||
assertTrue(root.isCurrent());
|
|
||||||
root.detach();
|
|
||||||
assertTrue(fork.isCurrent());
|
assertTrue(fork.isCurrent());
|
||||||
fork.detach();
|
|
||||||
assertTrue(root.isCurrent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -155,58 +150,53 @@ public class ContextTest {
|
||||||
@Test
|
@Test
|
||||||
public void attachedCancellableContextCannotBeCastFromCurrent() {
|
public void attachedCancellableContextCannotBeCastFromCurrent() {
|
||||||
Context initial = Context.current();
|
Context initial = Context.current();
|
||||||
Context.CancellableContext base = Context.current().withCancellation();
|
Context.CancellableContext base = initial.withCancellation();
|
||||||
base.attach();
|
base.attach();
|
||||||
assertFalse(Context.current() instanceof Context.CancellableContext);
|
assertFalse(Context.current() instanceof Context.CancellableContext);
|
||||||
|
assertNotSame(base, Context.current());
|
||||||
assertNotSame(initial, Context.current());
|
assertNotSame(initial, Context.current());
|
||||||
base.detachAndCancel(null);
|
base.detachAndCancel(initial, null);
|
||||||
assertTrue(initial.isCurrent());
|
assertSame(initial, Context.current());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void detachingNonCurrentThrowsIllegalStateException() {
|
public void attachingNonCurrentReturnsCurrent() {
|
||||||
Context current = Context.current();
|
Context initial = Context.current();
|
||||||
Context base = Context.current().withValue(PET, "dog");
|
Context base = initial.withValue(PET, "dog");
|
||||||
try {
|
assertSame(initial, base.attach());
|
||||||
base.detach();
|
assertSame(base, initial.attach());
|
||||||
fail("Expected exception");
|
|
||||||
} catch (IllegalStateException ise) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
assertSame(current, Context.current());
|
|
||||||
|
|
||||||
base.attach();
|
|
||||||
try {
|
|
||||||
current.withValue(PET, "cat").detach();
|
|
||||||
fail("Expected exception");
|
|
||||||
} catch (IllegalStateException ise) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
assertSame(base, Context.current());
|
|
||||||
base.detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void detachUnwindsAttach() {
|
public void detachingNonCurrentLogsSevereMessage() {
|
||||||
Context base = Context.current().fork();
|
final AtomicReference<LogRecord> logRef = new AtomicReference<LogRecord>();
|
||||||
Context child = base.withValue(COLOR, "red");
|
Handler handler = new Handler() {
|
||||||
Context grandchild = child.withValue(COLOR, "blue");
|
@Override
|
||||||
base.attach();
|
public void publish(LogRecord record) {
|
||||||
base.attach();
|
logRef.set(record);
|
||||||
child.attach();
|
}
|
||||||
base.attach();
|
|
||||||
grandchild.attach();
|
@Override
|
||||||
assertTrue(grandchild.isCurrent());
|
public void flush() {
|
||||||
grandchild.detach();
|
}
|
||||||
assertTrue(base.isCurrent());
|
|
||||||
base.detach();
|
@Override
|
||||||
assertTrue(child.isCurrent());
|
public void close() throws SecurityException {
|
||||||
child.detach();
|
}
|
||||||
assertTrue(base.isCurrent());
|
};
|
||||||
base.detach();
|
Logger logger = Logger.getLogger(Context.class.getName());
|
||||||
assertTrue(base.isCurrent());
|
try {
|
||||||
base.detach();
|
logger.addHandler(handler);
|
||||||
assertTrue(Context.ROOT.isCurrent());
|
Context initial = Context.current();
|
||||||
|
Context base = initial.withValue(PET, "dog");
|
||||||
|
// Base is not attached
|
||||||
|
base.detach(initial);
|
||||||
|
assertSame(initial, Context.current());
|
||||||
|
assertNotNull(logRef.get());
|
||||||
|
assertEquals(Level.SEVERE, logRef.get().getLevel());
|
||||||
|
} finally {
|
||||||
|
logger.removeHandler(handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -226,14 +216,14 @@ public class ContextTest {
|
||||||
assertEquals("cheese", FOOD.get());
|
assertEquals("cheese", FOOD.get());
|
||||||
assertNull(COLOR.get());
|
assertNull(COLOR.get());
|
||||||
|
|
||||||
child.detach();
|
child.detach(base);
|
||||||
|
|
||||||
// Should have values from base
|
// Should have values from base
|
||||||
assertEquals("dog", PET.get());
|
assertEquals("dog", PET.get());
|
||||||
assertEquals("lasagna", FOOD.get());
|
assertEquals("lasagna", FOOD.get());
|
||||||
assertNull(COLOR.get());
|
assertNull(COLOR.get());
|
||||||
|
|
||||||
base.detach();
|
base.detach(Context.ROOT);
|
||||||
|
|
||||||
assertNull(PET.get());
|
assertNull(PET.get());
|
||||||
assertEquals("lasagna", FOOD.get());
|
assertEquals("lasagna", FOOD.get());
|
||||||
|
|
@ -253,7 +243,7 @@ public class ContextTest {
|
||||||
assertEquals("blue", COLOR.get());
|
assertEquals("blue", COLOR.get());
|
||||||
assertEquals(fav, FAVORITE.get());
|
assertEquals(fav, FAVORITE.get());
|
||||||
|
|
||||||
child.detach();
|
base.attach();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -407,7 +397,7 @@ public class ContextTest {
|
||||||
assertSame(t, attached.cause());
|
assertSame(t, attached.cause());
|
||||||
assertSame(attached, listenerNotifedContext);
|
assertSame(attached, listenerNotifedContext);
|
||||||
|
|
||||||
base.detach();
|
Context.ROOT.attach();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -469,7 +459,7 @@ public class ContextTest {
|
||||||
}
|
}
|
||||||
assertSame(current, Context.current());
|
assertSame(current, Context.current());
|
||||||
|
|
||||||
current.detach();
|
current.detach(Context.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -509,7 +499,7 @@ public class ContextTest {
|
||||||
}
|
}
|
||||||
assertSame(current, Context.current());
|
assertSame(current, Context.current());
|
||||||
|
|
||||||
current.detach();
|
current.detach(Context.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -517,11 +507,11 @@ public class ContextTest {
|
||||||
QueuedExecutor queuedExecutor = new QueuedExecutor();
|
QueuedExecutor queuedExecutor = new QueuedExecutor();
|
||||||
Executor executor = Context.propagate(queuedExecutor);
|
Executor executor = Context.propagate(queuedExecutor);
|
||||||
Context base = Context.current().withValue(PET, "cat");
|
Context base = Context.current().withValue(PET, "cat");
|
||||||
base.attach();
|
Context previous = base.attach();
|
||||||
try {
|
try {
|
||||||
executor.execute(runner);
|
executor.execute(runner);
|
||||||
} finally {
|
} finally {
|
||||||
base.detach();
|
base.detach(previous);
|
||||||
}
|
}
|
||||||
assertEquals(1, queuedExecutor.runnables.size());
|
assertEquals(1, queuedExecutor.runnables.size());
|
||||||
queuedExecutor.runnables.remove().run();
|
queuedExecutor.runnables.remove().run();
|
||||||
|
|
@ -541,12 +531,12 @@ public class ContextTest {
|
||||||
@Test
|
@Test
|
||||||
public void typicalTryFinallyHandling() throws Exception {
|
public void typicalTryFinallyHandling() throws Exception {
|
||||||
Context base = Context.current().withValue(COLOR, "blue");
|
Context base = Context.current().withValue(COLOR, "blue");
|
||||||
base.attach();
|
Context previous = base.attach();
|
||||||
try {
|
try {
|
||||||
assertTrue(base.isCurrent());
|
assertTrue(base.isCurrent());
|
||||||
// Do something
|
// Do something
|
||||||
} finally {
|
} finally {
|
||||||
base.detach();
|
base.detach(previous);
|
||||||
}
|
}
|
||||||
assertFalse(base.isCurrent());
|
assertFalse(base.isCurrent());
|
||||||
}
|
}
|
||||||
|
|
@ -554,14 +544,14 @@ public class ContextTest {
|
||||||
@Test
|
@Test
|
||||||
public void typicalCancellableTryCatchFinallyHandling() throws Exception {
|
public void typicalCancellableTryCatchFinallyHandling() throws Exception {
|
||||||
Context.CancellableContext base = Context.current().withCancellation();
|
Context.CancellableContext base = Context.current().withCancellation();
|
||||||
base.attach();
|
Context previous = base.attach();
|
||||||
try {
|
try {
|
||||||
// Do something
|
// Do something
|
||||||
throw new IllegalStateException("Argh");
|
throw new IllegalStateException("Argh");
|
||||||
} catch (IllegalStateException ise) {
|
} catch (IllegalStateException ise) {
|
||||||
base.cancel(ise);
|
base.cancel(ise);
|
||||||
} finally {
|
} finally {
|
||||||
base.detachAndCancel(null);
|
base.detachAndCancel(previous, null);
|
||||||
}
|
}
|
||||||
assertTrue(base.isCancelled());
|
assertTrue(base.isCancelled());
|
||||||
assertNotNull(base.cause());
|
assertNotNull(base.cause());
|
||||||
|
|
@ -572,11 +562,11 @@ public class ContextTest {
|
||||||
Context current = Context.current();
|
Context current = Context.current();
|
||||||
Context.CancellableContext base = Context.current().withValue(FOOD, "fish").withCancellation();
|
Context.CancellableContext base = Context.current().withValue(FOOD, "fish").withCancellation();
|
||||||
|
|
||||||
base.attach();
|
Context previous = base.attach();
|
||||||
Context baseAttached = Context.current();
|
Context baseAttached = Context.current();
|
||||||
// Should not be able to get back the CancellableContext
|
// Should not be able to get back the CancellableContext
|
||||||
assertNotSame(baseAttached, base);
|
assertNotSame(baseAttached, base);
|
||||||
base.detach();
|
base.detach(previous);
|
||||||
|
|
||||||
Closeable c = base.attachAsCloseable();
|
Closeable c = base.attachAsCloseable();
|
||||||
assertEquals("fish", FOOD.get());
|
assertEquals("fish", FOOD.get());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue