Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
892492a
Add support for executing a single task
marcphilipp Oct 9, 2025
5c9f0f6
Verify that `invokeAll()` is only called internally
marcphilipp Oct 9, 2025
a71f232
Add support for executing children concurrently
marcphilipp Oct 9, 2025
1c703f0
Add support for executing children in same thread
marcphilipp Oct 9, 2025
c0a9d42
Always execute single child in same thread as its parent
marcphilipp Oct 9, 2025
df84bf6
Polishing
marcphilipp Oct 9, 2025
bd426ec
Use fixed thread pool
marcphilipp Oct 9, 2025
0947a89
Implement basic work stealing
marcphilipp Oct 9, 2025
58ac3d1
Polishing
marcphilipp Oct 9, 2025
a11039c
Configure timeout for all tests
marcphilipp Oct 9, 2025
58198ae
Introduce `ResourceLock.tryAcquire`
marcphilipp Oct 9, 2025
bb835e1
Acquire resource locks for tasks
marcphilipp Oct 9, 2025
67db7ba
Polish tests
marcphilipp Oct 9, 2025
4a231aa
Fix race condition
marcphilipp Oct 9, 2025
2dc1f3f
Change thread pool configuration to achieve more parallelism
marcphilipp Oct 9, 2025
b869706
Polishing
marcphilipp Oct 10, 2025
f70433b
Introduce worker leases to limit parallelism
marcphilipp Oct 10, 2025
ef2c49d
Add constructor needed by Jupiter
marcphilipp Oct 10, 2025
ff783e9
Add support for blocking inside of worker thread
marcphilipp Oct 10, 2025
752d565
Add support for submitting SAME_THREAD child tasks dynamically
marcphilipp Oct 12, 2025
0223336
Run isolated tasks last to maximize parallelism
marcphilipp Oct 12, 2025
332abe1
Polish logging
marcphilipp Oct 12, 2025
2df6b28
Stop workers sooner (without waiting for queue entries or worker lease)
marcphilipp Oct 12, 2025
c3032be
Use new implementation
marcphilipp Oct 12, 2025
5f3fd13
Improve logging pattern
marcphilipp Oct 12, 2025
d33beb5
Delete debug printing code
marcphilipp Oct 13, 2025
2264989
Prioritize children of started containers
marcphilipp Oct 13, 2025
8c9db08
Improve naming
marcphilipp Oct 13, 2025
539ef1a
Improve logging
marcphilipp Oct 13, 2025
86dfea4
Poll queue only if worker lease was available
marcphilipp Oct 13, 2025
05c45f9
Simplify tests
marcphilipp Oct 13, 2025
9d77839
Introduce configuration parameter for opting in to new implementation
marcphilipp Oct 13, 2025
0d201d1
Remove double negative
marcphilipp Oct 14, 2025
08568e4
Let `WorkerLease` implement `AutoCloseable` to simplify handling
marcphilipp Oct 14, 2025
a05279a
Use same (reverse) order for work stealing and work queue
marcphilipp Oct 14, 2025
1055dbb
Prioritize tests over containers
marcphilipp Oct 14, 2025
a545da0
Move `invokeAll()`` code to `WorkerThread`
marcphilipp Oct 14, 2025
1196b17
Hold back one task for the current worker when forking
marcphilipp Oct 14, 2025
e0bc61b
Implement work stealing for dynamic children
marcphilipp Oct 14, 2025
261ac0e
Limit work stealing of dynamic children to current entry
marcphilipp Oct 14, 2025
cf923fc
Polishing
marcphilipp Oct 14, 2025
afbb9f6
Simplify work-stealing `Future` implementation
marcphilipp Oct 14, 2025
a8cec38
Restore max pool size limit in test
marcphilipp Oct 14, 2025
6103e4d
Avoid starting an excessive number of threads
marcphilipp Oct 14, 2025
855880f
Polishing
marcphilipp Oct 14, 2025
d47f9fc
Add test for `WorkerLeaseManager` and `WorkerLease`
marcphilipp Oct 16, 2025
ba98383
Avoid race during worker startup
marcphilipp Oct 16, 2025
d111712
Add test for race condition when starting workers
mpkorstanje Oct 17, 2025
55d8b71
Avoid recursively calling `maybeStartWorker`
marcphilipp Oct 17, 2025
c8ddb46
Repeat test to increase likelihood of triggering its flakiness
marcphilipp Oct 17, 2025
4de2d9a
Temporarily disable stacktrace pruning
marcphilipp Oct 17, 2025
f081fde
Temporarily enable logging to have more info when tests fail
marcphilipp Oct 17, 2025
ed9b22d
Execute unclaimed children in blocking mode prior to joining forked work
marcphilipp Oct 17, 2025
163b72c
Ignore rejected worker starts if there's at least one active worker
marcphilipp Oct 17, 2025
be1ab01
Reinstate max-pool-size limit
marcphilipp Oct 17, 2025
7c74974
Use unique ID as key
marcphilipp Oct 18, 2025
03a1dd8
Simplify forking and work stealing
marcphilipp Oct 18, 2025
82a2d7f
Yield worker lease when blocking thread can continue
marcphilipp Oct 18, 2025
8cf7f47
Add TODO
marcphilipp Oct 18, 2025
2cc5311
fixup! Yield worker lease when blocking thread can continue
marcphilipp Oct 18, 2025
08986fb
Use EnumMap for queueEntriesByResult
mpkorstanje Oct 19, 2025
4567c16
Verify work is stolen in reverse order
mpkorstanje Oct 19, 2025
6e64a19
Ensure work is stolen in reverse order
mpkorstanje Oct 19, 2025
0d5e8e1
Fix prioritizesChildrenOfStartedContainers by swapping order
mpkorstanje Oct 19, 2025
f045138
Ensure task entry index is not sparse
mpkorstanje Oct 19, 2025
fdb5fac
Typos and formatting
mpkorstanje Oct 19, 2025
14d5f9a
Polishing
marcphilipp Oct 24, 2025
53af729
Fix flakey test?
mpkorstanje Oct 19, 2025
569edce
Test is not flaky anymore.
mpkorstanje Oct 20, 2025
a9ae858
Improve naming
mpkorstanje Oct 19, 2025
bc9d6b2
Skip over unavailable resources
mpkorstanje Oct 19, 2025
807ea54
Remove unused entry ordering
mpkorstanje Oct 20, 2025
192b0b4
Polishing
mpkorstanje Oct 20, 2025
2c72021
Polishing with spotless
mpkorstanje Oct 20, 2025
87416c1
Polishing
mpkorstanje Oct 20, 2025
d691b1e
Extract RejectedExecutionHandler to avoid control flow by exception
mpkorstanje Oct 20, 2025
b5df82e
Use ConcurrentSkipListSet with absolute ordering to back work queue
mpkorstanje Oct 21, 2025
a4d7ed6
Use long index, because containers are int size
mpkorstanje Oct 21, 2025
b11a481
Polishing
marcphilipp Oct 24, 2025
8ef8d20
Steal other dynamic children before blocking
marcphilipp Oct 25, 2025
80c38d8
Only steal queue entries for siblings of dynamic children
marcphilipp Oct 26, 2025
b3b1345
Polishing
marcphilipp Oct 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/src/test/resources/junit-platform.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.executor=org.junit.platform.engine.support.hierarchical.ConcurrentHierarchicalTestExecutorService
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=6
Expand Down
2 changes: 1 addition & 1 deletion documentation/src/test/resources/log4j2-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-config-2.xsd">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<PatternLayout pattern="%d{HH:mm:ss.SSSSSS} [%-18t] %-5level %logger{1.} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.MAINTAINED;
import static org.apiguardian.api.API.Status.STABLE;
import static org.junit.jupiter.engine.config.JupiterConfiguration.PARALLEL_CONFIG_PREFIX;
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_CUSTOM_CLASS_PROPERTY_NAME;
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME;
import static org.junit.platform.engine.support.hierarchical.DefaultParallelExecutionConfigurationStrategy.CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME;
Expand All @@ -38,6 +39,8 @@
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.platform.commons.util.ClassNamePatternFilterUtils;
import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;
import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy;

/**
Expand Down Expand Up @@ -210,6 +213,21 @@ public final class Constants {
@API(status = STABLE, since = "5.10")
public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME;

/**
* Property name used to configure the fully qualified class name
* {@link HierarchicalTestExecutorService} implementation to use if parallel
* test execution is
* {@linkplain #PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME enabled}: {@value}
*
* <p>The implementation class must provide a parameter of type
*
* <p>By default, {@link ForkJoinPoolHierarchicalTestExecutorService} is used.
*
* @since 6.1
*/
@API(status = EXPERIMENTAL, since = "6.1")
public static final String PARALLEL_EXECUTION_EXECUTOR_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTION_EXECUTOR_PROPERTY_NAME;

/**
* Property name used to enable auto-closing of {@link AutoCloseable} instances
*
Expand Down Expand Up @@ -237,8 +255,6 @@ public final class Constants {
@API(status = STABLE, since = "5.10")
public static final String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME;

static final String PARALLEL_CONFIG_PREFIX = "junit.jupiter.execution.parallel.config.";

/**
* Property name used to select the
* {@link ParallelExecutionConfigurationStrategy}: {@value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
import org.junit.platform.engine.ExecutionRequest;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.config.PrefixedConfigurationParameters;
import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter;
import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
Expand Down Expand Up @@ -79,8 +77,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId
protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) {
JupiterConfiguration configuration = getJupiterConfiguration(request);
if (configuration.isParallelExecutionEnabled()) {
return new ForkJoinPoolHierarchicalTestExecutorService(new PrefixedConfigurationParameters(
request.getConfigurationParameters(), Constants.PARALLEL_CONFIG_PREFIX));
return configuration.createParallelExecutorService();
}
return super.createExecutorService(request);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.junit.jupiter.api.io.TempDirFactory;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.engine.OutputDirectoryCreator;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;

/**
* Caching implementation of the {@link JupiterConfiguration} API.
Expand Down Expand Up @@ -69,6 +70,11 @@ public boolean isParallelExecutionEnabled() {
__ -> delegate.isParallelExecutionEnabled());
}

@Override
public HierarchicalTestExecutorService createParallelExecutorService() {
return delegate.createParallelExecutorService();
}

@Override
public boolean isClosingStoredAutoCloseablesEnabled() {
return (boolean) cache.computeIfAbsent(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDirFactory;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter.Strictness;
import org.junit.platform.commons.util.ClassNamePatternFilterUtils;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.engine.ConfigurationParameters;
import org.junit.platform.engine.DiscoveryIssue;
import org.junit.platform.engine.DiscoveryIssue.Severity;
import org.junit.platform.engine.OutputDirectoryCreator;
import org.junit.platform.engine.support.config.PrefixedConfigurationParameters;
import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter;
import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;

/**
* Default implementation of the {@link JupiterConfiguration} API.
Expand Down Expand Up @@ -81,6 +85,10 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration {
private static final ConfigurationParameterConverter<ExtensionContextScope> extensionContextScopeConverter = //
new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope");

private static final ConfigurationParameterConverter<HierarchicalTestExecutorService> parallelExecutorServiceConverter = //
new InstantiatingConfigurationParameterConverter<>(HierarchicalTestExecutorService.class,
"parallel executor service", Strictness.FAIL, parameters -> new Object[] { parallelConfig(parameters) });

private final ConfigurationParameters configurationParameters;
private final OutputDirectoryCreator outputDirectoryCreator;

Expand Down Expand Up @@ -136,6 +144,13 @@ public boolean isParallelExecutionEnabled() {
return configurationParameters.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false);
}

@Override
public HierarchicalTestExecutorService createParallelExecutorService() {
return parallelExecutorServiceConverter.get(configurationParameters, PARALLEL_EXECUTION_EXECUTOR_PROPERTY_NAME) //
.orElseGet(
() -> new ForkJoinPoolHierarchicalTestExecutorService(parallelConfig(configurationParameters)));
}

@Override
public boolean isClosingStoredAutoCloseablesEnabled() {
return configurationParameters.getBoolean(CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME).orElse(true);
Expand Down Expand Up @@ -214,4 +229,9 @@ public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope()
public OutputDirectoryCreator getOutputDirectoryCreator() {
return outputDirectoryCreator;
}

private static PrefixedConfigurationParameters parallelConfig(ConfigurationParameters configurationParameters) {
return new PrefixedConfigurationParameters(configurationParameters, PARALLEL_CONFIG_PREFIX);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,43 @@

package org.junit.jupiter.engine.config;

import java.lang.reflect.Constructor;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.IntStream;

import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.function.Try;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.engine.ConfigurationParameters;

/**
* @since 5.5
*/
class InstantiatingConfigurationParameterConverter<T> implements ConfigurationParameterConverter<T> {
final class InstantiatingConfigurationParameterConverter<T> implements ConfigurationParameterConverter<T> {

private static final Logger logger = LoggerFactory.getLogger(InstantiatingConfigurationParameterConverter.class);

private final Class<T> clazz;
private final String name;
private final Strictness strictness;
private final Function<ConfigurationParameters, Object[]> argumentResolver;

InstantiatingConfigurationParameterConverter(Class<T> clazz, String name) {
this(clazz, name, Strictness.WARN, __ -> new Object[0]);
}

InstantiatingConfigurationParameterConverter(Class<T> clazz, String name, Strictness strictness,
Function<ConfigurationParameters, Object[]> argumentResolver) {
this.clazz = clazz;
this.name = name;
this.strictness = strictness;
this.argumentResolver = argumentResolver;
}

@Override
Expand All @@ -44,31 +59,62 @@ Supplier<Optional<T>> supply(ConfigurationParameters configurationParameters, St
return configurationParameters.get(key)
.map(String::strip)
.filter(className -> !className.isEmpty())
.map(className -> newInstanceSupplier(className, key))
.map(className -> newInstanceSupplier(className, key, configurationParameters))
.orElse(Optional::empty);
// @formatter:on
}

private Supplier<Optional<T>> newInstanceSupplier(String className, String key) {
private Supplier<Optional<T>> newInstanceSupplier(String className, String key,
ConfigurationParameters configurationParameters) {
Try<Class<?>> clazz = ReflectionSupport.tryToLoadClass(className);
// @formatter:off
return () -> clazz.andThenTry(ReflectionSupport::newInstance)
return () -> clazz.andThenTry(it -> instantiate(it, configurationParameters))
.andThenTry(this.clazz::cast)
.ifSuccess(generator -> logSuccessMessage(className, key))
.ifFailure(cause -> logFailureMessage(className, key, cause))
.toOptional();
// @formatter:on
}

@SuppressWarnings("unchecked")
private <V> V instantiate(Class<V> clazz, ConfigurationParameters configurationParameters) {
var arguments = argumentResolver.apply(configurationParameters);
if (arguments.length == 0) {
return ReflectionSupport.newInstance(clazz);
}
var constructors = ReflectionUtils.findConstructors(clazz, it -> {
if (it.getParameterCount() != arguments.length) {
return false;
}
var parameters = it.getParameters();
return IntStream.range(0, parameters.length) //
.allMatch(i -> parameters[i].getType().isAssignableFrom(arguments[i].getClass()));
});
Preconditions.condition(constructors.size() == 1,
() -> "Failed to find unambiguous constructor for %s. Candidates: %s".formatted(clazz.getName(),
constructors));
return ReflectionUtils.newInstance((Constructor<V>) constructors.get(0), arguments);
}

private void logFailureMessage(String className, String key, Exception cause) {
logger.warn(cause, () -> """
Failed to load default %s class '%s' set via the '%s' configuration parameter. \
Falling back to default behavior.""".formatted(this.name, className, key));
switch (strictness) {
case WARN -> logger.warn(cause, () -> """
Failed to load default %s class '%s' set via the '%s' configuration parameter. \
Falling back to default behavior.""".formatted(this.name, className, key));
case FAIL -> throw new JUnitException(
"Failed to load default %s class '%s' set via the '%s' configuration parameter.".formatted(this.name,
className, key),
cause);
}
}

private void logSuccessMessage(String className, String key) {
logger.config(() -> "Using default %s '%s' set via the '%s' configuration parameter.".formatted(this.name,
className, key));
}

enum Strictness {
FAIL, WARN
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.engine.OutputDirectoryCreator;
import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;

/**
* @since 5.4
Expand All @@ -42,6 +43,8 @@ public interface JupiterConfiguration {
String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude";
String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate";
String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled";
String PARALLEL_EXECUTION_EXECUTOR_PROPERTY_NAME = "junit.jupiter.execution.parallel.executor";
String PARALLEL_CONFIG_PREFIX = "junit.jupiter.execution.parallel.config.";
String CLOSING_STORED_AUTO_CLOSEABLE_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.store.close.autocloseable.enabled";
String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME;
String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME;
Expand All @@ -61,6 +64,8 @@ public interface JupiterConfiguration {

boolean isParallelExecutionEnabled();

HierarchicalTestExecutorService createParallelExecutorService();

boolean isClosingStoredAutoCloseablesEnabled();

boolean isExtensionAutoDetectionEnabled();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2015-2025 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

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

import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.jspecify.annotations.Nullable;

abstract class BlockingAwareFuture<T extends @Nullable Object> extends DelegatingFuture<T> {

BlockingAwareFuture(Future<T> delegate) {
super(delegate);
}

@Override
public T get() throws InterruptedException, ExecutionException {
if (delegate.isDone()) {
return delegate.get();
}
return handleSafely(delegate::get);
}

@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (delegate.isDone()) {
return delegate.get();
}
return handleSafely(() -> delegate.get(timeout, unit));
}

private T handleSafely(Callable<T> callable) {
try {
return handle(callable);
}
catch (Exception e) {
throw throwAsUncheckedException(e);
}
}

protected abstract T handle(Callable<T> callable) throws Exception;

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ List<Lock> getLocks() {
return this.locks;
}

@Override
public boolean tryAcquire() {
List<Lock> acquiredLocks = new ArrayList<>(this.locks.size());
for (Lock lock : this.locks) {
if (lock.tryLock()) {
acquiredLocks.add(lock);
}
else {
break;
}
}
if (acquiredLocks.size() == this.locks.size()) {
return true;
}
else {
release(acquiredLocks);
return false;
}
}

@Override
public ResourceLock acquire() throws InterruptedException {
ForkJoinPool.managedBlock(new CompositeLockManagedBlocker());
Expand Down
Loading