diff --git a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedExecutorService/tx/TransactionServlet.java b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedExecutorService/tx/TransactionServlet.java index 3a37d421..3de56b7c 100644 --- a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedExecutorService/tx/TransactionServlet.java +++ b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedExecutorService/tx/TransactionServlet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -13,7 +13,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ - package ee.jakarta.tck.concurrent.spec.ManagedExecutorService.tx; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -38,7 +37,7 @@ import ee.jakarta.tck.concurrent.framework.junit.extensions.Wait; import jakarta.annotation.Resource; import jakarta.annotation.sql.DataSourceDefinition; -import jakarta.enterprise.concurrent.ManagedScheduledExecutorService; +import jakarta.enterprise.concurrent.ManagedExecutorService; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -59,8 +58,8 @@ public class TransactionServlet extends TestServlet { @Resource(lookup = "java:comp/env/jdbc/ManagedExecutorServiceDB") private DataSource ds; - @Resource(lookup = TestConstants.defaultManagedScheduledExecutorService) - private ManagedScheduledExecutorService scheduledExecutor; + @Resource(lookup = TestConstants.defaultManagedExecutorService) + private ManagedExecutorService executor; @Override protected void beforeClass() throws RemoteException { @@ -84,7 +83,7 @@ protected void beforeClass() throws RemoteException { public void transactionTest(final HttpServletRequest req, final HttpServletResponse res) throws Exception { boolean isCommit = Boolean.parseBoolean(req.getParameter(Constants.PARAM_COMMIT)); - Future taskResult = scheduledExecutor.submit(new TransactedTask(isCommit, Constants.SQL_TEMPLATE_INSERT)); + Future taskResult = executor.submit(new TransactedTask(isCommit, Constants.SQL_TEMPLATE_INSERT)); Wait.waitForTaskComplete(taskResult); } @@ -93,7 +92,7 @@ public void cancelTest() { int originTableCount = Counter.getCount(); CancelledTransactedTask cancelledTask = new CancelledTransactedTask(Constants.SQL_TEMPLATE_INSERT); - Future future = scheduledExecutor.submit(cancelledTask); + Future future = executor.submit(cancelledTask); // wait for transaction to begin Wait.waitForTransactionBegan(cancelledTask); diff --git a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedScheduledExecutorService/resourcedef/ManagedScheduledExecutorDefinitionOnEJBServlet.java b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedScheduledExecutorService/resourcedef/ManagedScheduledExecutorDefinitionOnEJBServlet.java index 61cfe394..1e657ef1 100644 --- a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedScheduledExecutorService/resourcedef/ManagedScheduledExecutorDefinitionOnEJBServlet.java +++ b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedScheduledExecutorService/resourcedef/ManagedScheduledExecutorDefinitionOnEJBServlet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedBean.java b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedBean.java new file mode 100644 index 00000000..21de12f4 --- /dev/null +++ b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedBean.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package ee.jakarta.tck.concurrent.spec.ManagedThreadFactory.injected; + +import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionServlet; +import jakarta.ejb.Stateless; +import jakarta.enterprise.concurrent.ManagedThreadFactoryDefinition; + +/** + * @ContextServiceDefinitions are defined under + * {@link ContextServiceDefinitionServlet} + */ +@ManagedThreadFactoryDefinition(name = "java:app/concurrent/EJBThreadFactoryInjA", context = "java:app/concurrent/EJBContextA", priority = 4) +@ManagedThreadFactoryDefinition(name = "java:comp/concurrent/EJBThreadFactoryInjB") +@Stateless +public class ManagedThreadFactoryDefinitionInjectedBean { +} diff --git a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedFullTests.java b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedFullTests.java new file mode 100644 index 00000000..5419b49b --- /dev/null +++ b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedFullTests.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package ee.jakarta.tck.concurrent.spec.ManagedThreadFactory.injected; + +import java.net.URL; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.EnterpriseArchive; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +import ee.jakarta.tck.concurrent.common.context.providers.IntContextProvider; +import ee.jakarta.tck.concurrent.common.context.providers.StringContextProvider; +import ee.jakarta.tck.concurrent.framework.TestClient; +import ee.jakarta.tck.concurrent.framework.URLBuilder; +import ee.jakarta.tck.concurrent.framework.junit.anno.Assertion; +import ee.jakarta.tck.concurrent.framework.junit.anno.Challenge; +import ee.jakarta.tck.concurrent.framework.junit.anno.Common.PACKAGE; +import ee.jakarta.tck.concurrent.framework.junit.anno.Full; +import ee.jakarta.tck.concurrent.framework.junit.anno.TestName; +import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionBean; +import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionInterface; +import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionServlet; +import ee.jakarta.tck.concurrent.spec.ManagedThreadFactory.resourcedef.ManagedThreadFactoryDefinitionFullTests; +import jakarta.enterprise.concurrent.spi.ThreadContextProvider; + +@Full +@RunAsClient // Requires client testing due to multiple servlets and annotation configuration +public class ManagedThreadFactoryDefinitionInjectedFullTests extends TestClient { + + @ArquillianResource(ManagedThreadFactoryDefinitionInjectedServlet.class) + private URL baseURL; + + @ArquillianResource(ManagedThreadFactoryDefinitionOnEJBInjectedServlet.class) + private URL ejbContextURL; + + @Deployment(name = "ManagedThreadFactoryDefinitionTests") + public static EnterpriseArchive createDeployment() { + + WebArchive war = ShrinkWrap.create(WebArchive.class, "ManagedThreadFactoryDefinitionTests_web.war") + .addPackages(false, PACKAGE.CONTEXT.getPackageName(), PACKAGE.CONTEXT_PROVIDERS.getPackageName()) + .addClasses(ManagedThreadFactoryDefinitionOnEJBInjectedServlet.class, + ManagedThreadFactoryDefinitionInjectedServlet.class, ContextServiceDefinitionServlet.class) + .addAsWebInfResource(ManagedThreadFactoryDefinitionFullTests.class.getPackage(), "web.xml", "web.xml") + .addAsServiceProvider(ThreadContextProvider.class.getName(), IntContextProvider.class.getName(), + StringContextProvider.class.getName()); + + JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "ManagedThreadFactoryDefinitionInjectedTests_ejb.jar") + .addPackages(false, ManagedThreadFactoryDefinitionInjectedFullTests.class.getPackage()) + .deleteClasses(ManagedThreadFactoryDefinitionOnEJBInjectedServlet.class, + ManagedThreadFactoryDefinitionInjectedServlet.class) + .addClasses(ContextServiceDefinitionInterface.class, ContextServiceDefinitionBean.class) + .addAsManifestResource(ManagedThreadFactoryDefinitionFullTests.class.getPackage(), "ejb-jar.xml", + "ejb-jar.xml"); + + EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive.class, "ManagedThreadFactoryDefinitionInjectedTests.ear") + .addAsModules(war, jar); + + return ear; + } + + @TestName + private String testname; + + @Override + protected String getServletPath() { + return "ManagedThreadFactoryDefinitionInjectedServlet"; + } + + @Challenge(link = "https://github.com/jakartaee/concurrency/issues/226", version = "3.0.0") + @Assertion(id = "GIT:156", strategy = "ManagedThreadFactoryDefinition with all attributes configured") + public void testManagedThreadFactoryDefinitionAllAttributes() throws Throwable { + runTest(baseURL, testname); + } + + @Challenge(link = "https://github.com/jakartaee/concurrency/issues/226", version = "3.0.0") + @Assertion(id = "GIT:156", + strategy = "A ManagedThreadFactoryDefinition defined on an EJB with all attributes configured enforces priority and propagates context.") + public void testManagedThreadFactoryDefinitionAllAttributesEJB() throws Throwable { + URLBuilder requestURL = URLBuilder.get().withBaseURL(ejbContextURL) + .withPaths("ManagedThreadFactoryDefinitionOnEJBInjectedServlet").withTestName(testname); + runTest(requestURL); + } + + @Assertion(id = "GIT:156", strategy = "ManagedThreadFactoryDefinition with minimal attributes configured") + public void testManagedThreadFactoryDefinitionDefaults() throws Throwable { + runTest(baseURL, testname); + } + + @Assertion(id = "GIT:156", + strategy = "ManagedThreadFactoryDefinition defined on an EJB with minimal attributes creates threads with normal priority" + + " and uses java:comp/DefaultContextService to determine context propagation and clearing") + public void testManagedThreadFactoryDefinitionDefaultsEJB() throws Throwable { + URLBuilder requestURL = URLBuilder.get().withBaseURL(ejbContextURL) + .withPaths("ManagedThreadFactoryDefinitionOnEJBInjectedServlet").withTestName(testname); + runTest(requestURL); + } + + //@Challenge(link = "https://github.com/jakartaee/concurrency/issues/226", version = "3.0.0") + @Assertion(id = "GIT:156", + strategy = "ManagedThreadFactory can be supplied to a ForkJoinPool, which manages thread context and priority as configured") + public void testParallelStreamBackedByManagedThreadFactory() throws Throwable { + runTest(baseURL, testname); + } + + //@Challenge(link = "https://github.com/jakartaee/concurrency/issues/226", version = "3.0.0") + @Assertion(id = "GIT:156", + strategy = "ManagedThreadFactoryDefinition defined on an EJB is supplied to a ForkJoinPool" + + " and uses java:comp/DefaultContextService to determine context propagation and priority.") + public void testParallelStreamBackedByManagedThreadFactoryEJB() throws Throwable { + URLBuilder requestURL = URLBuilder.get().withBaseURL(ejbContextURL) + .withPaths("ManagedThreadFactoryDefinitionOnEJBInjectedServlet").withTestName(testname); + runTest(requestURL); + } +} diff --git a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedServlet.java b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedServlet.java new file mode 100644 index 00000000..474f8d8a --- /dev/null +++ b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionInjectedServlet.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package ee.jakarta.tck.concurrent.spec.ManagedThreadFactory.injected; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import ee.jakarta.tck.concurrent.common.context.IntContext; +import ee.jakarta.tck.concurrent.common.context.StringContext; +import ee.jakarta.tck.concurrent.framework.TestServlet; +import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionServlet; +import jakarta.annotation.Resource; +import jakarta.enterprise.concurrent.ContextService; +import jakarta.enterprise.concurrent.ManagedThreadFactory; +import jakarta.enterprise.concurrent.ManagedThreadFactoryDefinition; +import jakarta.servlet.annotation.WebServlet; +import jakarta.transaction.Status; +import jakarta.transaction.UserTransaction; + +/** + * Same tests as in {@link ManagedThreadFactoryDefinitionServlet}, just with + * injected {@link ManagedThreadFactory}. + * + * @ContextServiceDefinitions are defined under + * * {@link ContextServiceDefinitionServlet} + */ +@ManagedThreadFactoryDefinition(name = "java:app/concurrent/ThreadFactoryInjA", context = "java:app/concurrent/ContextA", priority = 4) +@ManagedThreadFactoryDefinition(name = "java:comp/concurrent/ThreadFactoryInjB") +@WebServlet("/ManagedThreadFactoryDefinitionServlet") +public class ManagedThreadFactoryDefinitionInjectedServlet extends TestServlet { + private static final long serialVersionUID = 1L; + private static final long MAX_WAIT_SECONDS = TimeUnit.MINUTES.toSeconds(2); + + @Resource(lookup = "java:app/concurrent/ThreadFactoryInjA") + private ManagedThreadFactory threadFactoryInjA; + + @Resource(lookup = "java:comp/concurrent/ThreadFactoryInjB") + private ManagedThreadFactory threadFactoryInjB; + + /** + * A ManagedThreadFactoryDefinition with all attributes configured enforces + * priority and propagates context. + */ + public void testManagedThreadFactoryDefinitionAllAttributes() throws Throwable { + try { + IntContext.set(161); + StringContext.set("testManagedThreadFactoryDefinitionAllAttributes-1"); + IntContext.set(162); + StringContext.set("testManagedThreadFactoryDefinitionAllAttributes-2"); + + Thread thread1 = threadFactoryInjA.newThread(() -> { + }); + assertEquals(thread1.getPriority(), 4, "New threads must be created with the priority that is specified on " + + "ManagedThreadFactoryDefinition"); + + BlockingQueue results = new LinkedBlockingQueue<>(); + + threadFactoryInjA.newThread(() -> { + results.add(Thread.currentThread().getPriority()); + results.add(StringContext.get()); + results.add(IntContext.get()); + try { + results.add(InitialContext.doLookup("java:app/concurrent/ContextA")); + } catch (Throwable x) { + results.add(x); + } + }).start(); + + assertEquals(results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS), 4, + "ManagedThreadFactory must start threads with the configured priority."); + + assertEquals(results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS), "", + "Third-party context type StringContext must be cleared from thread " + + "per ManagedThreadFactoryDefinition and ContextServiceDefinition configuration."); + + assertEquals(results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS), 161, + "Third-party context type IntContext must be propagated to thread " + + "per ManagedThreadFactoryDefinition and ContextServiceDefinition configuration " + + "based on the thread context at the time the ManagedThreadFactory was looked up."); + + Object lookupResult = results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS); + if (lookupResult instanceof Throwable throwable) { + throw new AssertionError().initCause(throwable); + } + assertTrue(lookupResult instanceof ContextService, "Application context must be propagated to thread " + + "per ManagedThreadFactoryDefinition and ContextServiceDefinition configuration."); + } finally { + IntContext.set(0); + StringContext.set(""); + } + } + + /** + * ManagedThreadFactoryDefinition with minimal attributes creates threads with + * normal priority and uses java:comp/DefaultContextService to determine context + * propagation and clearing. + */ + public void testManagedThreadFactoryDefinitionDefaults() throws Throwable { + + CountDownLatch blocker = new CountDownLatch(1); + CountDownLatch allThreadsRunning = new CountDownLatch(2); + CompletableFuture lookupTaskResult = new CompletableFuture<>(); + CompletableFuture txTaskResult = new CompletableFuture<>(); + + Runnable lookupTask = () -> { + try { + allThreadsRunning.countDown(); + blocker.await(MAX_WAIT_SECONDS * 5, TimeUnit.SECONDS); + lookupTaskResult.complete(InitialContext.doLookup("java:comp/concurrent/ContextC")); + } catch (Throwable x) { + txTaskResult.completeExceptionally(x); + } + }; + + Runnable txTask = () -> { + try { + allThreadsRunning.countDown(); + UserTransaction trans = InitialContext.doLookup("java:comp/UserTransaction"); + int initialStatus = trans.getStatus(); + trans.begin(); + try { + blocker.await(MAX_WAIT_SECONDS * 5, TimeUnit.SECONDS); + } finally { + trans.rollback(); + } + txTaskResult.complete(initialStatus); + } catch (Throwable x) { + txTaskResult.completeExceptionally(x); + } + }; + + try { + threadFactoryInjB.newThread(lookupTask).start(); + threadFactoryInjB.newThread(txTask).start(); + + assertTrue(allThreadsRunning.await(MAX_WAIT_SECONDS, TimeUnit.SECONDS), + "ManagedThreadFactory threads must start running."); + + blocker.countDown(); + + Object result; + + result = lookupTaskResult.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS); + if (result instanceof Throwable) + throw new AssertionError().initCause((Throwable) result); + assertTrue(result instanceof ContextService, "Application context must be propagated to first thread " + + "per java:comp/concurrent/ThreadFactoryInjB configuration."); + + result = txTaskResult.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS); + if (result instanceof Throwable throwable) { + throw new AssertionError().initCause(throwable); + } + assertEquals(result, Status.STATUS_NO_TRANSACTION, + "Transaction context must be cleared from async Callable task " + + "per java:comp/concurrent/ThreadFactoryInjB configuration."); + } finally { + IntContext.set(0); + blocker.countDown(); + } + } + + /** + * ManagedThreadFactory can be supplied to a ForkJoinPool, causing ForkJoinPool + * tasks to run with the thread context and priority as configured. + */ + public void testParallelStreamBackedByManagedThreadFactory() throws Throwable { + ForkJoinPool fj = null; + try { + IntContext.set(1000); + StringContext.set("testParallelStreamBackedByManagedThreadFactory-1"); + + IntContext.set(2000); + StringContext.set("testParallelStreamBackedByManagedThreadFactory-2"); + + fj = new ForkJoinPool(4, threadFactoryInjA, null, false); + + IntContext.set(3000); + StringContext.set("testParallelStreamBackedByManagedThreadFactory-3"); + + ForkJoinTask> task = fj.submit(() -> { + return Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9).parallelStream().map(num -> { + assertEquals(StringContext.get(), "", + "Third-party context type StringContext must be cleared on " + "ForkJoin thread."); + try { + assertNotNull(InitialContext.doLookup("java:app/concurrent/ContextA"), + "Application context must be propagated to ForkJoin thread"); + } catch (NamingException x) { + throw new CompletionException(x); + } + return num * Thread.currentThread().getPriority() + IntContext.get(); + }).reduce(Integer::sum); + }); + + Optional result = task.join(); + assertEquals(result.get(), Integer.valueOf(9180), + "Third-party context type IntContext must propagated to ForkJoin threads " + + "(thousands digit should be 9) and thread priority (4) must be enforced " + + "on ForkJoin threads (hundreds/tens/ones digits must be 4x5x9=180) " + + "per configuration of the ManagedThreadFactoryDefinition and ContextServiceDefinition."); + } finally { + IntContext.set(0); + StringContext.set(null); + if (fj != null) + fj.shutdown(); + } + } +} diff --git a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionOnEJBInjectedServlet.java b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionOnEJBInjectedServlet.java new file mode 100644 index 00000000..a1162b9d --- /dev/null +++ b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/injected/ManagedThreadFactoryDefinitionOnEJBInjectedServlet.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package ee.jakarta.tck.concurrent.spec.ManagedThreadFactory.injected; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import ee.jakarta.tck.concurrent.common.context.IntContext; +import ee.jakarta.tck.concurrent.common.context.StringContext; +import ee.jakarta.tck.concurrent.framework.TestServlet; +import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionInterface; +import ee.jakarta.tck.concurrent.spec.ManagedThreadFactory.resourcedef.ManagedThreadFactoryDefinitionInterface; +import jakarta.annotation.Resource; +import jakarta.ejb.EJB; +import jakarta.enterprise.concurrent.ContextService; +import jakarta.enterprise.concurrent.ManagedThreadFactory; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.transaction.Status; +import jakarta.transaction.UserTransaction; + +@WebServlet("/ManagedThreadFactoryDefinitionOnEJBServlet") +public class ManagedThreadFactoryDefinitionOnEJBInjectedServlet extends TestServlet { + private static final long serialVersionUID = 1L; + private static final long MAX_WAIT_SECONDS = TimeUnit.MINUTES.toSeconds(2); + + @Resource + private UserTransaction tx; + + @EJB + private ManagedThreadFactoryDefinitionInterface managedThreadFactoryDefinitionBean; + + // Needed to initialize the ContextServiceDefinitions + @EJB + private ContextServiceDefinitionInterface contextServiceDefinitionBean; + + @Resource(lookup = "java:app/concurrent/EJBThreadFactoryA") + private ManagedThreadFactory threadFactoryA; + + @Resource(lookup = "java:comp/concurrent/EJBThreadFactoryB") + private ManagedThreadFactory threadFactoryB; + + @Override + public void init() throws ServletException { + try { + managedThreadFactoryDefinitionBean.doLookup("java:module/concurrent/ContextB"); + } catch (NamingException e) { + throw new ServletException(e); + } + } + + /** + * A ManagedThreadFactoryDefinition defined on an EJB with all attributes + * configured enforces priority and propagates context. + */ + public void testManagedThreadFactoryDefinitionAllAttributesEJB() throws Throwable { + try { + IntContext.set(161); + StringContext.set("testManagedThreadFactoryDefinitionAllAttributesEJB-1"); + + IntContext.set(162); + StringContext.set("testManagedThreadFactoryDefinitionAllAttributesEJB-2"); + + Thread thread1 = threadFactoryA.newThread(() -> { + }); + assertEquals(thread1.getPriority(), 4, "New threads must be created with the priority that is specified on " + + "ManagedThreadFactoryDefinition"); + + BlockingQueue results = new LinkedBlockingQueue<>(); + + threadFactoryA.newThread(() -> { + results.add(Thread.currentThread().getPriority()); + results.add(StringContext.get()); + results.add(IntContext.get()); + try { + results.add(InitialContext.doLookup("java:app/concurrent/ContextA")); + } catch (Throwable x) { + results.add(x); + } + }).start(); + + assertEquals(results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS), Integer.valueOf(4), + "ManagedThreadFactory must start threads with the configured priority."); + + assertEquals(results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS), "", + "Third-party context type StringContext must be cleared from thread " + + "per ManagedThreadFactoryDefinition and ContextServiceDefinition configuration."); + + assertEquals(results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS), Integer.valueOf(161), + "Third-party context type IntContext must be propagated to thread " + + "per ManagedThreadFactoryDefinition and ContextServiceDefinition configuration " + + "based on the thread context at the time the ManagedThreadFactory was looked up."); + + Object lookupResult = results.poll(MAX_WAIT_SECONDS, TimeUnit.SECONDS); + if (lookupResult instanceof Throwable) + throw new AssertionError().initCause((Throwable) lookupResult); + assertTrue(lookupResult instanceof ContextService, "Application context must be propagated to thread " + + "per ManagedThreadFactoryDefinition and ContextServiceDefinition configuration."); + } finally { + IntContext.set(0); + StringContext.set(""); + } + } + + /** + * ManagedThreadFactoryDefinition defined on an EJB with minimal attributes + * creates threads with normal priority and uses java:comp/DefaultContextService + * to determine context propagation and clearing. + */ + public void testManagedThreadFactoryDefinitionDefaultsEJB() throws Throwable { + CountDownLatch blocker = new CountDownLatch(1); + CountDownLatch allThreadsRunning = new CountDownLatch(2); + CompletableFuture lookupTaskResult = new CompletableFuture<>(); + CompletableFuture txTaskResult = new CompletableFuture<>(); + + Runnable lookupTask = () -> { + try { + allThreadsRunning.countDown(); + blocker.await(MAX_WAIT_SECONDS * 5, TimeUnit.SECONDS); + lookupTaskResult.complete(threadFactoryB); + } catch (Throwable x) { + lookupTaskResult.completeExceptionally(x); + } + }; + + Runnable txTask = () -> { + try { + allThreadsRunning.countDown(); + UserTransaction trans = InitialContext.doLookup("java:comp/UserTransaction"); + int initialStatus = trans.getStatus(); + trans.begin(); + try { + blocker.await(MAX_WAIT_SECONDS * 5, TimeUnit.SECONDS); + } finally { + trans.rollback(); + } + txTaskResult.complete(initialStatus); + } catch (Throwable x) { + txTaskResult.completeExceptionally(x); + } + }; + + try { + threadFactoryB.newThread(lookupTask).start(); + threadFactoryB.newThread(txTask).start(); + + assertTrue(allThreadsRunning.await(MAX_WAIT_SECONDS, TimeUnit.SECONDS), + "ManagedThreadFactory threads must start running."); + + blocker.countDown(); + + Object result; + + result = txTaskResult.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS); + if (result instanceof Throwable) { + throw new AssertionError().initCause((Throwable) result); + } + assertEquals(result, Status.STATUS_NO_TRANSACTION, + "Transaction context must be cleared from async Callable task " + + "per java:comp/concurrent/EJBThreadFactoryB configuration."); + + result = lookupTaskResult.get(MAX_WAIT_SECONDS, TimeUnit.SECONDS); + if (result instanceof Throwable) + throw new AssertionError().initCause((Throwable) result); + assertTrue(result instanceof ManagedThreadFactory, "Application context must be propagated to first thread " + + "per java:comp/concurrent/EJBThreadFactoryB configuration."); + } finally { + IntContext.set(0); + blocker.countDown(); + } + } + + /** + * ManagedThreadFactory defined on an EJB can be supplied to a ForkJoinPool, + * causing ForkJoinPool tasks to run with the thread context and priority as + * configured. + */ + public void testParallelStreamBackedByManagedThreadFactoryEJB() { + ForkJoinPool fj = null; + try { + IntContext.set(1000); + StringContext.set("testParallelStreamBackedByManagedThreadFactoryEJB-1"); + + IntContext.set(2000); + StringContext.set("testParallelStreamBackedByManagedThreadFactoryEJB-2"); + + fj = new ForkJoinPool(4, threadFactoryA, null, false); + + IntContext.set(3000); + StringContext.set("testParallelStreamBackedByManagedThreadFactoryEJB-3"); + + ForkJoinTask> task = fj.submit(() -> { + return Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9).parallelStream().map(num -> { + assertEquals(StringContext.get(), "", + "Third-party context type StringContext must be cleared on " + "ForkJoin thread."); + try { + assertNotNull(InitialContext.doLookup("java:app/concurrent/ContextA"), + "Application context must be propagated to ForkJoin thread"); + } catch (NamingException x) { + throw new CompletionException(x); + } + return num * Thread.currentThread().getPriority() + IntContext.get(); + }).reduce(Integer::sum); + }); + + Optional result = task.join(); + assertEquals(result.get(), Integer.valueOf(9180), + "Third-party context type IntContext must propagated to ForkJoin threads " + + "(thousands digit should be 9) and thread priority (4) must be enforced " + + "on ForkJoin threads (hundreds/tens/ones digits must be 4x5x9=180) " + + "per configuration of the ManagedThreadFactoryDefinition and ContextServiceDefinition."); + } finally { + IntContext.set(0); + StringContext.set(null); + if (fj != null) + fj.shutdown(); + } + } +} diff --git a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/resourcedef/ManagedThreadFactoryDefinitionServlet.java b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/resourcedef/ManagedThreadFactoryDefinitionServlet.java index 2eef32a9..1350351a 100644 --- a/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/resourcedef/ManagedThreadFactoryDefinitionServlet.java +++ b/tck/src/main/java/ee/jakarta/tck/concurrent/spec/ManagedThreadFactory/resourcedef/ManagedThreadFactoryDefinitionServlet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -37,7 +37,6 @@ import ee.jakarta.tck.concurrent.common.context.StringContext; import ee.jakarta.tck.concurrent.framework.TestServlet; import ee.jakarta.tck.concurrent.spec.ContextService.contextPropagate.ContextServiceDefinitionServlet; -import jakarta.annotation.Resource; import jakarta.enterprise.concurrent.ContextService; import jakarta.enterprise.concurrent.ManagedThreadFactory; import jakarta.enterprise.concurrent.ManagedThreadFactoryDefinition; @@ -56,9 +55,6 @@ public class ManagedThreadFactoryDefinitionServlet extends TestServlet { private static final long serialVersionUID = 1L; private static final long MAX_WAIT_SECONDS = TimeUnit.MINUTES.toSeconds(2); - @Resource - private UserTransaction tx; - /** * A ManagedThreadFactoryDefinition with all attributes configured enforces * priority and propagates context.