Skip to content

Commit

Permalink
Limit max pool size for dynamic parallel execution
Browse files Browse the repository at this point in the history
This is a followup of #3044 for the `dynamic` strategy.

Fixes: #3205
  • Loading branch information
mpkorstanje committed Mar 24, 2023
1 parent ee29268 commit de89325
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ repository on GitHub.
of the former. Please refer to the
<<../user-guide/index.adoc#launcher-api-launcher-interceptors-custom, User Guide>> for
details.
* Support for limiting the `max-pool-size-factor` for parallel execution via a configuration parameter.

[[release-notes-5.10.0-M1-junit-jupiter]]
=== JUnit Jupiter
Expand All @@ -54,7 +55,8 @@ repository on GitHub.

==== Deprecations and Breaking Changes

* ❓
* The `dynamic` parallel execution strategy now allows the thread pool to be saturated by
default.

==== New Features and Improvements

Expand All @@ -68,7 +70,10 @@ repository on GitHub.
`AnnotationConsumer` interfaces.
* New `AnnotationBasedArgumentConverter` that implements both `ArgumentConverter` and
`AnnotationConsumer` interfaces.

* New `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor` configuration
parameter to set the maximum pool size factor.
* New `junit.jupiter.execution.parallel.config.dynamic.saturate` configuration
parameter to disable pool saturation.

[[release-notes-5.10.0-M1-junit-vintage]]
=== JUnit Vintage
Expand Down
16 changes: 13 additions & 3 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2354,6 +2354,8 @@ configuration parameter to one of the following options.
Computes the desired parallelism based on the number of available processors/cores
multiplied by the `junit.jupiter.execution.parallel.config.dynamic.factor`
configuration parameter (defaults to `1`).
The optional `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor`
configuration parameter can be used to limit the maximum number of threads.

`fixed`::
Uses the mandatory `junit.jupiter.execution.parallel.config.fixed.parallelism`
Expand All @@ -2377,8 +2379,8 @@ of the synchronization mechanisms described in the next section, the `ForkJoinPo
is used behind the scenes may spawn additional threads to ensure execution continues with
sufficient parallelism.
If you require such guarantees, with Java 9+, it is possible to limit the maximum number
of concurrent threads by controlling the maximum pool size of the `fixed` and `custom`
strategies.
of concurrent threads by controlling the maximum pool size of the `dynamic`, `fixed` and
`custom` strategies.

[[writing-tests-parallel-execution-config-properties]]
===== Relevant properties
Expand Down Expand Up @@ -2425,6 +2427,14 @@ The following table lists relevant properties for configuring parallel execution
| a positive decimal number
| ```1.0```

| ```junit.jupiter.execution.parallel.config.dynamic.max-pool-size.factor```
| Factor to be multiplied by the number of available processors/cores and the value of
`junit.jupiter.execution.parallel.config.dynamic.factor` to determine the desired
parallelism for the ```dynamic``` configuration strategy
| a positive decimal number, must be greater than or equal to `1.0`
| 256 + the value of `junit.jupiter.execution.parallel.config.dynamic.factor` multiplied
by the number of available processors/cores

| ```junit.jupiter.execution.parallel.config.fixed.parallelism```
| Desired parallelism for the ```fixed``` configuration strategy
| a positive integer
Expand All @@ -2433,7 +2443,7 @@ The following table lists relevant properties for configuring parallel execution
| ```junit.jupiter.execution.parallel.config.fixed.max-pool-size```
| Desired maximum pool size of the underlying fork-join pool for the ```fixed```
configuration strategy
| a positive integer, must greater than or equal to `junit.jupiter.execution.parallel.config.fixed.parallelism`
| a positive integer, must be greater than or equal to `junit.jupiter.execution.parallel.config.fixed.parallelism`
| 256 + the value of `junit.jupiter.execution.parallel.config.fixed.parallelism`

| ```junit.jupiter.execution.parallel.config.fixed.saturate```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,20 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter
int parallelism = Math.max(1,
factor.multiply(BigDecimal.valueOf(Runtime.getRuntime().availableProcessors())).intValue());

return new DefaultParallelExecutionConfiguration(parallelism, parallelism, 256 + parallelism, parallelism,
KEEP_ALIVE_SECONDS, null);
int maxPoolSize = configurationParameters.get(CONFIG_DYNAMIC_MAX_POOL_SIZE_FACTOR_PROPERTY_NAME,
BigDecimal::new).map(maxPoolSizeFactor -> {
Preconditions.condition(maxPoolSizeFactor.compareTo(BigDecimal.ONE) >= 0,
() -> String.format(
"Factor '%s' specified via configuration parameter '%s' must be greater than or equal to 1",
factor, CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME));
return maxPoolSizeFactor.multiply(BigDecimal.valueOf(parallelism)).intValue();
}).orElseGet(() -> 256 + parallelism);

boolean saturate = configurationParameters.get(CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME,
Boolean::valueOf).orElse(true);

return new DefaultParallelExecutionConfiguration(parallelism, parallelism, maxPoolSize, parallelism,
KEEP_ALIVE_SECONDS, __ -> saturate);
}
},

Expand Down Expand Up @@ -154,12 +166,46 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter
* Property name of the factor used to determine the desired parallelism for the
* {@link #DYNAMIC} configuration strategy.
*
* <p>Value must be a decimal number; defaults to {@code 1}.
* <p>Value must be a non-negative decimal number; defaults to {@code 1}.
*
* @see #DYNAMIC
*/
public static final String CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME = "dynamic.factor";

/**
* Property name of the factor used to determine the maximum pool size of
* the underlying fork-join pool for the {@link #DYNAMIC} configuration
* strategy.
*
* <p>Value must be a decimal number equal and greater than or equal to
* {@code 1}. When set the maximum pool size is calculated as
* {@code dynamic.max-pool-size-factor * dynamic.factor * Runtime.getRuntime().availableProcessors()}
* When not set the maximum pool size is calculated as
* {@code 256 + dynamic.factor * Runtime.getRuntime().availableProcessors()}
* instead.
*
* @since 1.10
* @see #DYNAMIC
*/
@API(status = EXPERIMENTAL, since = "1.10")
public static final String CONFIG_DYNAMIC_MAX_POOL_SIZE_FACTOR_PROPERTY_NAME = "dynamic.max-pool-size-factor";

/**
* Property name used to disable saturation of the underlying fork-join pool
* for the {@link #DYNAMIC} configuration strategy.
*
* <p>When set to {@code false} the underlying fork-join pool will reject
* additional tasks if all available workers are busy and the maximum
* pool-size would be exceeded.
* <p>Value must either {@code true} or {@code false}; defaults to {@code true}.
*
* @since 1.10
* @see #DYNAMIC
* @see #CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME
*/
@API(status = EXPERIMENTAL, since = "1.10")
public static final String CONFIG_DYNAMIC_SATURATE_PROPERTY_NAME = "dynamic.saturate";

/**
* Property name used to specify the fully qualified class name of the
* {@link ParallelExecutionConfigurationStrategy} to be used by the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/
class DefaultParallelExecutionConfigurationStrategyTests {

private ConfigurationParameters configParams = mock();
final ConfigurationParameters configParams = mock();

@BeforeEach
void setUp() {
Expand Down Expand Up @@ -78,7 +78,25 @@ void dynamicStrategyCreatesValidConfiguration() {
assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2);
assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + (availableProcessors * 2));
assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30);
assertThat(configuration.getSaturatePredicate()).isNull();
assertThat(configuration.getSaturatePredicate().test(null)).isTrue();
}

@Test
void dynamicSaturateStrategyCreatesValidConfiguration() {
when(configParams.get("dynamic.factor")).thenReturn(Optional.of("2.0"));
when(configParams.get("dynamic.max-pool-size-factor")).thenReturn(Optional.of("3.0"));
when(configParams.get("dynamic.saturate")).thenReturn(Optional.of("false"));

ParallelExecutionConfigurationStrategy strategy = DefaultParallelExecutionConfigurationStrategy.DYNAMIC;
var configuration = strategy.createConfiguration(configParams);

var availableProcessors = Runtime.getRuntime().availableProcessors();
assertThat(configuration.getParallelism()).isEqualTo(availableProcessors * 2);
assertThat(configuration.getCorePoolSize()).isEqualTo(availableProcessors * 2);
assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2);
assertThat(configuration.getMaxPoolSize()).isEqualTo(availableProcessors * 6);
assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30);
assertThat(configuration.getSaturatePredicate().test(null)).isFalse();
}

@Test
Expand Down

0 comments on commit de89325

Please sign in to comment.