This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 861708449a17 CAMEL-26557: camel-util - BoundedExecutorService: a task 
of the executor runs nested work inline with the Abort policy too (#29129)
861708449a17 is described below

commit 861708449a1789ee8a312a8e5304638f2d10d374
Author: Adriano Machado <[email protected]>
AuthorDate: Wed Nov 4 03:13:36 2026 -0500

    CAMEL-26557: camel-util - BoundedExecutorService: a task of the executor 
runs nested work inline with the Abort policy too (#29129)
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../impl/DefaultExecutorServiceManagerTest.java    | 26 ++++++++-
 .../SplitterParallelAsyncProcessorIssueTest.java   | 19 +++---
 .../camel/processor/WireTapAbortPolicyTest.java    |  2 +-
 .../management/ManagedRouteRemoveWireTapTest.java  |  3 -
 .../util/concurrent/BoundedExecutorService.java    | 11 ++--
 .../concurrent/BoundedExecutorServiceTest.java     | 67 ++++++++++++++--------
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  9 +--
 .../modules/ROOT/pages/threading-model.adoc        | 11 ++--
 8 files changed, 93 insertions(+), 55 deletions(-)

diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExecutorServiceManagerTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExecutorServiceManagerTest.java
index 7f0bd2783883..71209504e21f 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExecutorServiceManagerTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultExecutorServiceManagerTest.java
@@ -42,8 +42,6 @@ import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches = 
"true",
-                          disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
 public class DefaultExecutorServiceManagerTest extends ContextTestSupport {
 
     @Test
@@ -145,6 +143,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testDefaultThreadPool() {
         ExecutorService myPool = 
context.getExecutorServiceManager().newDefaultThreadPool(this, "myPool");
         assertFalse(myPool.isShutdown());
@@ -161,6 +161,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testDefaultUnboundedQueueThreadPool() {
         ThreadPoolProfile custom = new ThreadPoolProfile("custom");
         custom.setPoolSize(10);
@@ -186,6 +188,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testDefaultNoMaxQueueThreadPool() {
         ThreadPoolProfile custom = new ThreadPoolProfile("custom");
         custom.setPoolSize(10);
@@ -211,6 +215,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testCustomDefaultThreadPool() {
         ThreadPoolProfile custom = new ThreadPoolProfile("custom");
         custom.setKeepAliveTime(20L);
@@ -279,6 +285,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testGetThreadPoolProfileInheritDefaultValues() {
         
assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));
         ThreadPoolProfile foo = new ThreadPoolProfile("foo");
@@ -296,6 +304,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testGetThreadPoolProfileInheritCustomDefaultValues() {
         ThreadPoolProfile newDefault = new ThreadPoolProfile("newDefault");
         newDefault.setKeepAliveTime(30L);
@@ -323,6 +333,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testGetThreadPoolProfileInheritCustomDefaultValues2() {
         ThreadPoolProfile newDefault = new ThreadPoolProfile("newDefault");
         // just change the max pool as the default profile should then inherit
@@ -347,6 +359,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testNewThreadPoolProfile() {
         
assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));
 
@@ -371,6 +385,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testNewThreadPoolProfileById() {
         
assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));
 
@@ -397,6 +413,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testNewThreadPoolMinMax() {
         ExecutorService pool = 
context.getExecutorServiceManager().newThreadPool(this, "Cool", 5, 10);
         assertNotNull(pool);
@@ -413,6 +431,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testNewFixedThreadPool() {
         ExecutorService pool = 
context.getExecutorServiceManager().newFixedThreadPool(this, "Cool", 5);
         assertNotNull(pool);
@@ -481,6 +501,8 @@ public class DefaultExecutorServiceManagerTest extends 
ContextTestSupport {
     }
 
     @Test
+    @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches 
= "true",
+                              disabledReason = "In case of Virtual Threads, 
ThreadPerTaskExecutor is created instead of ThreadPoolExecutor")
     public void testNewCachedThreadPool() {
         ExecutorService pool = 
context.getExecutorServiceManager().newCachedThreadPool(this, "Cool");
         assertNotNull(pool);
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/issues/SplitterParallelAsyncProcessorIssueTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/issues/SplitterParallelAsyncProcessorIssueTest.java
index ce1682224680..570a65ebf1ba 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/issues/SplitterParallelAsyncProcessorIssueTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/issues/SplitterParallelAsyncProcessorIssueTest.java
@@ -16,9 +16,8 @@
  */
 package org.apache.camel.issues;
 
-import java.util.HashSet;
-import java.util.Set;
 import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.camel.*;
 import org.apache.camel.builder.RouteBuilder;
@@ -30,7 +29,8 @@ import org.junit.jupiter.api.Test;
 
 public class SplitterParallelAsyncProcessorIssueTest extends 
ContextTestSupport {
 
-    private final Set<String> threads = new HashSet<>();
+    private final AtomicInteger inFlight = new AtomicInteger();
+    private final AtomicInteger peakInFlight = new AtomicInteger();
 
     @Test
     public void testSplitParallelProcessingMaxThreads() throws Exception {
@@ -56,10 +56,10 @@ public class SplitterParallelAsyncProcessorIssueTest 
extends ContextTestSupport
         // to avoid flaky failures on slow CI
         MockEndpoint.assertIsSatisfied(context, 30, TimeUnit.SECONDS);
 
-        log.info("{} Threads in use: {}", threads.size(), threads);
-
-        // 2 from split EIP and 2 from the async processor that uses the JDK 
ForJoinPool
-        Assertions.assertTrue(threads.size() <= 4, "Should not use more than 4 
threads, was: " + threads.size());
+        // the thread pool of the split allows 2 items at a time. More means 
that the async processor let the split
+        // start the next items before the previous ones were done, and fewer 
that a permit was lost (in virtual
+        // thread mode, every task gets a new thread, so counting thread names 
does not measure this)
+        Assertions.assertEquals(2, peakInFlight.get(), "Items processed at the 
same time");
     }
 
     @Override
@@ -86,7 +86,6 @@ public class SplitterParallelAsyncProcessorIssueTest extends 
ContextTestSupport
                     public boolean process(Exchange exchange, AsyncCallback 
callback) {
                         // Run the processing in a separate thread
                         CompletableFuture.runAsync(() -> {
-                            threads.add(Thread.currentThread().getName());
                             try {
                                 // Simulate some asynchronous processing
                                 Thread.sleep(250);
@@ -113,10 +112,10 @@ public class SplitterParallelAsyncProcessorIssueTest 
extends ContextTestSupport
                     .split()
                     .xpath("/employees/employee")
                     
.parallelProcessing().stopOnException().timeout(10000).executorService("threadPoolProfile").synchronous()
-                        .process(e -> 
threads.add(Thread.currentThread().getName()))
+                        .process(e -> 
peakInFlight.accumulateAndGet(inFlight.incrementAndGet(), Math::max))
                         .process(asyncProcessor)
-                        .process(e -> 
threads.add(Thread.currentThread().getName()))
                         .delay(250)
+                        .process(e -> inFlight.decrementAndGet())
                     .end()
                     .to("mock:split");
             }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/WireTapAbortPolicyTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/WireTapAbortPolicyTest.java
index d96527a0f8d9..5b279ab7ee47 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/WireTapAbortPolicyTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/WireTapAbortPolicyTest.java
@@ -39,7 +39,7 @@ import static org.junit.jupiter.api.Assertions.fail;
  * Wire tap unit test
  */
 @DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches = 
"true",
-                          disabledReason = "Tasks are not rejected when using 
Virtual Threads")
+                          disabledReason = "With Virtual Threads, maxQueueSize 
0 means no limit, so no task is rejected")
 @Isolated
 public class WireTapAbortPolicyTest extends ContextTestSupport {
 
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveWireTapTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveWireTapTest.java
index 5e5eead12460..99fe0f054bed 100644
--- 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveWireTapTest.java
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteRemoveWireTapTest.java
@@ -24,7 +24,6 @@ import javax.management.ObjectName;
 import org.apache.camel.ServiceStatus;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
 import org.junit.jupiter.api.condition.DisabledOnOs;
 import org.junit.jupiter.api.condition.OS;
 
@@ -33,8 +32,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@DisabledIfSystemProperty(named = "camel.threads.virtual.enabled", matches = 
"true",
-                          disabledReason = "In case of Virtual Threads, the 
thread pools are not ThreadPoolExecutor")
 @DisabledOnOs(OS.AIX)
 public class ManagedRouteRemoveWireTapTest extends ManagementTestSupport {
 
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/BoundedExecutorService.java
 
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/BoundedExecutorService.java
index 73233e97e03a..fcd3283f4b79 100644
--- 
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/BoundedExecutorService.java
+++ 
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/BoundedExecutorService.java
@@ -45,9 +45,10 @@ import java.util.concurrent.atomic.LongAdder;
  * </ul>
  * <p>
  * A task of this executor that submits more work, such as a parallel split 
that submits its items, can hold the permit
- * it would wait for. So when no permit is available it runs the work itself 
at once, with the {@code CallerRuns} and
- * {@code Block} policies, or in a new task when that gets too deep, which is 
also outside the concurrency limit. With
- * {@code Abort} it waits and is rejected as usual.
+ * it would wait for. So when no permit is available it runs the work itself 
at once, whatever the policy, or in a new
+ * task when that gets too deep, which is also outside the concurrency limit. 
Waiting would only delay the work while
+ * the task holds a permit, so a parallel split would run one item fewer at a 
time, or, with {@code Abort} and a single
+ * permit, be rejected after the timeout. The policies only apply to a 
submitter that is not a task of this executor.
  * <p>
  * <b>Caller thread blocking:</b> while waiting for a permit, the calling 
thread is blocked. When callers are virtual
  * threads this is inexpensive (the carrier thread is released). When callers 
are platform threads (e.g., HTTP server
@@ -97,8 +98,8 @@ public class BoundedExecutorService extends 
AbstractExecutorService {
             throw new RejectedExecutionException("Executor has been shut 
down");
         }
 
-        // a task of this executor can hold the permit it would wait for, so 
it must not wait
-        Integer depth = rejectedPolicy != ThreadPoolRejectedPolicy.Abort ? 
inlineDepth.get() : null;
+        // a task of this executor can hold the permit it would wait for, so 
it must not wait, whatever the policy
+        Integer depth = inlineDepth.get();
         boolean runInline = depth != null;
         boolean acquired = false;
         try {
diff --git 
a/core/camel-util/src/test/java/org/apache/camel/util/concurrent/BoundedExecutorServiceTest.java
 
b/core/camel-util/src/test/java/org/apache/camel/util/concurrent/BoundedExecutorServiceTest.java
index ffe6b8c5f47b..1660b043b14f 100644
--- 
a/core/camel-util/src/test/java/org/apache/camel/util/concurrent/BoundedExecutorServiceTest.java
+++ 
b/core/camel-util/src/test/java/org/apache/camel/util/concurrent/BoundedExecutorServiceTest.java
@@ -254,6 +254,48 @@ public class BoundedExecutorServiceTest {
         assertNestedTaskRunsInline(ThreadPoolRejectedPolicy.Block);
     }
 
+    @Test
+    public void testNestedTaskRunsInlineWithAbortPolicy() throws Exception {
+        // the wait cannot succeed, so the task was rejected after the timeout 
and a split with a single permit failed
+        assertNestedTaskRunsInline(ThreadPoolRejectedPolicy.Abort);
+    }
+
+    @Test
+    public void 
testNestedTaskRunsInlineWithAbortPolicyWhenOtherTasksHoldThePermits() throws 
Exception {
+        // a parallel split holds a permit while it submits its items, so when 
it waited for a permit it ran one item
+        // fewer at a time than the executor allows
+        var delegate = Executors.newCachedThreadPool();
+        var sized = new BoundedExecutorService(delegate, 2, 60, 
TimeUnit.SECONDS, false, ThreadPoolRejectedPolicy.Abort);
+        try {
+            CountDownLatch release = new CountDownLatch(1);
+            CountDownLatch done = new CountDownLatch(1);
+            AtomicBoolean ranInline = new AtomicBoolean();
+
+            sized.execute(() -> {
+                Thread outer = Thread.currentThread();
+                // the first item takes the other permit and keeps it
+                sized.execute(() -> {
+                    try {
+                        release.await(10, TimeUnit.SECONDS);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                });
+                // the second item runs while the first one is still running
+                sized.execute(() -> ranInline.set(Thread.currentThread() == 
outer));
+                done.countDown();
+            });
+
+            assertTrue(done.await(5, TimeUnit.SECONDS), "The second item 
waited for the first one to finish");
+            assertTrue(ranInline.get(), "The second item should run in the 
task that submitted it");
+            assertEquals(1, sized.getCallerRunsCount());
+            assertEquals(0, sized.getRejectedCount());
+            release.countDown();
+        } finally {
+            sized.shutdownNow();
+        }
+    }
+
     private void assertNestedTaskRunsInline(ThreadPoolRejectedPolicy policy) 
throws Exception {
         var delegate = Executors.newCachedThreadPool();
         var sized = new BoundedExecutorService(delegate, 1, 60, 
TimeUnit.SECONDS, false, policy);
@@ -441,31 +483,6 @@ public class BoundedExecutorServiceTest {
         }
     }
 
-    @Test
-    public void testNestedTaskIsStillRejectedWithAbortPolicy() throws 
Exception {
-        var delegate = Executors.newCachedThreadPool();
-        var sized = new BoundedExecutorService(
-                delegate, 1, 100, TimeUnit.MILLISECONDS, false, 
ThreadPoolRejectedPolicy.Abort);
-        try {
-            CountDownLatch rejected = new CountDownLatch(1);
-
-            sized.execute(() -> {
-                try {
-                    sized.execute(() -> {
-                        // never runs
-                    });
-                } catch (RejectedExecutionException e) {
-                    rejected.countDown();
-                }
-            });
-
-            assertTrue(rejected.await(5, TimeUnit.SECONDS), "Abort must fail 
the task and not run it");
-            assertEquals(0, sized.getCallerRunsCount());
-        } finally {
-            sized.shutdownNow();
-        }
-    }
-
     @Test
     public void testTaskOfAnotherExecutorStillWaits() throws Exception {
         // only a task of this executor can hold one of its permits, so others 
wait for backpressure as usual
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 4520961b6bcf..dda0dccee5f1 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -156,10 +156,11 @@ split when it had them. `SplitResult.getProcessedItems()` 
now reports the items
 === camel-util - BoundedExecutorService: a task of the executor does not wait 
for a permit
 
 With virtual threads, a task that runs in a thread pool bounded by 
`maxQueueSize` and submits more work to it, such as a
-parallel split, no longer waits for a permit when none is available. It runs 
the work itself at once, with the
-`CallerRuns` and `Block` rejected policies. Before, it could wait for a permit 
it held itself, until `keepAliveTime`
-expired, or forever with `Block`. A split with a `timeout` could lose items 
that way. A submitter outside the pool waits
-as before.
+parallel split, no longer waits for a permit when none is available. It runs 
the work itself at once, whatever the
+rejected policy. Before, it could wait for a permit it held itself, until 
`keepAliveTime` expired, or forever with
+`Block`. A split with a `timeout` could lose items that way. With `Abort`, a 
parallel split ran one item fewer at a time
+than `maxQueueSize` allows, and with `maxQueueSize` 1 it was rejected after 
`keepAliveTime`. A submitter outside the
+pool waits, and is rejected with `Abort`, as before.
 
 === camel-core - Routing Slip and Dynamic Router fire the sent event of a step 
before the next step
 
diff --git a/docs/user-manual/modules/ROOT/pages/threading-model.adoc 
b/docs/user-manual/modules/ROOT/pages/threading-model.adoc
index 445b202f8d6a..5ad100c4ed3e 100644
--- a/docs/user-manual/modules/ROOT/pages/threading-model.adoc
+++ b/docs/user-manual/modules/ROOT/pages/threading-model.adoc
@@ -329,7 +329,7 @@ message broker consumers and batch workloads where losing a 
task is unacceptable
 |===
 
 With virtual threads, a task of the pool that submits more work to the same 
pool runs it at once when no permit is
-free, for `CallerRuns` and `Block`, instead of waiting (see 
<<virtual-threads>>).
+free, whatever the policy, instead of waiting (see <<virtual-threads>>).
 
 With platform threads, these policies apply when the `ThreadPoolExecutor` work 
queue is full. With virtual threads,
 the same policies apply when the concurrency semaphore has no available 
permits (see <<virtual-threads>>).
@@ -370,10 +370,11 @@ a `RejectedExecutionException` is thrown.
 * *Block*: the caller blocks indefinitely until a permit becomes available.
 
 A task of the pool that submits more work to the same pool (for example a 
parallel `split` that submits its items)
-can hold the permit it would wait for. So with `CallerRuns` and `Block`, when 
no permit is free, that work runs at once
-on the submitting thread instead of waiting. For `Block` this applies even 
when other tasks hold the permits. Such work
-runs outside the concurrency limit, as does the work handed to a new task when 
it is nested too deeply. `Abort` is
-not affected: it still waits and is rejected as usual.
+can hold the permit it would wait for. So when no permit is free, that work 
runs at once on the submitting thread
+instead of waiting, whatever the `rejectedPolicy`, also when other tasks hold 
the permits. Waiting would only delay the
+work while the task holds a permit: a parallel `split` would run one item 
fewer at a time than the limit, or wait for
+the permit it holds itself. Such work runs outside the concurrency limit, as 
does the work handed to a new task when it
+is nested too deeply. The `rejectedPolicy` only applies to a submitter that is 
not a task of the pool.
 
 While waiting for a permit, the calling thread is blocked. When callers are 
virtual threads this is inexpensive
 (the carrier thread is released). When callers are platform threads (e.g., 
HTTP server threads) the blocked thread

Reply via email to