3232import java .util .concurrent .CompletableFuture ;
3333import java .util .concurrent .CountDownLatch ;
3434import java .util .concurrent .locks .ReentrantLock ;
35+ import java .util .stream .Stream ;
3536
3637import org .jspecify .annotations .NullMarked ;
3738import org .jspecify .annotations .Nullable ;
@@ -275,6 +276,45 @@ void prioritizesTestsOverContainers() throws Exception {
275276 assertThat (child2 .startTime ).isBeforeOrEqualTo (child1 .startTime );
276277 }
277278
279+ @ Test
280+ void limitsWorkerThreadsToMaxPoolSize () throws Exception {
281+ service = new ConcurrentHierarchicalTestExecutorService (configuration (3 , 3 ));
282+
283+ CountDownLatch latch = new CountDownLatch (3 );
284+ Executable behavior = () -> {
285+ latch .countDown ();
286+ latch .await ();
287+ };
288+ var leaf1a = new TestTaskStub (ExecutionMode .CONCURRENT , behavior ) //
289+ .withName ("leaf1a" ).withLevel (3 );
290+ var leaf1b = new TestTaskStub (ExecutionMode .CONCURRENT , behavior ) //
291+ .withName ("leaf1b" ).withLevel (3 );
292+ var leaf2a = new TestTaskStub (ExecutionMode .CONCURRENT , behavior ) //
293+ .withName ("leaf2a" ).withLevel (3 );
294+ var leaf2b = new TestTaskStub (ExecutionMode .CONCURRENT , behavior ) //
295+ .withName ("leaf2b" ).withLevel (3 );
296+
297+ // When executed, there are 2 worker threads active and 1 available.
298+ // Both invokeAlls race each other trying to start 1 more.
299+ var child1 = new TestTaskStub (ExecutionMode .CONCURRENT ,
300+ () -> requiredService ().invokeAll (List .of (leaf1a , leaf1b ))) //
301+ .withName ("child1" ).withLevel (2 );
302+ var child2 = new TestTaskStub (ExecutionMode .CONCURRENT ,
303+ () -> requiredService ().invokeAll (List .of (leaf2a , leaf2b ))) //
304+ .withName ("child2" ).withLevel (2 );
305+
306+ var root = new TestTaskStub (ExecutionMode .SAME_THREAD ,
307+ () -> requiredService ().invokeAll (List .of (child1 , child2 ))) //
308+ .withName ("root" ).withLevel (1 );
309+
310+ service .submit (root ).get ();
311+
312+ assertThat (List .of (root , child1 , child2 , leaf1a , leaf1b , leaf2a , leaf2b )) //
313+ .allSatisfy (TestTaskStub ::assertExecutedSuccessfully );
314+ assertThat (Stream .of (leaf1a , leaf1b , leaf2a , leaf2b ).map (TestTaskStub ::executionThread ).distinct ()) //
315+ .hasSize (3 );
316+ }
317+
278318 private static ExclusiveResource exclusiveResource () {
279319 return new ExclusiveResource ("key" , ExclusiveResource .LockMode .READ_WRITE );
280320 }
@@ -284,7 +324,11 @@ private ConcurrentHierarchicalTestExecutorService requiredService() {
284324 }
285325
286326 private static ParallelExecutionConfiguration configuration (int parallelism ) {
287- return new DefaultParallelExecutionConfiguration (parallelism , parallelism , 256 + parallelism , parallelism , 0 ,
327+ return configuration (parallelism , 256 + parallelism );
328+ }
329+
330+ private static ParallelExecutionConfiguration configuration (int parallelism , int maxPoolSize ) {
331+ return new DefaultParallelExecutionConfiguration (parallelism , parallelism , maxPoolSize , parallelism , 0 ,
288332 __ -> true );
289333 }
290334
0 commit comments