Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2018,2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.context.tck;
import java.lang.annotation.Annotation;
import java.io.CharConversionException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Phaser;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.eclipse.microprofile.context.tck.contexts.buffer.Buffer;
import org.eclipse.microprofile.context.tck.contexts.buffer.spi.BufferContextProvider;
import org.eclipse.microprofile.context.tck.contexts.label.Label;
import org.eclipse.microprofile.context.tck.contexts.label.spi.LabelContextProvider;
import org.eclipse.microprofile.context.tck.contexts.priority.spi.ThreadPriorityContextProvider;
import org.eclipse.microprofile.context.ManagedExecutor;
import org.eclipse.microprofile.context.ThreadContext;
import org.eclipse.microprofile.context.spi.ThreadContextProvider;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.Assert;
import org.testng.ITestResult;
import org.testng.annotations.Test;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
public class ManagedExecutorTest extends Arquillian {
/**
* Maximum tolerated wait for an asynchronous operation to complete.
* This is important to ensure that tests don't hang waiting for asynchronous operations to complete.
* Normally these sort of operations will complete in tiny fractions of a second, but we are specifying
* an extremely generous value here to allow for the widest possible variety of test execution environments.
*/
private static final long MAX_WAIT_NS = TimeUnit.MINUTES.toNanos(2);
/**
* Pool of unmanaged threads (not context-aware) that can be used by tests.
*/
private ExecutorService unmanagedThreads;
@AfterClass
public void after() {
unmanagedThreads.shutdownNow();
}
@AfterMethod
public void afterMethod(Method m, ITestResult result) {
System.out.println("<<< END " + m.getClass().getSimpleName() + '.' + m.getName() + (result.isSuccess() ? " SUCCESS" : " FAILED"));
Throwable failure = result.getThrowable();
if (failure != null) {
failure.printStackTrace(System.out);
}
}
@BeforeClass
public void before() {
unmanagedThreads = Executors.newFixedThreadPool(5);
}
@BeforeMethod
public void beforeMethod(Method m) {
System.out.println(">>> BEGIN " + m.getClass().getSimpleName() + '.' + m.getName());
}
@Deployment
public static WebArchive createDeployment() {
// build a JAR that provides three fake context types: 'Buffer', 'Label', and 'ThreadPriority'
JavaArchive fakeContextProviders = ShrinkWrap.create(JavaArchive.class, "fakeContextTypes.jar")
.addPackages(true, "org.eclipse.microprofile.context.tck.contexts.buffer")
.addPackages(true, "org.eclipse.microprofile.context.tck.contexts.label")
.addPackage("org.eclipse.microprofile.context.tck.contexts.priority.spi")
.addAsServiceProvider(ThreadContextProvider.class,
BufferContextProvider.class, LabelContextProvider.class, ThreadPriorityContextProvider.class);
return ShrinkWrap.create(WebArchive.class, ManagedExecutorTest.class.getSimpleName() + ".war")
.addClass(ManagedExecutorTest.class)
.addAsLibraries(fakeContextProviders);
}
@Test
public void builderForManagedExecutorIsProvided() {
Assert.assertNotNull(ManagedExecutor.builder(),
"MicroProfile Context Propagation implementation does not provide a ManagedExecutor builder.");
}
/**
* Verify that the ManagedExecutor implementation clears context
* types that are not configured under propagated, or cleared.
*
* @throws TimeoutException indicates test failure
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
*/
@Test
public void clearUnspecifiedContexts() throws InterruptedException, ExecutionException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.build();
int originalPriority = Thread.currentThread().getPriority();
try {
// Set non-default values
int newPriority = originalPriority == 3 ? 2 : 3;
Thread.currentThread().setPriority(newPriority);
Buffer.set(new StringBuffer("clearUnspecifiedContexts-test-buffer-A"));
Future future = executor.completedFuture(1).thenRun(() -> {
Assert.assertEquals(Buffer.get().toString(), "clearUnspecifiedContexts-test-buffer-A",
"Context type was not propagated to contextual action.");
Buffer.set(new StringBuffer("clearUnspecifiedContexts-test-buffer-B"));
Assert.assertEquals(Thread.currentThread().getPriority(), Thread.NORM_PRIORITY,
"Context type that remained unspecified was not cleared by default.");
});
Assert.assertNull(future.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Non-null value returned by stage that runs Runnable.");
Assert.assertEquals(Buffer.get().toString(), "clearUnspecifiedContexts-test-buffer-A",
"Previous context (Buffer) was not restored after context was propagated for contextual action.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Thread.currentThread().setPriority(originalPriority);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all dependent stages of the completed future that is created
* by the ManagedExecutor's completedFuture implementation. Thread context is captured
* at each point where a dependent stage is added, rather than solely upon creation of the
* initial stage or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void completedFutureDependentStagesRunWithContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("completedFuture-test-buffer-A"));
Label.set("completedFuture-test-label");
CompletableFuture stage1a = executor.completedFuture(1000L);
Assert.assertTrue(stage1a.isDone(),
"Future created by completedFuture is not complete.");
Assert.assertFalse(stage1a.isCompletedExceptionally(),
"Future created by completedFuture reports exceptional completion.");
Assert.assertEquals(stage1a.getNow(1234L), Long.valueOf(1000L),
"Future created by completedFuture has result that differs from what was specified.");
// The following incomplete future blocks subsequent stages from running inline on the current thread
CompletableFuture stage1b = new CompletableFuture();
Buffer.set(new StringBuffer("completedFuture-test-buffer-B"));
CompletableFuture stage2 = stage1a.thenCombine(stage1b, (a, b) -> {
Assert.assertEquals(a, Long.valueOf(1000L),
"First value supplied to BiFunction was lost or altered.");
Assert.assertEquals(b, Long.valueOf(3L),
"Second value supplied to BiFunction was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "completedFuture-test-buffer-B",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
return a * b;
});
Buffer.set(new StringBuffer("completedFuture-test-buffer-C"));
CompletableFuture stage3 = stage2.thenApply(i -> {
Assert.assertEquals(i, Long.valueOf(3000L),
"Value supplied to third stage was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "completedFuture-test-buffer-C",
"Context type was not propagated to contextual action.");
// This stage runs inline on the same thread as the test, so alter the
// context here and later verify that the MicroProfile Context Propagation implementation
// properly restores it to the thread's previous value, which will be
// completedFuture-test-buffer-E at the point when this runs.
Buffer.set(new StringBuffer("completedFuture-test-buffer-D"));
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
return i - 300;
});
Buffer.set(new StringBuffer("completedFuture-test-buffer-E"));
// Complete stage 1b, allowing stage 2 and then 3 to run
stage1b.complete(3L);
Assert.assertEquals(stage3.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Long.valueOf(2700L),
"Unexpected result for stage 3.");
Assert.assertEquals(stage2.getNow(3333L), Long.valueOf(3000L),
"Unexpected or missing result for stage 2.");
Assert.assertTrue(stage2.isDone(), "Second stage did not transition to done upon completion.");
Assert.assertTrue(stage3.isDone(), "Third stage did not transition to done upon completion.");
Assert.assertFalse(stage2.isCompletedExceptionally(), "Second stage should not report exceptional completion.");
Assert.assertFalse(stage3.isCompletedExceptionally(), "Third stage should not report exceptional completion.");
// Is context properly restored on current thread?
Assert.assertEquals(Buffer.get().toString(), "completedFuture-test-buffer-E",
"Previous context was not restored after context was cleared for managed executor tasks.");
Assert.assertEquals(Label.get(), "completedFuture-test-label",
"Previous context was not restored after context was propagated for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all dependent stages of the completed future that is created
* by the ManagedExecutor's completedStage implementation. Thread context is captured
* at each point where a dependent stage is added, rather than solely upon creation of the
* initial stage or construction of the builder.
*
* @throws InterruptedException indicates test failure
*/
@Test
public void completedStageDependentStagesRunWithContext() throws InterruptedException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("completedStage-test-buffer"));
Label.set("completedStage-test-label-A");
CompletionStage stage1 = executor.completedStage("5A");
// The following incomplete future prevents subsequent stages from completing
CompletableFuture stage2 = new CompletableFuture();
Label.set("completedStage-test-label-B");
CompletionStage stage3 = stage1.thenCompose(s -> {
Assert.assertEquals(s, "5A",
"Value supplied to compose function was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "completedStage-test-label-B",
"Context type was not propagated to contextual action.");
return stage2.thenApply(i -> i + Integer.parseInt(s, 16));
});
Label.set("completedStage-test-label-C");
CompletionStage stage4 = stage3.applyToEither(new CompletableFuture(), i -> {
Assert.assertEquals(i, Integer.valueOf(99),
"Value supplied to function was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "completedStage-test-label-C",
"Context type was not propagated to contextual action.");
return i + 1;
});
Label.set("completedStage-test-label-D");
CountDownLatch completed = new CountDownLatch(1);
AtomicInteger resultRef = new AtomicInteger();
stage4.whenComplete((result, failure) -> {
resultRef.set(result);
completed.countDown();
});
// allow stages 3 and 4 to complete
stage2.complete(9);
Assert.assertTrue(completed.await(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Completion stage did not finish in a reasonable amount of time.");
Assert.assertEquals(resultRef.get(), 100,
"Unexpected result for stage 4.");
// Is context properly restored on current thread?
Assert.assertEquals(Buffer.get().toString(), "completedStage-test-buffer",
"Previous context was not restored after context was cleared for managed executor tasks.");
Assert.assertEquals(Label.get(), "completedStage-test-label-D",
"Previous context was not restored after context was propagated for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify the MicroProfile Context Propagation implementation of propagate(), and cleared()
* for ManagedExecutor.Builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void contextControlsForManagedExecutorBuilder() throws InterruptedException, ExecutionException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(Label.CONTEXT_NAME)
.maxAsync(-1)
.maxQueued(-1)
.build();
try {
ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(Label.CONTEXT_NAME, Buffer.CONTEXT_NAME)
.maxAsync(-1)
.maxQueued(-1)
.build();
Assert.fail("ManagedExecutor.Builder.build() should throw an IllegalStateException for set overlap between propagated and cleared");
}
catch (IllegalStateException ISE) {
// test passes
}
try {
ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME, "BOGUS_CONTEXT")
.cleared(Label.CONTEXT_NAME)
.maxAsync(-1)
.maxQueued(-1)
.build();
Assert.fail("ManagedExecutor.Builder.build() should throw an IllegalStateException for a nonexistent thread context type");
}
catch (IllegalStateException ISE) {
// test passes
}
try {
// Set non-default values
Buffer.get().append("contextControls-test-buffer-A");
Label.set("contextControls-test-label-A");
Future future = executor.submit(() -> {
Assert.assertEquals(Buffer.get().toString(), "contextControls-test-buffer-A",
"Context type was not propagated to contextual action.");
Buffer.get().append("-B");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("contextControls-test-label-B");
return null;
});
Assert.assertNull(future.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Unexpected result of task.");
Assert.assertEquals(Buffer.get().toString(), "contextControls-test-buffer-A-B",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "contextControls-test-label-A",
"Context type was not left unchanged by contextual action.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* When an already-contextualized Callable is specified as the action/task,
* the action/task runs with its already-captured context rather than
* capturing and applying context per the configuration of the managed executor.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void contextOfContextualCallableOverridesContextOfManagedExecutor() throws ExecutionException, InterruptedException, TimeoutException {
ThreadContext bufferContext = ThreadContext.builder()
.propagated(Buffer.CONTEXT_NAME)
.unchanged()
.cleared(ThreadContext.ALL_REMAINING)
.build();
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Callable getBuffer = () -> {
Assert.assertEquals(Label.get(), "",
"Context type not cleared from thread.");
return Buffer.get().toString();
};
Callable getLabel = () -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
return Label.get();
};
Buffer.set(new StringBuffer("contextualCallableOverride-buffer-1"));
Label.set("contextualCallableOverride-label-1");
Callable precontextualizedTask1 = bufferContext.contextualCallable(getBuffer);
Buffer.set(new StringBuffer("contextualCallableOverride-buffer-2"));
Label.set("contextualCallableOverride-label-2");
Callable precontextualizedTask2 = bufferContext.contextualCallable(getBuffer);
Buffer.set(new StringBuffer("contextualCallableOverride-buffer-3"));
Label.set("contextualCallableOverride-label-3");
Future future = executor.submit(precontextualizedTask1);
Assert.assertEquals(future.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), "contextualCallableOverride-buffer-1",
"Previously captured context type not found on thread.");
List> futures = executor.invokeAll(
Arrays.asList(precontextualizedTask2, getLabel, precontextualizedTask1, precontextualizedTask2),
MAX_WAIT_NS,
TimeUnit.NANOSECONDS);
future = futures.get(0);
Assert.assertEquals(future.get(), "contextualCallableOverride-buffer-2",
"Previously captured context type not found on thread.");
future = futures.get(1);
Assert.assertEquals(future.get(), "contextualCallableOverride-label-3",
"Context type captured by managed executor not found on thread.");
future = futures.get(2);
Assert.assertEquals(future.get(), "contextualCallableOverride-buffer-1",
"Previously captured context type not found on thread.");
future = futures.get(3);
Assert.assertEquals(future.get(), "contextualCallableOverride-buffer-2",
"Previously captured context type not found on thread.");
String result = executor.invokeAny(
Arrays.asList(precontextualizedTask1, precontextualizedTask1),
MAX_WAIT_NS,
TimeUnit.NANOSECONDS);
Assert.assertEquals(result, "contextualCallableOverride-buffer-1",
"Previously captured context type not found on thread.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* When an already-contextualized Consumer or BiFunction is specified as the action/task,
* the action/task runs with its already-captured context rather than
* capturing and applying context per the configuration of the managed executor.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void contextOfContextualConsumerAndBiFunctionOverrideContextOfManagedExecutor()
throws ExecutionException, InterruptedException, TimeoutException {
ThreadContext labelContext = ThreadContext.builder()
.propagated(Label.CONTEXT_NAME)
.unchanged()
.cleared(ThreadContext.ALL_REMAINING)
.build();
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Buffer.set(new StringBuffer("contextualBiFunctionOverride-buffer-1"));
Label.set("contextualBiFunctionOverride-label-1");
BiFunction precontextualizedFunction1 = labelContext.contextualFunction((result, failure) -> {
Assert.assertEquals(Label.get(), "contextualBiFunctionOverride-label-1",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
return failure == null ? result : 100;
});
Buffer.set(new StringBuffer("contextualBiFunctionOverride-buffer-2"));
Label.set("contextualBiFunctionOverride-label-2");
BiFunction precontextualizedFunction2 = labelContext.contextualFunction((i, j) -> {
Assert.assertEquals(Label.get(), "contextualBiFunctionOverride-label-2",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
return i - j;
});
Buffer.set(new StringBuffer("contextualConsumerOverride-buffer-3"));
Label.set("contextualConsumerOverride-label-3");
Consumer precontextualizedConsumer3 = labelContext.contextualConsumer(i -> {
Assert.assertEquals(Label.get(), "contextualConsumerOverride-label-3",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
});
Buffer.set(new StringBuffer("contextualConsuemrOverride-buffer-4"));
Label.set("contextualConsumerOverride-label-4");
Consumer precontextualizedConsumer4 = labelContext.contextualConsumer(i -> {
Assert.assertEquals(Label.get(), "contextualConsumerOverride-label-4",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
});
BiFunction normalFunction5 = (unused1, unused2) -> {
Assert.assertEquals(Buffer.get().toString(), "contextualConsumerAndBiFunctionOverride-buffer-5",
"Previously captured context type not found on thread.");
Assert.assertEquals(Label.get(), "",
"Context type not cleared from thread.");
return "done";
};
Buffer.set(new StringBuffer("contextualConsumerAndBiFunctionOverride-buffer-5"));
Label.set("contextualConsumerAndBiFunctionOverride-label-5");
CompletableFuture stage0 = executor.failedFuture(new ArrayIndexOutOfBoundsException("Expected error."));
CompletableFuture stage1 = stage0.handleAsync(precontextualizedFunction1);
CompletableFuture stage2 = executor.completedFuture(200).thenCombineAsync(stage1, precontextualizedFunction2);
CompletableFuture stage3 = stage2.thenAccept(precontextualizedConsumer3);
CompletableFuture stage4 = stage2.acceptEitherAsync(stage1, precontextualizedConsumer4);
CompletableFuture stage5 = stage4.thenCombine(stage3, normalFunction5);
Assert.assertEquals(stage5.join(), "done",
"Unexpected result for completion stage.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* When an already-contextualized Function is specified as the action/task,
* the action/task runs with its already-captured context rather than
* capturing and applying context per the configuration of the managed executor.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void contextOfContextualFunctionOverridesContextOfManagedExecutor() throws ExecutionException, InterruptedException, TimeoutException {
ThreadContext labelContext = ThreadContext.builder()
.propagated(Label.CONTEXT_NAME)
.unchanged()
.cleared(ThreadContext.ALL_REMAINING)
.build();
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Buffer.set(new StringBuffer("contextualFunctionOverride-buffer-1"));
Label.set("contextualFunctionOverride-label-1");
Function precontextualizedFunction1 = labelContext.contextualFunction(i -> {
Assert.assertEquals(Label.get(), "contextualFunctionOverride-label-1",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
return i + 1;
});
Buffer.set(new StringBuffer("contextualFunctionOverride-buffer-2"));
Label.set("contextualFunctionOverride-label-2");
Function precontextualizedFunction2 = labelContext.contextualFunction(i -> {
Assert.assertEquals(Label.get(), "contextualFunctionOverride-label-2",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
return i + 20;
});
Function precontextualizedErrorHandler = labelContext.contextualFunction(failure -> {
Assert.assertEquals(Label.get(), "contextualFunctionOverride-label-2",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
return -1;
});
Buffer.set(new StringBuffer("contextualFunctionOverride-buffer-3"));
Label.set("contextualFunctionOverride-label-3");
Function normalFunction = i -> {
Assert.assertEquals(Buffer.get().toString(), "contextualFunctionOverride-buffer-3",
"Previously captured context type not found on thread.");
Assert.assertEquals(Label.get(), "",
"Context type not cleared from thread.");
return i + 300;
};
CompletableFuture stage0 = executor.newIncompleteFuture();
CompletableFuture stage1 = stage0.thenApplyAsync(precontextualizedFunction1);
Buffer.set(new StringBuffer("contextualFunctionOverride-buffer-4"));
Label.set("contextualFunctionOverride-label-4");
Function> precontextualizedFunction4 = labelContext.contextualFunction(i -> {
Assert.assertEquals(Label.get(), "contextualFunctionOverride-label-4",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
return stage1;
});
Buffer.set(new StringBuffer("contextualFunctionOverride-buffer-3"));
Label.set("contextualFunctionOverride-label-3");
CompletableFuture stage2 = stage0.thenComposeAsync(precontextualizedFunction4);
CompletableFuture stage3 = stage2.applyToEither(stage1, precontextualizedFunction2);
CompletableFuture stage4 = stage3.thenApply(normalFunction);
CompletableFuture stage5 = stage4.thenApply(i -> i / (i - 321)) // intentional ArithmeticException for division by 0
.exceptionally(precontextualizedErrorHandler);
stage0.complete(0);
Assert.assertEquals(stage2.join(), Integer.valueOf(1),
"Unexpected result for completion stage.");
Assert.assertEquals(stage5.join(), Integer.valueOf(-1),
"Unexpected result for completion stage.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* When an already-contextualized Runnable is specified as the action/task,
* the action/task runs with its already-captured context rather than
* capturing and applying context per the configuration of the managed executor.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void contextOfContextualRunnableOverridesContextOfManagedExecutor() throws ExecutionException, InterruptedException, TimeoutException {
ThreadContext labelContext = ThreadContext.builder()
.propagated(Label.CONTEXT_NAME)
.unchanged()
.cleared(ThreadContext.ALL_REMAINING)
.build();
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Buffer.set(new StringBuffer("contextualRunnableOverride-buffer-1"));
Label.set("contextualRunnableOverride-label-1");
Runnable precontextualizedTask1 = labelContext.contextualRunnable(() -> {
Assert.assertEquals(Label.get(), "contextualRunnableOverride-label-1",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
});
Buffer.set(new StringBuffer("contextualRunnableOverride-buffer-2"));
Label.set("contextualRunnableOverride-label-2");
Runnable precontextualizedTask2 = labelContext.contextualRunnable(() -> {
Assert.assertEquals(Label.get(), "contextualRunnableOverride-label-2",
"Previously captured context type not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
});
Buffer.set(new StringBuffer("contextualRunnableOverride-buffer-3"));
Label.set("contextualRunnableOverride-label-3");
Runnable normalTask = () -> {
Assert.assertEquals(Buffer.get().toString(), "contextualRunnableOverride-buffer-3",
"Previously captured context type not found on thread.");
Assert.assertEquals(Label.get(), "",
"Context type not cleared from thread.");
};
Future future = executor.submit(precontextualizedTask1, 1);
Assert.assertEquals(future.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Integer.valueOf(1),
"Unexpected result of task.");
CompletableFuture stage0 = executor.runAsync(precontextualizedTask1);
CompletableFuture stage1 = stage0.thenRunAsync(precontextualizedTask1);
CompletableFuture stage2 = stage0.thenRun(precontextualizedTask2);
CompletableFuture stage3 = stage1.runAfterEither(stage2, precontextualizedTask2);
CompletableFuture stage4 = stage1.runAfterBothAsync(stage2, precontextualizedTask1);
CompletableFuture stage5 = stage4.runAfterBoth(stage3, normalTask);
stage5.join();
LinkedBlockingQueue results = new LinkedBlockingQueue();
Runnable precontextualizedTask3 = labelContext.contextualRunnable(() -> results.add(Label.get()));
Buffer.set(new StringBuffer("contextualRunnableOverride-buffer-4"));
Label.set("contextualRunnableOverride-label-4");
executor.execute(precontextualizedTask3);
Assert.assertEquals(results.poll(MAX_WAIT_NS, TimeUnit.NANOSECONDS), "contextualRunnableOverride-label-3",
"Previously captured context type not found on thread.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* When an already-contextualized Supplier or BiFunction is specified as the action/task,
* the action/task runs with its already-captured context rather than
* capturing and applying context per the configuration of the managed executor.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void contextOfContextualSuppplierAndBiConsumerOverrideContextOfManagedExecutor()
throws ExecutionException, InterruptedException, TimeoutException {
ThreadContext bufferContext = ThreadContext.builder()
.propagated(Buffer.CONTEXT_NAME)
.unchanged()
.cleared(ThreadContext.ALL_REMAINING)
.build();
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Supplier getBuffer = () -> {
Assert.assertEquals(Label.get(), "",
"Context type not cleared from thread.");
return Buffer.get().toString();
};
Buffer.set(new StringBuffer("contextualSupplierOverride-buffer-1"));
Label.set("contextualSupplierOverride-label-1");
Supplier precontextualizedSupplier1 = bufferContext.contextualSupplier(getBuffer);
Buffer.set(new StringBuffer("contextualSupplierOverride-buffer-2"));
Label.set("contextualSupplierOverride-label-2");
Supplier precontextualizedSupplier2 = bufferContext.contextualSupplier(getBuffer);
Buffer.set(new StringBuffer("contextualBiConsumerOverride-buffer-3"));
Label.set("contextualBiConsumerOverride-label-3");
BiConsumer precontextualizedConsumer3 = bufferContext.contextualConsumer((b1, b2) -> {
Assert.assertEquals(Buffer.get().toString(), "contextualBiConsumerOverride-buffer-3",
"Previously captured context type not found on thread.");
Assert.assertEquals(Label.get(), "",
"Context type not cleared from thread.");
Assert.assertEquals(b1, "contextualSupplierOverride-buffer-1",
"Previously captured context type not found on Supplier's thread.");
Assert.assertEquals(b2, "contextualSupplierOverride-buffer-2",
"Previously captured context type not found on Supplier's thread.");
});
Buffer.set(new StringBuffer("contextualBiConsumerOverride-buffer-4"));
Label.set("contextualBiConsumerOverride-label-4");
BiConsumer precontextualizedConsumer4 = bufferContext.contextualConsumer((unused, failure) -> {
Assert.assertEquals(Buffer.get().toString(), "contextualBiConsumerOverride-buffer-4",
"Previously captured context type not found on thread.");
Assert.assertEquals(Label.get(), "",
"Context type not cleared from thread.");
});
Buffer.set(new StringBuffer("contextualSupplierAndBiConsumerOverride-buffer-5"));
Label.set("contextualSupplierAndBiConsumerOverride-label-5");
CompletableFuture stage1 = executor.supplyAsync(precontextualizedSupplier1);
CompletableFuture stage2 = executor.supplyAsync(precontextualizedSupplier2);
CompletableFuture stage3 = stage1.thenAcceptBoth(stage2, precontextualizedConsumer3);
CompletableFuture stage4 = stage3.whenCompleteAsync(precontextualizedConsumer4);
CompletableFuture stage5 = stage4.whenComplete((unused, failure) -> {
Assert.assertEquals(Label.get(), "contextualSupplierAndBiConsumerOverride-label-5",
"Context type captured by managed executor not found on thread.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type not cleared from thread.");
});
stage5.join();
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is cleared per the configuration of the ManagedExecutor builder
* for all tasks that are executed via the execute method. This test supplies the ManagedExecutor
* to a Java SE CompletableFuture, which invokes the execute method to run tasks asynchronously.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void executedTaskRunsWithClearedContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated()
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Buffer.set(new StringBuffer("executed-task-test-buffer-A"));
Label.set("executed-task-test-label-A");
CompletableFuture cf1 = new CompletableFuture();
CompletableFuture cf2 = cf1.thenAcceptAsync(i -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("executed-task-test-label-B");
}, executor);
cf1.complete(1000);
cf2.join();
Assert.assertEquals(Buffer.get().toString(), "executed-task-test-buffer-A",
"Context unexpectedly changed on thread.");
Assert.assertEquals(Label.get(), "executed-task-test-label-A",
"Context unexpectedly changed on thread.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is propagated per the configuration of the ManagedExecutor builder
* for all tasks that are executed via the execute method.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void executedTaskRunsWithContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME, Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Buffer.set(new StringBuffer("executed-task-test-buffer-C"));
Label.set("executed-task-test-label-C");
CompletableFuture result = new CompletableFuture();
executor.execute(() -> {
try {
Assert.assertEquals(Buffer.get().toString(), "executed-task-test-buffer-C",
"Context type that is configured to be propagated was not propagated.");
Assert.assertEquals(Label.get(), "executed-task-test-label-C",
"Context type that is configured to be propagated was not propagated.");
Label.set("executed-task-test-label-D");
result.complete("successful");
}
catch (Throwable x) {
result.completeExceptionally(x);
}
});
// Force exception to be raised, if any
result.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.assertEquals(Buffer.get().toString(), "executed-task-test-buffer-C",
"Context unexpectedly changed on thread.");
Assert.assertEquals(Label.get(), "executed-task-test-label-C",
"Context unexpectedly changed on thread.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all dependent stages of the completed future that is created
* by the ManagedExecutor's failedFuture implementation. Thread context is captured
* at each point where a dependent stage is added, rather than solely upon creation of the
* initial stage or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void failedFutureDependentStagesRunWithContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("failedFuture-test-buffer-1"));
Label.set("failedFuture-test-label");
CompletableFuture stage1 = executor.failedFuture(new CharConversionException("A fake exception created by the test"));
Assert.assertTrue(stage1.isDone(),
"Future created by failedFuture is not complete.");
Assert.assertTrue(stage1.isCompletedExceptionally(),
"Future created by failedFuture does not report exceptional completion.");
try {
Character result = stage1.getNow('1');
Assert.fail("Failed future must raise exception. Instead, getNow returned: " + result);
}
catch (CompletionException x) {
if (x.getCause() == null || !(x.getCause() instanceof CharConversionException)
|| !"A fake exception created by the test".equals(x.getCause().getMessage())) {
throw x;
}
}
Buffer.set(new StringBuffer("failedFuture-test-buffer-B"));
CompletableFuture stage2a = stage1.exceptionally(x -> {
Assert.assertEquals(x.getClass(), CharConversionException.class,
"Wrong exception class supplied to 'exceptionally' method.");
Assert.assertEquals(x.getMessage(), "A fake exception created by the test",
"Exception message was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "failedFuture-test-buffer-B",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
return 'A';
});
// The following incomplete future blocks subsequent stages from running inline on the current thread
CompletableFuture stage2b = new CompletableFuture();
Buffer.set(new StringBuffer("failedFuture-test-buffer-C"));
AtomicBoolean stage3Runs = new AtomicBoolean();
CompletableFuture stage3 = stage2a.runAfterBoth(stage2b, () -> {
stage3Runs.set(true);
Assert.assertEquals(Buffer.get().toString(), "failedFuture-test-buffer-C",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
});
Buffer.set(new StringBuffer("failedFuture-test-buffer-D"));
Assert.assertFalse(stage3.isDone(),
"Third stage should not report done until both of the stages upon which it depends complete.");
// Complete stage 2b, allowing stage 3 to run
stage2b.complete('B');
Assert.assertNull(stage3.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Unexpected result for stage 3.");
Assert.assertTrue(stage3Runs.get(),
"The Runnable for stage 3 did not run.");
Assert.assertEquals(stage2a.getNow('F'), Character.valueOf('A'),
"Unexpected or missing result for stage 2.");
Assert.assertTrue(stage2a.isDone(), "Second stage did not transition to done upon completion.");
Assert.assertTrue(stage3.isDone(), "Third stage did not transition to done upon completion.");
Assert.assertFalse(stage2a.isCompletedExceptionally(), "Second stage should not report exceptional completion.");
Assert.assertFalse(stage3.isCompletedExceptionally(), "Third stage should not report exceptional completion.");
// Is context properly restored on current thread?
Assert.assertEquals(Buffer.get().toString(), "failedFuture-test-buffer-D",
"Previous context was not restored after context was cleared for managed executor tasks.");
Assert.assertEquals(Label.get(), "failedFuture-test-label",
"Previous context was not restored after context was propagated for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all dependent stages of the completed future that is created
* by the ManagedExecutor's failedStage implementation. Thread context is captured
* at each point where a dependent stage is added, rather than solely upon creation of the
* initial stage or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void failedStageDependentStagesRunWithContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("failedStage-test-buffer"));
Label.set("failedStage-test-label-A");
CompletionStage stage1 = executor.failedStage(new LinkageError("Error intentionally raised by test case"));
Label.set("failedStage-test-label-B");
CompletionStage stage2 = stage1.whenComplete((result, failure) -> {
Assert.assertEquals(failure.getClass(), LinkageError.class,
"Wrong exception class supplied to 'whenComplete' method.");
Assert.assertEquals(failure.getMessage(), "Error intentionally raised by test case",
"Error message was lost or altered.");
Assert.assertNull(result,
"Non-null result supplied to whenComplete for failed stage.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "failedStage-test-label-B",
"Context type was not propagated to contextual action.");
});
Label.set("failedStage-test-label-C");
CompletableFuture future1 = stage1.toCompletableFuture();
try {
Integer result = future1.join();
Assert.fail("The join operation did not raise the error from the failed stage. Instead: " + result);
}
catch (CompletionException x) {
if (x.getCause() == null || !(x.getCause() instanceof LinkageError)
|| !"Error intentionally raised by test case".equals(x.getCause().getMessage())) {
throw x;
}
}
CompletableFuture future2 = stage2.toCompletableFuture();
try {
Integer result = future2.get();
Assert.fail("The get operation did not raise the error from the failed stage. Instead: " + result);
}
catch (ExecutionException x) {
if (x.getCause() == null || !(x.getCause() instanceof LinkageError)
|| !"Error intentionally raised by test case".equals(x.getCause().getMessage())) {
throw x;
}
}
Assert.assertEquals(Buffer.get().toString(), "failedStage-test-buffer",
"Previous context was not restored after context was cleared for managed executor tasks.");
Assert.assertEquals(Label.get(), "failedStage-test-label-C",
"Previous context was not restored after context was propagated for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that the ManagedExecutor implementation starts 2 async tasks/actions, and no more,
* when maxAsync is configured to 2.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void maxAsync2() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(2)
.propagated()
.cleared(ThreadContext.ALL_REMAINING)
.build();
Phaser barrier = new Phaser(2);
try {
// Use up both maxAsync slots on blocking operations and wait for them to start
Future future1 = executor.submit(() -> barrier.awaitAdvance(barrier.arriveAndAwaitAdvance()));
CompletableFuture future2 = executor.supplyAsync(() -> barrier.awaitAdvance(barrier.arriveAndAwaitAdvance()));
barrier.awaitAdvanceInterruptibly(0, MAX_WAIT_NS, TimeUnit.NANOSECONDS);
// This data structure holds the results of tasks which shouldn't be able to run yet
LinkedBlockingQueue results = new LinkedBlockingQueue();
// Submit additional tasks/actions for async execution.
// These should queue, but otherwise be unable to start yet due to maxAsync=2.
CompletableFuture future3 = executor.runAsync(() -> results.offer("Result3"));
CompletableFuture future4 = executor.supplyAsync(() -> results.offer("Result4"));
Future future5 = executor.submit(() -> results.offer("Result5"));
CompletableFuture future6 = executor.completedFuture("6")
.thenApplyAsync(s -> results.offer("Result" + s));
// Detect whether any of the above tasks/actions run within the next 5 seconds
Assert.assertNull(results.poll(5, TimeUnit.SECONDS),
"Should not be able start more than 2 async tasks when maxAsync is 2.");
// unblock and allow tasks to finish
barrier.arrive();
barrier.arrive(); // there are 2 parties in each phase
Assert.assertNotNull(results.poll(MAX_WAIT_NS, TimeUnit.SECONDS), "None of the queued tasks ran.");
Assert.assertNotNull(results.poll(MAX_WAIT_NS, TimeUnit.SECONDS), "Only 1 of the queued tasks ran.");
Assert.assertNotNull(results.poll(MAX_WAIT_NS, TimeUnit.SECONDS), "Only 2 of the queued tasks ran.");
Assert.assertNotNull(results.poll(MAX_WAIT_NS, TimeUnit.SECONDS), "Only 3 of the queued tasks ran.");
Assert.assertEquals(future1.get(), Integer.valueOf(2), "Unexpected result of first task.");
Assert.assertEquals(future2.get(), Integer.valueOf(2), "Unexpected result of second task.");
Assert.assertNull(future3.join(), "Unexpected result of third task.");
Assert.assertEquals(future4.join(), Boolean.TRUE, "Unexpected result of fourth task.");
Assert.assertEquals(future5.get(), Boolean.TRUE, "Unexpected result of fifth task.");
Assert.assertEquals(future6.get(), Boolean.TRUE, "Unexpected result of sixth task.");
}
finally {
barrier.forceTermination();
executor.shutdownNow();
}
}
/**
* Attempt to specify invalid values (less than -1 and 0) for maxAsync.
* Require this to be rejected upon the maxQueued operation per JavaDoc
* rather than from the build method.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void maxAsyncInvalidValues() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor.Builder builder = ManagedExecutor.builder();
builder.propagated(ThreadContext.ALL_REMAINING);
builder.cleared(ThreadContext.TRANSACTION);
try {
builder.maxAsync(-10);
Assert.fail("ManagedExecutor builder permitted value of -10 for maxAsync.");
}
catch (IllegalArgumentException x) {
// test passes
}
try {
builder.maxAsync(-2);
Assert.fail("ManagedExecutor builder permitted value of -2 for maxAsync.");
}
catch (IllegalArgumentException x) {
// test passes
}
try {
builder.maxQueued(0);
Assert.fail("ManagedExecutor builder permitted value of 0 for maxAsync.");
}
catch (IllegalArgumentException x) {
// test passes
}
// builder remains usable
ManagedExecutor executor = builder.build();
try {
// neither of the invalid values apply - can run a task
Future future = executor.submit(() -> "it worked!");
Assert.assertEquals(future.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), "it worked!",
"Task had missing or unexpected result.");
}
finally {
executor.shutdownNow();
}
}
/**
* Verify that 3 tasks/actions, and no more, can be queued when maxQueued is configured to 3.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void maxQueued3() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(1)
.maxQueued(3)
.propagated()
.cleared(ThreadContext.ALL_REMAINING)
.build();
Phaser barrier = new Phaser(1);
try {
// First, use up the single maxAsync slot with a blocking task and wait for it to start
executor.submit(() -> barrier.awaitAdvanceInterruptibly(barrier.arrive() + 1));
barrier.awaitAdvanceInterruptibly(0, MAX_WAIT_NS, TimeUnit.NANOSECONDS);
// Use up first queue position
Future future1 = executor.submit(() -> 101);
// Use up second queue position
CompletableFuture future2 = executor.runAsync(() -> System.out.println("second task running"));
// Use up third queue position
Future future3 = executor.submit(() -> 103);
// Fourth attempt to queue a task must be rejected
try {
Future future4 = executor.submit(() -> 104);
Assert.fail("Exceeded maxQueued of 3. Future for 4th queued task/action is " + future4);
}
catch (RejectedExecutionException x) {
// test passes
}
// Fifth attempt to queue a task must also be rejected
try {
CompletableFuture future5 = executor.supplyAsync(() -> 105);
Assert.fail("Exceeded maxQueued of 3. Future for 5th queued task/action is " + future5);
}
catch (RejectedExecutionException x) {
// test passes
}
// unblock and allow tasks to finish
barrier.arrive();
Assert.assertEquals(future1.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Integer.valueOf(101),
"Unexpected result of first task.");
Assert.assertNull(future2.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Unexpected result of second task.");
// At least 2 queue positions must be available at this point
Future future6 = executor.submit(() -> 106);
CompletableFuture future7 = executor.supplyAsync(() -> 107);
Assert.assertEquals(future3.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Integer.valueOf(103),
"Unexpected result of third task.");
Assert.assertEquals(future6.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Integer.valueOf(106),
"Unexpected result of sixth task.");
Assert.assertEquals(future7.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Integer.valueOf(107),
"Unexpected result of seventh task.");
}
finally {
barrier.forceTermination();
executor.shutdownNow();
}
}
/**
* Attempt to specify invalid values (less than -1 and 0) for maxQueued.
* Require this to be rejected upon the maxQueued operation per JavaDoc
* rather than from the build method.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void maxQueuedInvalidValues() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor.Builder builder = ManagedExecutor.builder()
.propagated()
.cleared(ThreadContext.ALL_REMAINING);
try {
builder.maxQueued(-2);
Assert.fail("ManagedExecutor builder permitted value of -2 for maxQueued.");
}
catch (IllegalArgumentException x) {
// test passes
}
try {
builder.maxQueued(0);
Assert.fail("ManagedExecutor builder permitted value of 0 for maxQueued.");
}
catch (IllegalArgumentException x) {
// test passes
}
// builder remains usable
ManagedExecutor executor = builder.build();
try {
// neither of the invalid values apply - can queue a task and run it
Future future = executor.submit(() -> "successful!");
Assert.assertEquals(future.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), "successful!",
"Task had missing or unexpected result.");
}
finally {
executor.shutdownNow();
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all dependent stages of the incomplete future that is created
* by the ManagedExecutor's newIncompleteFuture implementation. Thread context is captured
* at each point where a dependent stage is added, rather than solely upon creation of the
* initial stage or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
*/
@Test
public void newIncompleteFutureDependentStagesRunWithContext() throws ExecutionException, InterruptedException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
CompletableFuture stage1 = executor.newIncompleteFuture();
Assert.assertFalse(stage1.isDone(),
"Completable future created by newIncompleteFuture did not start out as incomplete.");
// Set non-default values
Buffer.get().append("newIncompleteFuture-test-buffer");
Label.set("newIncompleteFuture-test-label-A");
CompletableFuture stage2 = stage1.thenApply(i -> {
Assert.assertEquals(i, Integer.valueOf(10),
"Value supplied to second stage was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "newIncompleteFuture-test-label-A",
"Context type was not correctly propagated to contextual action.");
return i * 2;
});
Label.set("newIncompleteFuture-test-label-B");
CompletableFuture stage3 = stage2.thenApply(i -> {
Assert.assertEquals(i, Integer.valueOf(20),
"Value supplied to third stage was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "newIncompleteFuture-test-label-B",
"Context type was not correctly propagated to contextual action.");
return i + 10;
});
Label.set("newIncompleteFuture-test-label-C");
// To avoid the possibility that CompletableFuture.get might cause the action to run
// on the current thread, which would bypass the intent of testing context propagation,
// use a countdown latch to independently wait for completion.
CountDownLatch completed = new CountDownLatch(1);
stage3.whenComplete((result, failure) -> completed.countDown());
Assert.assertTrue(stage1.complete(10),
"Unable to complete the future that was created by newIncompleteFuture.");
Assert.assertTrue(completed.await(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Completable future did not finish in a reasonable amount of time.");
Assert.assertTrue(stage1.isDone(), "First stage did not transition to done upon completion.");
Assert.assertTrue(stage2.isDone(), "Second stage did not transition to done upon completion.");
Assert.assertTrue(stage3.isDone(), "Third stage did not transition to done upon completion.");
Assert.assertEquals(stage1.get(), Integer.valueOf(10),
"Result of first stage does not match the value with which it was completed.");
Assert.assertEquals(stage2.getNow(22), Integer.valueOf(20),
"Result of second stage was lost or altered.");
Assert.assertEquals(stage3.join(), Integer.valueOf(30),
"Result of third stage was lost or altered.");
Assert.assertFalse(stage1.isCompletedExceptionally(), "First stage should not report exceptional completion.");
Assert.assertFalse(stage2.isCompletedExceptionally(), "Second stage should not report exceptional completion.");
Assert.assertFalse(stage3.isCompletedExceptionally(), "Third stage should not report exceptional completion.");
// Is context properly restored on current thread?
Assert.assertEquals(Buffer.get().toString(), "newIncompleteFuture-test-buffer",
"Previous context was not restored after context was cleared for managed executor tasks.");
Assert.assertEquals(Label.get(), "newIncompleteFuture-test-label-C",
"Previous context was not restored after context was propagated for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that Application context makes the application's thread context class loader available to the task.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void propagateApplicationContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor.Builder builder = ManagedExecutor.builder()
.propagated(ThreadContext.APPLICATION)
.cleared(ThreadContext.ALL_REMAINING);
ManagedExecutor executor;
try {
executor = builder.build();
}
catch (IllegalStateException x) {
return; // Skip test if Application context is not supported.
}
try {
CompletableFuture> cf = executor.supplyAsync(() -> {
try {
// load a class from the application
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return loader.loadClass("org.eclipse.microprofile.context.tck.contexts.label.Label");
}
catch (ClassNotFoundException x) {
throw new CompletionException(x);
}
});
Assert.assertEquals(cf.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Label.class,
"Could not load class from application's class loader.");
}
finally {
executor.shutdownNow();
}
}
/**
* Verify that when JTA transactions are supported and configuration of propagated=TRANSACTION
* is permitted, it is at least possible to propagate the absence of a transaction,
* and if context is allowed to be captured while a transaction is active, then the task or action
* runs with a transaction on the thread. It would be nice to test participation in the same
* transaction, but that isn't possible without the TCK having a dependency on a particular
* type of transactional resource.
*
* @throws Exception indicates test failure
*/
@Test
public void propagateTransactionContextJTA() throws Exception {
ManagedExecutor executor;
try {
executor = ManagedExecutor.builder()
.propagated(ThreadContext.TRANSACTION)
.cleared(ThreadContext.ALL_REMAINING)
.build();
}
catch (IllegalStateException x) {
System.out.println("Skipping test propagateTransactionContextJTA. Transaction context propagation is not supported.");
return;
}
Class> userTransaction;
try {
userTransaction = Class.forName("javax.transaction.UserTransaction");
}
catch (ClassNotFoundException x) {
System.out.println("Skipping test propagateTransactionContextJTA. javax.transaction.UserTransaction not available to applications.");
return;
}
Object txFromJNDI = null;
try {
txFromJNDI = InitialContext.doLookup("java:comp/UserTransaction");
System.out.println("JTA UserTransaction is available in JNDI.");
}
catch (NamingException x) {
System.out.println("JTA UserTransaction not available in JNDI: " + x);
}
Object txFromCDI = null;
try {
// txFromCDI = CDI.current().select(UserTransaction.class).get();
Class> cdi = Class.forName("javax.enterprise.inject.spi.CDI");
Object current = cdi.getMethod("current").invoke(null);
Object instance = cdi.getMethod("select", Class.class, Annotation[].class).invoke(current, userTransaction, new Annotation[] {});
txFromCDI = instance.getClass().getMethod("get").invoke(instance);
System.out.println("JTA UserTransaction is available via CDI.");
}
catch (RuntimeException | InvocationTargetException x) {
System.out.println("JTA UserTransaction not available via CDI: " +
(x instanceof InvocationTargetException ? x.getCause() : x));
}
Object tx = txFromJNDI == null ? txFromCDI : txFromJNDI;
if (tx == null) {
System.out.println("Skipping test propagateTransactionContextJTA. JTA transactions are not supported.");
return;
}
System.out.println("Using JTA UserTransaction: " + tx);
Method begin = userTransaction.getMethod("begin");
Method commit = userTransaction.getMethod("commit");
Method getStatus = userTransaction.getMethod("getStatus");
Method rollback = userTransaction.getMethod("rollback");
// Propagate context from thread where no transaction is active
CompletableFuture stage0 = executor.newIncompleteFuture();
CompletableFuture stage1 = stage0.thenApply(s -> {
try {
Assert.assertEquals(getStatus.invoke(tx), 6, // javax.transaction.Status.STATUS_NO_TRANSACTION
"Transaction status should indicate no transaction is active on thread.");
begin.invoke(tx);
commit.invoke(tx);
return "SUCCESS1";
}
catch (Exception x) {
throw new CompletionException(x);
}
});
CompletableFuture stage2;
boolean txPropagationRejected = false;
begin.invoke(tx);
try {
// Force stage1 to run on thread where transaction context is already present
Assert.assertTrue(stage0.complete("READY"));
Assert.assertEquals(stage1.join(), "SUCCESS1");
Assert.assertEquals(getStatus.invoke(tx), 0, // javax.transaction.Status.STATUS_ACTIVE
"Transaction no longer active after running task.");
// Attempt to propagate this transaction to another thread.
try {
stage2 = stage1.thenApplyAsync(s -> {
try {
Assert.assertEquals(getStatus.invoke(tx), 0, // javax.transaction.Status.STATUS_ACTIVE
"Transaction context not propagated.");
return "SUCCESS2";
}
catch (Exception x) {
throw new CompletionException(x);
}
});
}
catch (IllegalStateException x) {
System.out.println("Skipping portion of test propagateTransactionContextJTA. Propagation of active transaction is not supported.");
txPropagationRejected = true;
return;
}
String result;
try {
result = stage2.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS);
}
catch (ExecutionException x) {
if (x.getCause() instanceof IllegalStateException) {
System.out.println("Skipping portion of test propagateTransactionContextJTA. " +
"Propagation of active transaction to multiple threads in parallel is not supported.");
txPropagationRejected = true;
return;
}
else {
throw x;
}
}
Assert.assertEquals(result, "SUCCESS2");
}
finally {
if (txPropagationRejected) {
rollback.invoke(tx);
}
else {
commit.invoke(tx);
}
}
}
/**
* Verify that the ManagedExecutor shutdownNow method prevents additional tasks from being submitted
* and cancels tasks that are currently in progress or queued.
* Also verify that once the tasks and actions terminate, the ManagedExecutor transitions to terminated state.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void shutdownNowPreventsAdditionalSubmitsAndCancelsTasks() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(1)
.maxQueued(4)
.propagated()
.cleared(ThreadContext.ALL_REMAINING)
.build();
Phaser barrier = new Phaser(1);
Future future1;
CompletableFuture future2;
CompletableFuture future3;
Future future4;
Future future5 = null;
AtomicInteger task2ResultRef = new AtomicInteger(-1);
List tasksThatDidNotStart;
try {
try {
// Block the single maxAsync slot
future1 = executor.submit(() -> barrier.awaitAdvanceInterruptibly(barrier.arrive() + 1));
barrier.awaitAdvanceInterruptibly(0, MAX_WAIT_NS, TimeUnit.NANOSECONDS);
// Queue up some tasks
future2 = executor.runAsync(() -> task2ResultRef.set(20));
future3 = executor.supplyAsync(() -> "Q30");
future4 = executor.submit(() -> "Q40");
Assert.assertFalse(executor.isTerminated(),
"ManagedExecutor should not report being terminated when tasks are still running/queued.");
// Await termination from a different executor,
future5 = unmanagedThreads.submit(() -> executor.awaitTermination(MAX_WAIT_NS, TimeUnit.NANOSECONDS));
}
finally {
tasksThatDidNotStart = executor.shutdownNow();
}
Assert.assertNotNull(tasksThatDidNotStart,
"Null list returned by ManagedExecutor.shutdownNow.");
Assert.assertEquals(tasksThatDidNotStart.size(), 3,
"List of tasks that did not start should correspond to the tasks/actions that are queued. Observed: " +
tasksThatDidNotStart);
Assert.assertTrue(executor.isShutdown(),
"ManagedExecutor reported that it has not been shut down after we shut it down.");
// additional submits of async tasks/actions must be rejected
try {
Future future6 = executor.submit(() -> 60);
Assert.fail("Should not be possible to submit new task after shutdownNow. Future: " + future6);
}
catch (RejectedExecutionException x) {
// test passes
}
try {
Future future7 = executor.supplyAsync(() -> 70);
Assert.fail("Should not be possible to create new async action after shutdownNow. Future: " + future7);
}
catch (RejectedExecutionException x) {
// test passes
}
Assert.assertTrue(executor.awaitTermination(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"ManagedExecutor did not reach terminated state within a reasonable amount of time.");
Assert.assertTrue(executor.isTerminated(),
"ManagedExecutor did not report being terminated after running/queued tasks were canceled and ended.");
// assert that future 1 was completed but ended with ExecutionException because it was running on executor
// when shutdownNow() was invoked
try {
Assert.assertTrue(future1.isDone());
Integer result1 = future1.get(1, TimeUnit.SECONDS);
Assert.fail("Running task should not complete successfully after shutdownNow. Result: " + result1);
}
catch (ExecutionException x) {
if (!(x.getCause() instanceof InterruptedException)) {
throw x;
}
// test passes
}
catch (CancellationException x) {
// test passes, impl may chose to mark such task as cancelled
}
// assert that future 2,3,4 weren't executed (based on impl they are either neither done nor cancelled
// or they are done and cancelled)
if (future2.isDone()) {
try {
Object result2 = future2.join();
Assert.fail("Queued action should not run after shutdownNow. Result: " + result2);
}
catch (CancellationException x) {
// test passes
}
}
else {
Assert.assertTrue(!future2.isCancelled(), "Running task should not complete after shutdownNow() invocation.");
}
if (future3.isDone()) {
try {
String result3 = future3.getNow("333");
Assert.fail("Queued action should not run after shutdownNow. Result: " + result3);
}
catch (CancellationException x) {
// test passes
}
}
else {
Assert.assertTrue(!future3.isCancelled(), "Running task should not complete after shutdownNow() invocation.");
}
if (future4.isDone()) {
try {
String result4 = future4.get(1, TimeUnit.SECONDS);
Assert.fail("Queued task should not run after shutdownNow. Result: " + result4);
}
catch (CancellationException x) {
// test passes
}
}
else {
Assert.assertTrue(!future4.isCancelled(), "Running task should not complete after shutdownNow() invocation.");
}
Assert.assertEquals(task2ResultRef.get(), -1,
"Queued action should not start running after shutdownNow.");
Assert.assertEquals(future5.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Boolean.TRUE,
"Notification of termination was not received in a reasonable amount of time by the " +
"awaitTermination request that was issued before shutdownNow");
}
finally {
barrier.forceTermination();
if (future5 != null) {
future5.cancel(true);
}
}
}
/**
* Verify that the ManagedExecutor shutdown method prevents additional tasks from being submitted
* but does not interfere with tasks and actions that are running or queued.
* Also verify that once the tasks and actions finish, the ManagedExecutor transitions to terminated state.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void shutdownPreventsAdditionalSubmits() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(1)
.maxQueued(10)
.propagated()
.cleared(ThreadContext.ALL_REMAINING)
.build();
Phaser barrier = new Phaser(1);
CompletableFuture future1;
CompletableFuture future2;
Future future3;
Future future4 = null;
Future future5 = null;
try {
try {
// Block the single maxAsync slot
future1 = executor.supplyAsync(() -> barrier.awaitAdvance(barrier.arrive() + 1));
barrier.awaitAdvanceInterruptibly(0, MAX_WAIT_NS, TimeUnit.NANOSECONDS);
// Queue up some tasks
future2 = executor.supplyAsync(() -> "Q2");
future3 = executor.submit(() -> "Q3");
Assert.assertFalse(executor.isShutdown(),
"ManagedExecutor reportd that it has been shut down even though we did not shut it down yet.");
// Await termination from a different executor,
future4 = unmanagedThreads.submit(() -> executor.awaitTermination(MAX_WAIT_NS, TimeUnit.NANOSECONDS));
}
finally {
executor.shutdown();
}
Assert.assertTrue(executor.isShutdown(),
"ManagedExecutor reported that it has not been shut down after we shut it down.");
// additional submits of async tasks/actions must be rejected
try {
Future future6 = executor.submit(() -> 60);
Assert.fail("Should not be possible to submit new task after shutdown. Future: " + future6);
}
catch (RejectedExecutionException x) {
// test passes
}
try {
Future future7 = executor.supplyAsync(() -> 70);
Assert.fail("Should not be possible to create new async action after shutdown. Future: " + future7);
}
catch (RejectedExecutionException x) {
// test passes
}
Assert.assertFalse(future1.isDone(),
"Task should remain running after shutdown is invoked.");
Assert.assertFalse(future2.isDone(),
"Action should remain queued after shutdown is invoked.");
Assert.assertFalse(future3.isDone(),
"Task should remain queued after shutdown is invoked.");
Assert.assertFalse(executor.isTerminated(),
"ManagedExecutor should not report being terminated when tasks are still running/queued.");
// Extra invocation of shutdown has no effect per ExecutorService JavaDoc,
executor.shutdown();
// Await termination from a different executor,
future5 = unmanagedThreads.submit(() -> executor.awaitTermination(MAX_WAIT_NS, TimeUnit.NANOSECONDS));
// Let the running task finish and the queued tasks run
barrier.arrive();
Assert.assertEquals(future1.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Integer.valueOf(2),
"Unexpected result for action that was running when shutdown was requested.");
Assert.assertEquals(future2.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), "Q2",
"Unexpected result for action that was in the queue when shutdown was requested.");
Assert.assertEquals(future3.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), "Q3",
"Unexpected result for task that was in the queue when shutdown was requested.");
Assert.assertEquals(future4.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Boolean.TRUE,
"Notification of termination was not received in a reasonable amount of time by the " +
"awaitTermination request that was issued prior to shutdown");
Assert.assertEquals(future5.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS), Boolean.TRUE,
"Notification of termination was not received in a reasonable amount of time by the " +
"awaitTermination request that was issued after shutdown");
Assert.assertTrue(executor.isTerminated(),
"ManagedExecutor did not report being terminated after running/queued tasks completed.");
}
finally {
barrier.forceTermination();
if (future4 != null) {
future4.cancel(true);
}
if (future5 != null) {
future5.cancel(true);
}
}
}
/**
* Verify that the ManagedExecutor.Builder can be used to create multiple ManagedExecutors with
* different configured contexts.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void reuseManagedExecutorBuilder() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor.Builder builder = ManagedExecutor.builder()
.propagated()
.cleared(Buffer.CONTEXT_NAME);
ManagedExecutor clearingExecutor = builder.build();
ManagedExecutor propagatingExecutor = builder.propagated(Buffer.CONTEXT_NAME)
.cleared()
.build();
try {
// Set non-default value
Buffer.set(new StringBuffer("reuseBuilder-test-buffer-A"));
Future clearedFuture = clearingExecutor.completedFuture(1).thenRun(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Buffer.set(new StringBuffer("reuseBuilder-test-buffer-B"));
});
Future propagatedFuture = propagatingExecutor.completedFuture(1).thenRunAsync(() -> {
Assert.assertEquals(Buffer.get().toString(), "reuseBuilder-test-buffer-A",
"Context type was not propagated to contextual action.");
Buffer.set(new StringBuffer("reuseBuilder-test-buffer-C"));
});
Assert.assertNull(propagatedFuture.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Non-null value returned by stage that runs Runnable.");
Assert.assertNull(clearedFuture.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Non-null value returned by stage that runs Runnable.");
Assert.assertEquals(Buffer.get().toString(), "reuseBuilder-test-buffer-A",
"Previous context (Buffer) was not restored after context was propagated for contextual action.");
}
finally {
clearingExecutor.shutdownNow();
propagatingExecutor.shutdownNow();
// Restore original value
Buffer.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all dependent stages as well as the initial stage created
* by the ManagedExecutor's runAsync implementation. Thread context is captured
* at each point where a dependent stage is added, rather than solely upon creation of the
* initial stage or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
*/
@Test
public void runAsyncStageAndDependentStagesRunWithContext() throws ExecutionException, InterruptedException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.get().append("runAsync-test-buffer");
Label.set("runAsync-test-label-A");
CompletableFuture stage1 = executor.runAsync(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "runAsync-test-label-A",
"Context type was not correctly propagated to contextual action.");
});
Label.set("runAsync-test-label-B");
CompletableFuture stage2 = stage1.thenRunAsync(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "runAsync-test-label-B",
"Context type was not correctly propagated to contextual action.");
});
Label.set("runAsync-test-label-C");
CompletableFuture stage3 = stage2.thenRunAsync(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "runAsync-test-label-C",
"Context type was not correctly propagated to contextual action.");
}, unmanagedThreads); // supplied executor runs the action, but does not determine context propagation
Label.set("runAsync-test-label-D");
CompletableFuture stage4 = stage3.thenRun(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "runAsync-test-label-D",
"Context type was not correctly propagated to contextual action.");
throw new NegativeArraySizeException("Fake exception raised by test");
});
Label.set("runAsync-test-label-E");
CompletableFuture stage5 = stage4.handle((v, x) -> {
Assert.assertNull(v,
"Non-null value supplied to 'handle' method.");
Assert.assertEquals(x.getClass(), CompletionException.class,
"Exception parameter to 'handle' method is inconsistent with java.util.concurrent.CompletableFuture.");
Throwable cause = x.getCause();
Assert.assertNotNull(cause,
"CompletionException supplied to 'handle' method lacks cause.");
Assert.assertEquals(cause.getClass(), NegativeArraySizeException.class,
"Wrong exception class supplied to 'handle' method.");
Assert.assertEquals(cause.getMessage(), "Fake exception raised by test",
"Exception message was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "runAsync-test-label-E",
"Context type was not correctly propagated to contextual action.");
return 'E';
});
Label.set("runAsync-test-label-F");
CompletableFuture stage6 = stage5.thenAccept(c -> {
Assert.assertEquals(c, Character.valueOf('E'),
"Value supplied to Consumer was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "runAsync-test-label-F",
"Context type was not correctly propagated to contextual action.");
});
Label.set("runAsync-test-label-G");
// use a countdown latch to independently wait for completion.
CountDownLatch completed = new CountDownLatch(1);
stage6.whenComplete((result, failure) -> completed.countDown());
Assert.assertTrue(completed.await(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Completable future did not finish in a reasonable amount of time.");
Assert.assertTrue(stage1.isDone(), "First stage did not transition to done upon completion.");
Assert.assertTrue(stage2.isDone(), "Second stage did not transition to done upon completion.");
Assert.assertTrue(stage3.isDone(), "Third stage did not transition to done upon completion.");
Assert.assertTrue(stage4.isDone(), "Fourth stage did not transition to done upon completion.");
Assert.assertTrue(stage5.isDone(), "Fifth stage did not transition to done upon completion.");
Assert.assertTrue(stage6.isDone(), "Sixth stage did not transition to done upon completion.");
try {
Object result = stage4.join();
Assert.fail("The join method must not return value " + result + " for stage with exceptional completion.");
}
catch (CompletionException x) {
if (x.getCause() == null || !(x.getCause() instanceof NegativeArraySizeException)
|| !"Fake exception raised by test".equals(x.getCause().getMessage())) {
throw x;
}
}
Assert.assertEquals(stage5.join(), Character.valueOf('E'),
"Return value of 'handle' method was lost or altered.");
Assert.assertFalse(stage1.isCompletedExceptionally(), "First stage should not report exceptional completion.");
Assert.assertFalse(stage2.isCompletedExceptionally(), "Second stage should not report exceptional completion.");
Assert.assertFalse(stage3.isCompletedExceptionally(), "Third stage should not report exceptional completion.");
Assert.assertTrue(stage4.isCompletedExceptionally(), "Fourth stage did not report exceptional completion.");
Assert.assertFalse(stage5.isCompletedExceptionally(), "Fifth stage should not report exceptional completion.");
Assert.assertFalse(stage6.isCompletedExceptionally(), "Sixth stage should not report exceptional completion.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all tasks that are submitted via the submit(Callable),
* submit(Runnable) and submit(Runnable, result) methods.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void submittedTasksRunWithContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
Buffer.set(new StringBuffer("submitted-tasks-test-buffer-A"));
Label.set("submitted-tasks-test-label-A");
Future> futureA = executor.submit(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "submitted-tasks-test-label-A",
"Context type was not correctly propagated to contextual action.");
throw new Error("Fake error intentionally raised by test Runnable.");
});
Label.set("submitted-tasks-test-label-B");
Future futureB = executor.submit(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "submitted-tasks-test-label-B",
"Context type was not correctly propagated to contextual action.");
}, "Task-B-Result");
Label.set("submitted-tasks-test-label-C");
Future futureC = executor.submit(() -> {
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Assert.assertEquals(Label.get(), "submitted-tasks-test-label-C",
"Context type was not correctly propagated to contextual action.");
return "Task-C-Result";
});
try {
Object result = futureA.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.fail("Result of " + result + " returned for Runnable that throws an Error.");
}
catch (ExecutionException x) {
if (x.getCause() == null
|| (!(x.getCause() instanceof Error))
|| (!"Fake error intentionally raised by test Runnable.".equals(x.getCause().getMessage()))) {
throw x;
}
}
Assert.assertEquals("Task-B-Result", futureB.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Result does not match the predetermined result that was specified when submitting the Runnable.");
Assert.assertEquals("Task-C-Result", futureC.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Result does not match the result returned by the Callable.");
Assert.assertTrue(futureA.isDone(),
"Future for first Runnable should report being done after test case awaits its completion.");
Assert.assertTrue(futureB.isDone(),
"Future for second Runnable should report being done after test case awaits its completion.");
Assert.assertTrue(futureC.isDone(),
"Future for Callable should report being done after test case awaits its completion.");
Assert.assertFalse(futureA.isCancelled(),
"Future for first Runnable should not be canceled because the test case did not cancel it.");
Assert.assertFalse(futureB.isCancelled(),
"Future for second Runnable should not be canceled because the test case did not cancel it.");
Assert.assertFalse(futureC.isCancelled(),
"Future for Callable should not be canceled because the test case did not cancel it.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all dependent stages as well as the initial stage created
* by the ManagedExecutor's supplyAsync implementation. Thread context is captured
* at each point where a dependent stage is added, rather than solely upon creation of the
* initial stage or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void supplyAsyncStageAndDependentStagesRunWithContext() throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("supplyAsync-test-buffer-A"));
Label.set("supplyAsync-test-label");
CompletableFuture stage1 = executor.supplyAsync(() -> {
Assert.assertEquals(Buffer.get().toString(), "supplyAsync-test-buffer-A",
"Context type was not correctly propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
return 100L;
});
Buffer.set(new StringBuffer("supplyAsync-test-buffer-B"));
CompletableFuture stage2 = stage1.thenApply(i -> {
Assert.assertEquals(i, Long.valueOf(100),
"Value supplied to Function was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "supplyAsync-test-buffer-B",
"Context type was not correctly propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
return 200L;
});
Buffer.set(new StringBuffer("supplyAsync-test-buffer-C"));
CompletableFuture stage3 = stage1.thenApply(i -> {
Assert.assertEquals(i, Long.valueOf(100),
"Value supplied to Function was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "supplyAsync-test-buffer-C",
"Context type was not correctly propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
return 300L;
});
Buffer.set(new StringBuffer("supplyAsync-test-buffer-D"));
CompletableFuture stage4 = stage2.thenAcceptBoth(stage3, (i, j) -> {
Assert.assertEquals(i, Long.valueOf(200),
"First value supplied to BiConsumer was lost or altered.");
Assert.assertEquals(j, Long.valueOf(300),
"Second value supplied to BiConsumer was lost or altered.");
Assert.assertEquals(Buffer.get().toString(), "supplyAsync-test-buffer-D",
"Context type was not correctly propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
});
Buffer.set(new StringBuffer("supplyAsync-test-buffer-D"));
// use a countdown latch to independently wait for completion.
CountDownLatch completed = new CountDownLatch(1);
stage4.handleAsync((result, x) -> {
completed.countDown();
return result;
}, unmanagedThreads);
Assert.assertTrue(completed.await(MAX_WAIT_NS, TimeUnit.NANOSECONDS),
"Completable future did not finish in a reasonable amount of time.");
Assert.assertEquals(stage1.get(10, TimeUnit.SECONDS), Long.valueOf(100),
"Unexpected result for first stage.");
Assert.assertEquals(stage2.join(), Long.valueOf(200),
"Unexpected result for second stage.");
Assert.assertEquals(stage3.getNow(33L), Long.valueOf(300),
"Unexpected result for third stage.");
Assert.assertNull(stage4.join(),
"Unexpected result for fourth stage.");
// Is context properly restored on current thread?
Assert.assertEquals(Buffer.get().toString(), "supplyAsync-test-buffer-D",
"Previous context was not restored after context was propagated for managed executor tasks.");
Assert.assertEquals(Label.get(), "supplyAsync-test-label",
"Previous context was not restored after context was cleared for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all tasks that are submitted via the ManagedExecutor's
* timed invokeAll operation. Thread context is captured at the point where invokeAll is
* invoked, rather than upon creation of the executor or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void timedInvokeAllRunsTasksWithContext()
throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("timed-invokeAll-test-buffer-A"));
Label.set("timed-invokeAll-test-label-A");
List> futures = executor.invokeAll(Arrays.>asList(
() -> {
Assert.assertEquals(Buffer.get().toString(), "timed-invokeAll-test-buffer-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("timed-invokeAll-test-label-B");
Buffer.set(new StringBuffer("timed-invokeAll-test-buffer-B"));
return "B";
},
() -> {
Assert.assertEquals(Buffer.get().toString(), "timed-invokeAll-test-buffer-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("timed-invokeAll-test-label-C");
Buffer.set(new StringBuffer("invokeAll-test-buffer-C"));
return "C";
}),
MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.assertEquals(futures.size(), 2,
"Number of futures does not match the number of tasks. " + futures);
Assert.assertTrue(futures.get(0).isDone(),
"Future for first task does not indicate it is done after invokeAll.");
Assert.assertEquals(futures.get(0).get(), "B",
"Future for first task returned wrong value.");
Assert.assertTrue(futures.get(1).isDone(),
"Future for second task does not indicate it is done after invokeAll.");
Assert.assertEquals(futures.get(1).get(1, TimeUnit.SECONDS), "C",
"Future for second task returned wrong value.");
Assert.assertEquals(Label.get(), "timed-invokeAll-test-label-A",
"Previous context was not restored after context was propagated for managed executor tasks.");
Assert.assertEquals(Buffer.get().toString(), "timed-invokeAll-test-buffer-A",
"Previous context was not restored after context was cleared for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for one or more tasks that are submitted via the ManagedExecutor's
* timed invokeAny operation. Thread context is captured at the point where invokeAny is
* invoked, rather than upon creation of the executor or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void timedInvokeAnyRunsTaskWithContext()
throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.propagated(Buffer.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("timed-invokeAny-test-buffer-A"));
Label.set("timed-invokeAny-test-label-A");
Character result = executor.invokeAny(Arrays.>asList(
() -> {
Assert.assertEquals(Buffer.get().toString(), "timed-invokeAny-test-buffer-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("timed-invokeAny-test-label-B");
Buffer.set(new StringBuffer("timed-invokeAny-test-buffer-B"));
return 'b';
},
() -> {
Assert.assertEquals(Buffer.get().toString(), "timed-invokeAny-test-buffer-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Label.get(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("timed-invokeAny-test-label-C");
Buffer.set(new StringBuffer("invokeAny-test-buffer-C"));
return 'c';
}),
MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.assertTrue(Character.valueOf('b').equals(result) || Character.valueOf('c').equals(result),
"Result of invokeAny, " + result + ", does not match the result of either of the tasks.");
Assert.assertEquals(Label.get(), "timed-invokeAny-test-label-A",
"Previous context was not restored after context was propagated for managed executor tasks.");
Assert.assertEquals(Buffer.get().toString(), "timed-invokeAny-test-buffer-A",
"Previous context was not restored after context was cleared for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for all tasks that are submitted via the ManagedExecutor's
* untimed invokeAll operation. Thread context is captured at the point where invokeAll is
* invoked, rather than upon creation of the executor or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void untimedInvokeAllRunsTasksWithContext()
throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(1)
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
// Verify that maxAsync=1 is enforced by recording the thread id upon which
// the managed executor runs tasks asynchronous to the requesting thread.
// If this value is ever found to be non-zero when set to a new thread id,
// this indicates that multiple tasks are running asynchronously to the
// requesting thread, which is a violation of maxAsync=1.
AtomicLong asyncThreadIdRef = new AtomicLong();
long testThreadId = Thread.currentThread().getId();
try {
// Set non-default values
Buffer.set(new StringBuffer("untimed-invokeAll-test-buffer-A"));
Label.set("untimed-invokeAll-test-label-A");
List> futures = executor.invokeAll(Arrays.>asList(
() -> {
long threadId = Thread.currentThread().getId();
if (threadId != testThreadId) {
Assert.assertEquals(asyncThreadIdRef.getAndSet(threadId), 0L,
"Thread ID indicates that ManagedExecutor invokeAll operation exceeded maxAsync of 1.");
}
Assert.assertEquals(Label.get(), "untimed-invokeAll-test-label-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("untimed-invokeAll-test-label-B");
Buffer.set(new StringBuffer("untimed-invokeAll-test-buffer-B"));
asyncThreadIdRef.compareAndSet(threadId, 0L);
return 66;
},
() -> {
long threadId = Thread.currentThread().getId();
if (threadId != testThreadId) {
Assert.assertEquals(asyncThreadIdRef.getAndSet(threadId), 0L,
"Thread ID indicates that ManagedExecutor invokeAll operation exceeded maxAsync of 1.");
}
Assert.assertEquals(Label.get(), "untimed-invokeAll-test-label-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("untimed-invokeAll-test-label-C");
Buffer.set(new StringBuffer("uninvokeAll-test-buffer-C"));
asyncThreadIdRef.compareAndSet(threadId, 0L);
return 67;
},
() -> {
long threadId = Thread.currentThread().getId();
if (threadId != testThreadId) {
Assert.assertEquals(asyncThreadIdRef.getAndSet(threadId), 0L,
"Thread ID indicates that ManagedExecutor invokeAll operation exceeded maxAsync of 1.");
}
Assert.assertEquals(Label.get(), "untimed-invokeAll-test-label-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("untimed-invokeAll-test-label-D");
Buffer.set(new StringBuffer("untimed-invokeAll-test-buffer-D"));
asyncThreadIdRef.compareAndSet(threadId, 0L);
return 68;
},
() -> {
long threadId = Thread.currentThread().getId();
if (threadId != testThreadId) {
Assert.assertEquals(asyncThreadIdRef.getAndSet(threadId), 0L,
"Thread ID indicates that ManagedExecutor invokeAll operation exceeded maxAsync of 1.");
}
Assert.assertEquals(Label.get(), "untimed-invokeAll-test-label-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("untimed-invokeAll-test-label-E");
Buffer.set(new StringBuffer("untimed-invokeAll-test-buffer-E"));
asyncThreadIdRef.compareAndSet(threadId, 0L);
return 69;
}),
MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.assertEquals(futures.size(), 4,
"Number of futures does not match the number of tasks. " + futures);
Assert.assertTrue(futures.get(0).isDone(),
"Future for first task does not indicate it is done after invokeAll.");
Assert.assertEquals(futures.get(0).get(), Integer.valueOf(66),
"Future for first task returned wrong value.");
Assert.assertTrue(futures.get(1).isDone(),
"Future for second task does not indicate it is done after invokeAll.");
Assert.assertEquals(futures.get(1).get(1, TimeUnit.SECONDS), Integer.valueOf(67),
"Future for second task returned wrong value.");
Assert.assertTrue(futures.get(2).isDone(),
"Future for third task does not indicate it is done after invokeAll.");
Assert.assertEquals(futures.get(2).get(), Integer.valueOf(68),
"Future for third task returned wrong value.");
Assert.assertTrue(futures.get(3).isDone(),
"Future for fourth task does not indicate it is done after invokeAll.");
Assert.assertEquals(futures.get(3).get(), Integer.valueOf(69),
"Future for fourth task returned wrong value.");
Assert.assertEquals(Label.get(), "untimed-invokeAll-test-label-A",
"Previous context was not restored after context was propagated for managed executor tasks.");
Assert.assertEquals(Buffer.get().toString(), "untimed-invokeAll-test-buffer-A",
"Previous context was not restored after context was cleared for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
/**
* Verify that thread context is captured and propagated per the configuration of the
* ManagedExecutor builder for one or more tasks that are submitted via the ManagedExecutor's
* untimed invokeAny operation. Thread context is captured at the point where invokeAny is
* invoked, rather than upon creation of the executor or construction of the builder.
*
* @throws ExecutionException indicates test failure
* @throws InterruptedException indicates test failure
* @throws TimeoutException indicates test failure
*/
@Test
public void untimedInvokeAnyRunsTasksWithContext()
throws ExecutionException, InterruptedException, TimeoutException {
ManagedExecutor executor = ManagedExecutor.builder()
.maxAsync(1)
.propagated(Label.CONTEXT_NAME)
.cleared(ThreadContext.ALL_REMAINING)
.build();
try {
// Set non-default values
Buffer.set(new StringBuffer("untimed-invokeAny-test-buffer-A"));
Label.set("untimed-invokeAny-test-label-A");
String result = executor.invokeAny(Arrays.>asList(
() -> {
Assert.assertEquals(Label.get(), "untimed-invokeAny-test-label-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("untimed-invokeAny-test-label-B");
Buffer.set(new StringBuffer("untimed-invokeAny-test-buffer-B"));
return "Bb";
},
() -> {
Assert.assertEquals(Label.get(), "untimed-invokeAny-test-label-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("untimed-invokeAny-test-label-C");
Buffer.set(new StringBuffer("uninvokeAny-test-buffer-C"));
return "Cc";
},
() -> {
Assert.assertEquals(Label.get(), "untimed-invokeAny-test-label-A",
"Context type was not propagated to contextual action.");
Assert.assertEquals(Buffer.get().toString(), "",
"Context type that is configured to be cleared was not cleared.");
Label.set("untimed-invokeAny-test-label-D");
Buffer.set(new StringBuffer("untimed-invokeAny-test-buffer-D"));
return "Dd";
}),
MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.assertTrue("Bb".equals(result) || "Cc".equals(result) || "Dd".equals(result),
"Result of invokeAny, " + result + ", does not match the result of any of the tasks.");
Assert.assertEquals(Label.get(), "untimed-invokeAny-test-label-A",
"Previous context was not restored after context was propagated for managed executor tasks.");
Assert.assertEquals(Buffer.get().toString(), "untimed-invokeAny-test-buffer-A",
"Previous context was not restored after context was cleared for managed executor tasks.");
}
finally {
executor.shutdownNow();
// Restore original values
Buffer.set(null);
Label.set(null);
}
}
}