This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch concurency in repository https://gitbox.apache.org/repos/asf/tomee.git
commit 75cefe7e61cb43e192cbc2c307a0d6d74917c800 Author: Richard Zowalla <[email protected]> AuthorDate: Thu Apr 2 20:17:21 2026 +0200 Use default MSES delegate for scheduled async trigger loop Per spec, scheduled async methods are not subject to maxAsync constraints. Use the default MSES's thread pool for the trigger loop instead of the referenced executor's pool. This prevents thread starvation when maxAsync threads are busy with blocking tasks. --- .../openejb/cdi/concurrency/AsynchronousInterceptor.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/container/openejb-core/src/main/java/org/apache/openejb/cdi/concurrency/AsynchronousInterceptor.java b/container/openejb-core/src/main/java/org/apache/openejb/cdi/concurrency/AsynchronousInterceptor.java index 263e34aaa5..5fef6731e2 100644 --- a/container/openejb-core/src/main/java/org/apache/openejb/cdi/concurrency/AsynchronousInterceptor.java +++ b/container/openejb-core/src/main/java/org/apache/openejb/cdi/concurrency/AsynchronousInterceptor.java @@ -128,7 +128,13 @@ public class AsynchronousInterceptor { final boolean isVoid = ctx.getMethod().getReturnType() == Void.TYPE; final ContextServiceImpl ctxService = (ContextServiceImpl) mses.getContextService(); final ContextServiceImpl.Snapshot snapshot = ctxService.snapshot(null); - final ScheduledExecutorService delegate = mses.getDelegate(); + + // Per spec, scheduled async methods are NOT subject to maxAsync constraints. + // Use the default MSES's delegate for the trigger loop — it is not constrained + // by the referenced executor's maxAsync setting. + final ManagedScheduledExecutorServiceImpl defaultMses = + ManagedScheduledExecutorServiceImplFactory.lookup("java:comp/DefaultManagedScheduledExecutorService"); + final ScheduledExecutorService triggerDelegate = defaultMses.getDelegate(); // A single CompletableFuture represents ALL executions in the schedule. // Per spec: "A single future represents the completion of all executions in the schedule." @@ -140,8 +146,7 @@ public class AsynchronousInterceptor { final AtomicReference<ScheduledFuture<?>> scheduledRef = new AtomicReference<>(); final AtomicReference<LastExecution> lastExecutionRef = new AtomicReference<>(); - // Schedule the first execution via the manual trigger loop - scheduleNextExecution(delegate, snapshot, ctxService, trigger, outerFuture, + scheduleNextExecution(triggerDelegate, snapshot, ctxService, trigger, outerFuture, ctx, isVoid, scheduledRef, lastExecutionRef); // Cancel the underlying scheduled task when the future completes externally
