Skip to content

Commit 2841296

Browse files
committed
Support saturating the ForkJoinPool via a property.
The JUnit Platform now supports saturating the ForkJoinPool via a property. Enabling this property will limit the number of concurrently executing tests will to the configured parallelism even when some are blocked. For JUnit Jupiter this can be enabled by through the `junit.jupiter.execution.parallel.config.dynamic.saturate` and `junit.jupiter.execution.parallel.config.fixed.saturate` configuration properties. Fixes: junit-team#2545 Fixes: junit-team#1858 Fixes: junit-team#3026
1 parent e98b714 commit 2841296

File tree

6 files changed

+122
-18
lines changed

6 files changed

+122
-18
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.9.1.adoc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ on GitHub.
2323

2424
==== New Features and Improvements
2525

26-
* ❓
27-
26+
* Support saturating the ForkJoinPool via a property.
2827

2928
[[release-notes-5.9.1-junit-jupiter]]
3029
=== JUnit Jupiter
@@ -47,8 +46,9 @@ on GitHub.
4746

4847
==== New Features and Improvements
4948

50-
* ❓
51-
49+
* Added `junit.jupiter.execution.parallel.config.dynamic.saturate` and
50+
`junit.jupiter.execution.parallel.config.fixed.saturate` to limit the concurrently
51+
executing tests to the maximum desired parallelism.
5252

5353
[[release-notes-5.9.1-junit-vintage]]
5454
=== JUnit Vintage

documentation/src/docs/asciidoc/user-guide/writing-tests.adoc

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2332,12 +2332,14 @@ strategy with a factor of `1`. Consequently, the desired parallelism will be equ
23322332
number of available processors/cores.
23332333

23342334
.Parallelism does not imply maximum number of concurrent threads
2335-
NOTE: JUnit Jupiter does not guarantee that the number of concurrently executing tests
2335+
NOTE: By default JUnit Jupiter does not guarantee that the number of concurrently executing tests
23362336
will not exceed the configured parallelism. For example, when using one of the
23372337
synchronization mechanisms described in the next section, the `ForkJoinPool` that is used
23382338
behind the scenes may spawn additional threads to ensure execution continues with
2339-
sufficient parallelism. Thus, if you require such guarantees in a test class, please use
2340-
your own means of controlling concurrency.
2339+
sufficient parallelism.
2340+
By setting the optional configuration parameters `junit.jupiter.execution.parallel.config.dynamic.saturate`
2341+
or `junit.jupiter.execution.parallel.config.fixed.saturate` to `true` the number of
2342+
concurrently executing tests will not exceed the configured parallelism.
23412343

23422344
[[writing-tests-parallel-execution-config-properties]]
23432345
===== Relevant properties
@@ -2384,11 +2386,28 @@ The following table lists relevant properties for configuring parallel execution
23842386
| a positive decimal number
23852387
| ```1.0```
23862388

2389+
| ```junit.jupiter.execution.parallel.config.dynamic.saturate```
2390+
| Limit the concurrently executing tests to the maximum parallelism desired parallelism
2391+
for the ```dynamic``` configuration strategy.
2392+
|
2393+
* `true`
2394+
* `false`
2395+
| ```false```
2396+
23872397
| ```junit.jupiter.execution.parallel.config.fixed.parallelism```
23882398
| Desired parallelism for the ```fixed``` configuration strategy
23892399
| a positive integer
23902400
| no default value
23912401

2402+
2403+
| ```junit.jupiter.execution.parallel.config.fixed.saturate```
2404+
| Limit the concurrently executing tests to the maximum parallelism desired parallelism
2405+
for the ```fixed``` configuration strategy.
2406+
|
2407+
* `true`
2408+
* `false`
2409+
| ```false```
2410+
23922411
| ```junit.jupiter.execution.parallel.config.custom.class```
23932412
| Fully qualified class name of the _ParallelExecutionConfigurationStrategy_ to be
23942413
used for the ```custom``` configuration strategy

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import static org.apiguardian.api.API.Status.STABLE;
1616
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME;
1717
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME;
18+
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME;
1819
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_PARALLELISM_PROPERTY_NAME;
20+
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_SATURATE_PROPERTY_NAME;
1921
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_STRATEGY_PROPERTY_NAME;
2022

2123
import org.apiguardian.api.API;
@@ -166,6 +168,19 @@ public final class Constants {
166168
public static final String PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX
167169
+ CONFIG_FIXED_PARALLELISM_PROPERTY_NAME;
168170

171+
/**
172+
* Property name used to limit the concurrently executing tests to the
173+
* maximum parallelism desired parallelism for the {@code fixed}
174+
* configuration strategy: {@value}
175+
*
176+
* <p>Value must either {@code true} or {@code false}; defaults to {@code false}.
177+
*
178+
* @since 5.9.1
179+
*/
180+
@API(status = EXPERIMENTAL, since = "5.9.1")
181+
public static final String PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX
182+
+ CONFIG_FIXED_SATURATE_PROPERTY_NAME;
183+
169184
/**
170185
* Property name used to set the factor to be multiplied with the number of
171186
* available processors/cores to determine the desired parallelism for the
@@ -179,6 +194,19 @@ public final class Constants {
179194
public static final String PARALLEL_CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX
180195
+ CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME;
181196

197+
/**
198+
* Property name used to limit the concurrently executing tests to the
199+
* maximum parallelism desired parallelism for the {@code dynamic}
200+
* configuration strategy: {@value}
201+
*
202+
* <p>Value must either {@code true} or {@code false}; defaults to {@code false}.
203+
*
204+
* @since 5.9.1
205+
*/
206+
@API(status = EXPERIMENTAL, since = "5.9.1")
207+
public static final String PARALLEL_CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME = PARALLEL_CONFIG_PREFIX
208+
+ CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME;
209+
182210
/**
183211
* Property name used to specify the fully qualified class name of the
184212
* {@link ParallelExecutionConfigurationStrategy} to be used for the

junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
package org.junit.platform.engine.support.hierarchical;
1212

13+
import java.util.concurrent.ForkJoinPool;
14+
import java.util.function.Predicate;
15+
1316
/**
1417
* @since 1.3
1518
*/
@@ -20,14 +23,16 @@ class DefaultParallelExecutionConfiguration implements ParallelExecutionConfigur
2023
private final int maxPoolSize;
2124
private final int corePoolSize;
2225
private final int keepAliveSeconds;
26+
private final Predicate<? super ForkJoinPool> saturate;
2327

2428
DefaultParallelExecutionConfiguration(int parallelism, int minimumRunnable, int maxPoolSize, int corePoolSize,
25-
int keepAliveSeconds) {
29+
int keepAliveSeconds, boolean saturate) {
2630
this.parallelism = parallelism;
2731
this.minimumRunnable = minimumRunnable;
2832
this.maxPoolSize = maxPoolSize;
2933
this.corePoolSize = corePoolSize;
3034
this.keepAliveSeconds = keepAliveSeconds;
35+
this.saturate = saturate ? __ -> true : null;
3136
}
3237

3338
@Override
@@ -55,4 +60,8 @@ public int getKeepAliveSeconds() {
5560
return keepAliveSeconds;
5661
}
5762

63+
@Override
64+
public Predicate<? super ForkJoinPool> getSaturatePredicate() {
65+
return saturate;
66+
}
5867
}

junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter
4242
() -> new JUnitException(String.format("Configuration parameter '%s' must be set",
4343
CONFIG_FIXED_PARALLELISM_PROPERTY_NAME)));
4444

45+
boolean saturate = configurationParameters.get(CONFIG_FIXED_SATURATE_PROPERTY_NAME,
46+
Boolean::valueOf).orElse(false);
47+
4548
return new DefaultParallelExecutionConfiguration(parallelism, parallelism, 256 + parallelism, parallelism,
46-
KEEP_ALIVE_SECONDS);
49+
KEEP_ALIVE_SECONDS, saturate);
4750
}
4851
},
4952

@@ -65,8 +68,11 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter
6568
int parallelism = Math.max(1,
6669
factor.multiply(BigDecimal.valueOf(Runtime.getRuntime().availableProcessors())).intValue());
6770

71+
boolean saturate = configurationParameters.get(CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME,
72+
Boolean::valueOf).orElse(false);
73+
6874
return new DefaultParallelExecutionConfiguration(parallelism, parallelism, 256 + parallelism, parallelism,
69-
KEEP_ALIVE_SECONDS);
75+
KEEP_ALIVE_SECONDS, saturate);
7076
}
7177
},
7278

@@ -114,6 +120,20 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter
114120
*/
115121
public static final String CONFIG_FIXED_PARALLELISM_PROPERTY_NAME = "fixed.parallelism";
116122

123+
/**
124+
* Property name used to enable saturation of the underlying fork join pool
125+
* for the {@link #FIXED} configuration strategy.
126+
*
127+
* <p>When set to {@code true} the maximum number of concurrent threads will
128+
* not exceed the desired parallelism, even when some threads are blocked.
129+
*
130+
* <p>Value must either {@code true} or {@code false}; defaults to {@code false}.
131+
*
132+
* @see #FIXED
133+
*/
134+
@API(status = EXPERIMENTAL, since = "1.9.1")
135+
public static final String CONFIG_FIXED_SATURATE_PROPERTY_NAME = "fixed.saturate";
136+
117137
/**
118138
* Property name of the factor used to determine the desired parallelism for the
119139
* {@link #DYNAMIC} configuration strategy.
@@ -124,6 +144,20 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter
124144
*/
125145
public static final String CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = "dynamic.factor";
126146

147+
/**
148+
* Property name used to enable saturation of the underlying fork join pool
149+
* for the {@link #DYNAMIC} configuration strategy.
150+
*
151+
* <p>When set to {@code true} the maximum number of concurrent threads will
152+
* not exceed the desired parallelism, even when some threads are blocked.
153+
*
154+
* <p>Value must either {@code true} or {@code false}; defaults to {@code false}.
155+
*
156+
* @see #DYNAMIC
157+
*/
158+
@API(status = EXPERIMENTAL, since = "1.9.1")
159+
public static final String CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME = "dynamic.saturate";
160+
127161
/**
128162
* Property name used to specify the fully qualified class name of the
129163
* {@link ParallelExecutionConfigurationStrategy} to be used by the

platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
import static org.mockito.Mockito.when;
1818

1919
import java.util.Optional;
20-
import java.util.concurrent.ForkJoinPool;
21-
import java.util.function.Predicate;
2220

2321
import org.junit.jupiter.api.BeforeEach;
2422
import org.junit.jupiter.api.Test;
@@ -54,6 +52,16 @@ void fixedStrategyCreatesValidConfiguration() {
5452
assertThat(configuration.getSaturatePredicate()).isNull();
5553
}
5654

55+
@Test
56+
void fixedSaturateStrategyCreatesValidConfiguration() {
57+
when(configParams.get("fixed.parallelism")).thenReturn(Optional.of("42"));
58+
when(configParams.get("fixed.saturate")).thenReturn(Optional.of("true"));
59+
60+
ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.FIXED;
61+
var configuration = strategy.createConfiguration(configParams);
62+
assertThat(configuration.getSaturatePredicate()).isNotNull();
63+
}
64+
5765
@Test
5866
void dynamicStrategyCreatesValidConfiguration() {
5967
when(configParams.get("dynamic.factor")).thenReturn(Optional.of("2.0"));
@@ -70,6 +78,17 @@ void dynamicStrategyCreatesValidConfiguration() {
7078
assertThat(configuration.getSaturatePredicate()).isNull();
7179
}
7280

81+
@Test
82+
void dynamicSaturateStrategyCreatesValidConfiguration() {
83+
when(configParams.get("dynamic.factor")).thenReturn(Optional.of("2.0"));
84+
when(configParams.get("dynamic.saturate")).thenReturn(Optional.of("true"));
85+
86+
ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC;
87+
var configuration = strategy.createConfiguration(configParams);
88+
89+
assertThat(configuration.getSaturatePredicate()).isNotNull();
90+
}
91+
7392
@Test
7493
void customStrategyCreatesValidConfiguration() {
7594
when(configParams.get("custom.class")).thenReturn(
@@ -183,12 +202,7 @@ void customStrategyThrowsExceptionWhenClassDoesNotExist() {
183202
static class CustomParallelExecutionConfigurationStrategy implements ParallelExecutionConfigurationStrategy {
184203
@Override
185204
public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) {
186-
return new DefaultParallelExecutionConfiguration(1, 2, 3, 4, 5) {
187-
@Override
188-
public Predicate<? super ForkJoinPool> getSaturatePredicate() {
189-
return __ -> true;
190-
}
191-
};
205+
return new DefaultParallelExecutionConfiguration(1, 2, 3, 4, 5, true);
192206
}
193207
}
194208

0 commit comments

Comments
 (0)