Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7323245
Setup Concurrency TCK
rzo1 Apr 1, 2026
cc72fe0
Implement @Asynchronous(runAt=@Schedule(...)) for Jakarta Concurrency…
rzo1 Apr 2, 2026
39fcb01
Add virtual thread support and DD virtual attribute for Concurrency 3.1
rzo1 Apr 2, 2026
621c1da
Remove unused code from Concurrency 3.1 implementation
rzo1 Apr 2, 2026
13e8d20
Add CronTrigger tests verifying ZonedTrigger works with existing sche…
rzo1 Apr 2, 2026
14393ad
Fix scheduled async interceptor to call setFuture before ctx.proceed
rzo1 Apr 2, 2026
ca24f51
Fix scheduled async lifecycle: stop on non-null return, reject invali…
rzo1 Apr 2, 2026
122e689
Use bean-discovery-mode=annotated in Concurrency TCK archive processor
rzo1 Apr 2, 2026
5412e73
Add multiple schedules and maxAsync unit tests for scheduled async
rzo1 Apr 2, 2026
89da24a
Fix JNDI lookup for java:module/ and java:app/ scoped scheduled execu…
rzo1 Apr 2, 2026
087c874
Support plain ManagedExecutorService as executor for scheduled async
rzo1 Apr 2, 2026
053a932
Rewrite scheduled async with manual trigger loop and context preserva…
rzo1 Apr 2, 2026
34be3d5
Use default MSES delegate for scheduled async trigger loop
rzo1 Apr 2, 2026
28ed8d4
Filter Concurrency TCK to Web profile using JUnit 5 tag
rzo1 Apr 2, 2026
ca32f14
Add qualifier and virtual DD element support for Concurrency 3.1
rzo1 Apr 2, 2026
514f55c
Allow virtual thread factory to work with ForkJoinPool
rzo1 Apr 2, 2026
1011751
Add CDI qualifier support for Concurrency 3.1 resource definitions
rzo1 Apr 3, 2026
3bfd837
Silently fall back to platform threads when virtual=true on Java 17
rzo1 Apr 3, 2026
7cd6ae0
Fix InvocationContext reuse in scheduled async and add missing licens…
rzo1 Apr 3, 2026
b67c204
Add regression test for scheduled async CDI interceptors
jungm Apr 12, 2026
a8562aa
Route scheduled @Asynchronous through the interceptor stack
rzo1 Apr 17, 2026
d4a9e67
Honor virtual=true on the default scheduled thread-factory path
rzo1 Apr 17, 2026
f6cfa55
Add declarative JRE gating for JUnit 4 tests
rzo1 Apr 17, 2026
4ee90ed
Add regression test for scheduled async maxAsync isolation
rzo1 Apr 17, 2026
dff924c
Isolate scheduled @Asynchronous firings from maxAsync
rzo1 Apr 17, 2026
d32d9f5
Respect DD values when merging concurrency resource annotations
rzo1 Apr 17, 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