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,2020 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.io.CharConversionException;
import java.lang.reflect.Method;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
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.enterprise.inject.Instance;
import javax.enterprise.inject.spi.CDI;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
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.ContextManager;
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")
.addClasses(ManagedExecutorTest.class, TckThread.class, TckThreadFactory.class, ThreadContextTest.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 {
ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader();
ManagedExecutor.Builder builder = ManagedExecutor.builder()
.propagated(ThreadContext.APPLICATION)
.cleared(ThreadContext.ALL_REMAINING);
ManagedExecutor executor = builder.build();
ExecutorService unmanagedSingleThreadExecutor = Executors.newFixedThreadPool(1);
try {
// Set the class loader of the single thread upon which unmanagedSingleThreadExecutor runs tasks
ClassLoader newClassLoader = unmanagedSingleThreadExecutor.submit(() -> {
ClassLoader loader = new ClassLoader() {};
Thread.currentThread().setContextClassLoader(loader);
return loader;
}).get(MAX_WAIT_NS, TimeUnit.NANOSECONDS);
// Use a ManagedExecutor to apply context to an operation that runs on unmanagedSingleThreadExecutor
CompletableFuture>> cf = executor.completedFuture(1).thenApplyAsync(i -> {
try {
// load a class from the application
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return new SimpleEntry>(
loader,
loader.loadClass("org.eclipse.microprofile.context.tck.contexts.label.Label"));
}
catch (ClassNotFoundException x) {
throw new CompletionException(x);
}
}, unmanagedSingleThreadExecutor);
Map.Entry> result = cf.get(MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.assertEquals(result.getKey(), appClassLoader);
Assert.assertEquals(result.getValue(), Label.class,
"Did not properly load class from application's class loader.");
// Verify that the class loader of unmanagedSingleThreadExecutor was restored after
// running the previous task.
ClassLoader loaderForSingleThreadExecutor = unmanagedSingleThreadExecutor.submit(() -> {
return Thread.currentThread().getContextClassLoader();
}).get(MAX_WAIT_NS, TimeUnit.NANOSECONDS);
Assert.assertEquals(loaderForSingleThreadExecutor, newClassLoader);
}
finally {
executor.shutdownNow();
unmanagedSingleThreadExecutor.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.
*
* The test tries to get hold of {@code UserTransaction} via JNDI and via CDI.
* Should neither work, it still doesn't throw exception but instead returns.
*
* @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;
}
// try to get UserTransaction via JNDI
UserTransaction txFromJNDI = null;
try {
txFromJNDI = InitialContext.doLookup("java:comp/UserTransaction");
}
catch (NamingException x) {
// JTA UserTransaction not available in JNDI
}
// try to get UserTransaction via CDI
UserTransaction txFromCDI = null;
try {
CDI