Skip to content

Commit

Permalink
Limit max pool size for dynamic parallel execution (#3206)
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 authored Apr 23, 2023
1 parent 918f097 commit e48bd29
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ repository on GitHub.
methods 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.
* The new `testfeed` details mode for `ConsoleLauncher` prints test execution events as
they occur in a concise format.

Expand All @@ -55,7 +56,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 @@ -76,6 +78,10 @@ repository on GitHub.
`ArgumentsProvider` and `AnnotationConsumer`.
* New `AnnotationBasedArgumentConverter` convenience base class which implements both
`ArgumentConverter` and `AnnotationConsumer`.
* 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]]
Expand Down
24 changes: 21 additions & 3 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,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 @@ -2418,8 +2420,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 @@ -2466,6 +2468,22 @@ 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.dynamic.saturate```
| Disable saturation of the underlying fork-join pool for the ```dynamic``` configuration
strategy
|
* `true`
* `false`
| ```true```

| ```junit.jupiter.execution.parallel.config.fixed.parallelism```
| Desired parallelism for the ```fixed``` configuration strategy
| a positive integer
Expand All @@ -2474,7 +2492,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 @@ -72,8 +72,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 @@ -155,12 +167,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 e48bd29

Please sign in to comment.