Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6b87dd2
Setup Concurrency TCK
rzo1 Apr 1, 2026
e9c554d
Implement @Asynchronous(runAt=@Schedule(...)) for Jakarta Concurrency…
rzo1 Apr 2, 2026
ffe731c
Add virtual thread support and DD virtual attribute for Concurrency 3.1
rzo1 Apr 2, 2026
6a237b6
Remove unused code from Concurrency 3.1 implementation
rzo1 Apr 2, 2026
41dab0e
Add CronTrigger tests verifying ZonedTrigger works with existing sche…
rzo1 Apr 2, 2026
bdc3fe9
Fix scheduled async interceptor to call setFuture before ctx.proceed
rzo1 Apr 2, 2026
a830d73
Fix scheduled async lifecycle: stop on non-null return, reject invali…
rzo1 Apr 2, 2026
8447b7f
Use bean-discovery-mode=annotated in Concurrency TCK archive processor
rzo1 Apr 2, 2026
020017b
Add multiple schedules and maxAsync unit tests for scheduled async
rzo1 Apr 2, 2026
40716ae
Fix JNDI lookup for java:module/ and java:app/ scoped scheduled execu…
rzo1 Apr 2, 2026
b4dc7bf
Support plain ManagedExecutorService as executor for scheduled async
rzo1 Apr 2, 2026
d05eb5d
Rewrite scheduled async with manual trigger loop and context preserva…
rzo1 Apr 2, 2026
75cefe7
Use default MSES delegate for scheduled async trigger loop
rzo1 Apr 2, 2026
0439ef5
Filter Concurrency TCK to Web profile using JUnit 5 tag
rzo1 Apr 2, 2026
456ee84
Add qualifier and virtual DD element support for Concurrency 3.1
rzo1 Apr 2, 2026
a40d71b
Allow virtual thread factory to work with ForkJoinPool
rzo1 Apr 2, 2026
93da38e
Add CDI qualifier support for Concurrency 3.1 resource definitions
rzo1 Apr 3, 2026
5415cd4
Silently fall back to platform threads when virtual=true on Java 17
rzo1 Apr 3, 2026
675b2bc
Fix InvocationContext reuse in scheduled async and add missing licens…
rzo1 Apr 3, 2026
eca9218
Add regression test for scheduled async CDI interceptors
jungm Apr 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ tck/**/temp
examples/jaxrs-json-provider-jettison/temp/
transformer/jakartaee-prototype/
transformer/transformer-0.1.0-SNAPSHOT/
*.zip
*.zip

CLAUDE.md
.claude
tck-dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.openejb.arquillian.tests.concurrency;

import jakarta.annotation.Resource;
import jakarta.enterprise.concurrent.ManagedExecutorService;
import jakarta.enterprise.concurrent.ManagedScheduledExecutorService;
import jakarta.enterprise.concurrent.ManagedThreadFactory;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
* Arquillian test verifying that web.xml deployment descriptors with
* {@code <virtual>} and {@code <qualifier>} elements deploy successfully.
* This tests the SXC JAXB accessor parsing for Concurrency 3.1 DD elements.
*/
@RunWith(Arquillian.class)
public class DeploymentDescriptorConcurrencyTest {

private static final String WEB_XML =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<web-app version=\"6.1\"\n" +
" xmlns=\"https://jakarta.ee/xml/ns/jakartaee\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"https://jakarta.ee/xml/ns/jakartaee\n" +
" https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd\">\n" +
"\n" +
" <managed-thread-factory>\n" +
" <name>java:app/concurrent/DDThreadFactory</name>\n" +
" <virtual>true</virtual>\n" +
" </managed-thread-factory>\n" +
"\n" +
" <managed-executor>\n" +
" <name>java:app/concurrent/DDExecutor</name>\n" +
" <virtual>false</virtual>\n" +
" </managed-executor>\n" +
"\n" +
" <managed-scheduled-executor>\n" +
" <name>java:app/concurrent/DDScheduledExecutor</name>\n" +
" <virtual>false</virtual>\n" +
" </managed-scheduled-executor>\n" +
"\n" +
"</web-app>\n";

@Inject
private DDBean ddBean;

@Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class, "DDConcurrencyTest.war")
.addClasses(DDBean.class)
.setWebXML(new StringAsset(WEB_XML))
.addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
}

@Test
public void deploymentSucceeds() {
// If we get here, the web.xml with <virtual> parsed successfully
assertNotNull("DDBean should be injected", ddBean);
}

@Test
public void ddDefinedExecutorWorks() throws Exception {
final boolean completed = ddBean.runOnDDExecutor();
assertTrue("Task should run on DD-defined executor", completed);
}

@ApplicationScoped
public static class DDBean {

@Resource(lookup = "java:app/concurrent/DDExecutor")
private ManagedExecutorService executor;

public boolean runOnDDExecutor() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
executor.execute(latch::countDown);
return latch.await(5, TimeUnit.SECONDS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.openejb.arquillian.tests.concurrency;

import jakarta.enterprise.concurrent.Asynchronous;
import jakarta.enterprise.concurrent.ManagedScheduledExecutorDefinition;
import jakarta.enterprise.concurrent.Schedule;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
* Arquillian test that mirrors the TCK pattern of using
* {@code @ManagedScheduledExecutorDefinition} with a custom JNDI name
* and {@code @Asynchronous(executor="java:module/...", runAt=@Schedule(...))}.
*
* <p>This verifies that {@code java:module/} and {@code java:app/} scoped
* executor lookups work for scheduled async methods.</p>
*/
@RunWith(Arquillian.class)
public class ScheduledAsyncCustomExecutorTest {

@Inject
private ScheduledBeanWithCustomExecutor bean;

@Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class, "ScheduledAsyncCustomExecutorTest.war")
.addClasses(ScheduledBeanWithCustomExecutor.class)
.addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
}

@Test
public void scheduledWithModuleScopedExecutor() throws Exception {
final AtomicInteger counter = new AtomicInteger();
final CompletableFuture<Integer> future = bean.scheduledWithModuleExecutor(2, counter);

assertNotNull("Future should be returned", future);
final Integer result = future.get(15, TimeUnit.SECONDS);
assertEquals("Should complete after 2 runs", Integer.valueOf(2), result);
}

@Test
public void scheduledWithAppScopedExecutor() throws Exception {
final AtomicInteger counter = new AtomicInteger();
final CompletableFuture<Integer> future = bean.scheduledWithAppExecutor(1, counter);

assertNotNull("Future should be returned", future);
final Integer result = future.get(15, TimeUnit.SECONDS);
assertEquals("Should complete after 1 run", Integer.valueOf(1), result);
}

@ManagedScheduledExecutorDefinition(name = "java:module/concurrent/TestScheduledExecutor")
@ManagedScheduledExecutorDefinition(name = "java:app/concurrent/TestAppScheduledExecutor")
@ApplicationScoped
public static class ScheduledBeanWithCustomExecutor {

@Asynchronous(executor = "java:module/concurrent/TestScheduledExecutor",
runAt = @Schedule(cron = "* * * * * *"))
public CompletableFuture<Integer> scheduledWithModuleExecutor(final int runs, final AtomicInteger counter) {
final int count = counter.incrementAndGet();
if (count < runs) {
return null;
}
final CompletableFuture<Integer> future = Asynchronous.Result.getFuture();
future.complete(count);
return future;
}

@Asynchronous(executor = "java:app/concurrent/TestAppScheduledExecutor",
runAt = @Schedule(cron = "* * * * * *"))
public CompletableFuture<Integer> scheduledWithAppExecutor(final int runs, final AtomicInteger counter) {
final int count = counter.incrementAndGet();
if (count < runs) {
return null;
}
final CompletableFuture<Integer> future = Asynchronous.Result.getFuture();
future.complete(count);
return future;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.openejb.arquillian.tests.concurrency;

import jakarta.enterprise.concurrent.Asynchronous;
import jakarta.enterprise.concurrent.Schedule;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.assertTrue;

/**
* Arquillian integration test for {@code @Asynchronous(runAt = @Schedule(...))}
* — the scheduled recurring async method feature introduced in Jakarta Concurrency 3.1.
*/
@RunWith(Arquillian.class)
public class ScheduledAsynchronousTest {

@Inject
private ScheduledBean scheduledBean;

@Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class, "ScheduledAsynchronousTest.war")
.addClasses(ScheduledBean.class)
.addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
}

@Test
public void scheduledVoidMethodExecutesRepeatedly() throws Exception {
scheduledBean.everySecondVoid();

final boolean reached = ScheduledBean.VOID_LATCH.await(10, TimeUnit.SECONDS);
assertTrue("Scheduled void method should have been invoked at least 3 times, count: "
+ ScheduledBean.VOID_COUNTER.get(), reached);
}

@Test
public void scheduledReturningMethodExecutes() throws Exception {
final CompletableFuture<String> future = scheduledBean.everySecondReturning();

final boolean reached = ScheduledBean.RETURNING_LATCH.await(10, TimeUnit.SECONDS);
assertTrue("Scheduled returning method should have been invoked, count: "
+ ScheduledBean.RETURNING_COUNTER.get(), reached);
}

@ApplicationScoped
public static class ScheduledBean {
static final AtomicInteger VOID_COUNTER = new AtomicInteger();
static final CountDownLatch VOID_LATCH = new CountDownLatch(3);

static final AtomicInteger RETURNING_COUNTER = new AtomicInteger();
static final CountDownLatch RETURNING_LATCH = new CountDownLatch(1);

@Asynchronous(runAt = @Schedule(cron = "* * * * * *"))
public void everySecondVoid() {
VOID_COUNTER.incrementAndGet();
VOID_LATCH.countDown();
}

@Asynchronous(runAt = @Schedule(cron = "* * * * * *"))
public CompletableFuture<String> everySecondReturning() {
RETURNING_COUNTER.incrementAndGet();
RETURNING_LATCH.countDown();
return Asynchronous.Result.complete("done");
}
}
}
Loading