Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce support for virtual threads #3442

Open
1 task
sormuras opened this issue Sep 1, 2023 · 3 comments · May be fixed by #3443
Open
1 task

Introduce support for virtual threads #3442

sormuras opened this issue Sep 1, 2023 · 3 comments · May be fixed by #3443

Comments

@sormuras
Copy link
Member

sormuras commented Sep 1, 2023

Java 21 ships with virtual threads: https://openjdk.org/jeps/444

The JUnit Platform should provide an implementation of the HierarchicalTestExecutorService using virtual threads as an opt-in feature.

Deliverables

  • Implement and test VirtualThreadHierarchicalTestExecutorService in the org.junit.platform.engine module
@jbduncan
Copy link
Contributor

jbduncan commented Sep 1, 2023

As a reminder, what is the advantage of running tests on virtual threads? Performance?

@sbrannen sbrannen changed the title Virtual thread support Introduce support for virtual threads Sep 5, 2023
@marcphilipp marcphilipp modified the milestones: 5.10.1, 5.11 M1 Oct 13, 2023
@kwakeroni
Copy link

As a reminder, what is the advantage of running tests on virtual threads? Performance?

I'm not the original poster, but I imagine that would be the main advantage, indeed.

Personally, I've imagined using virtual threads as a more dynamic alternative to @ResourceLock. @ResourceLock requires resources to be declared statically. Sometimes the resource is only known at runtime (for example due to configuration parameters).

Instead of grouping tests up-front by static resource, tests could try to get a lock on the resource they need and wait until it is available without blocking other tests. With the current thread pooling this could deteriorate the test suite to sequential execution, with virtual threads that would not be an issue. This way, custom extensions could hook into the concurrent execution simply by 'holding up' a test until it is ready to be run.

ParallelExecutionConfiguration seems rather tied to the thread pooling concept. Having a configuration parameter to select a custom HierarchicalTestExecutorService implementation, would already allow for easier experimentation. (referring to the implementation of JupiterTestEngine.createExecutorService)

@goughy000
Copy link

Do we think something along these lines (but more polished up of course) would be a useful first step to allow selecting a custom HierearchicalTestExecutorService via configuration parameters, without getting into the weeds of virtual threads specifically?

As @kwakeroni says this would allow people to do some experimentation with virtual threads (or other things) without introducing them into jupiter itself yet

(I've not put much thought into this or really tried it out, just copied how it works for the TempDirFactory)

diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java
index dbd799b7f5..cea6dc9d0b 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java
@@ -22,6 +22,7 @@ import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor;
 import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver;
 import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
 import org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory;
+import org.junit.platform.engine.ConfigurationParameters;
 import org.junit.platform.engine.EngineDiscoveryRequest;
 import org.junit.platform.engine.ExecutionRequest;
 import org.junit.platform.engine.TestDescriptor;
@@ -30,6 +31,7 @@ import org.junit.platform.engine.support.config.PrefixedConfigurationParameters;
 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.HierarchicalTestExecutorServiceFactory;
 import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
 
 /**
@@ -73,11 +75,13 @@ public final class JupiterTestEngine extends HierarchicalTestEngine<JupiterEngin
        @Override
        protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) {
                JupiterConfiguration configuration = getJupiterConfiguration(request);
-               if (configuration.isParallelExecutionEnabled()) {
-                       return new ForkJoinPoolHierarchicalTestExecutorService(new PrefixedConfigurationParameters(
-                               request.getConfigurationParameters(), Constants.PARALLEL_CONFIG_PREFIX));
+               if (!configuration.isParallelExecutionEnabled()) {
+                       return super.createExecutorService(request);
                }
-               return super.createExecutorService(request);
+
+               return configuration.getHierarchicalTestExecutorServiceFactory().get().createExecutorService(
+                       new PrefixedConfigurationParameters(request.getConfigurationParameters(),
+                               Constants.PARALLEL_CONFIG_PREFIX));
        }
 
        @Override
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java
index 2d61b58c1c..8d343b988f 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.extension.ExecutionCondition;
 import org.junit.jupiter.api.io.CleanupMode;
 import org.junit.jupiter.api.io.TempDirFactory;
 import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorServiceFactory;
 
 /**
  * Caching implementation of the {@link JupiterConfiguration} API.
@@ -125,4 +126,10 @@ public class CachingJupiterConfiguration implements JupiterConfiguration {
                        key -> delegate.getDefaultTempDirFactorySupplier());
        }
 
+       @SuppressWarnings("unchecked")
+       @Override
+       public Supplier<HierarchicalTestExecutorServiceFactory> getHierarchicalTestExecutorServiceFactory() {
+               return (Supplier<HierarchicalTestExecutorServiceFactory>) cache.computeIfAbsent(PARALLEL_EXECUTION_EXECUTOR_SERVICE_PROPERTY_NAME,
+                               key -> delegate.getHierarchicalTestExecutorServiceFactory());
+       }
 }
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java
index d64c4ceee3..e435171c1b 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.parallel.ExecutionMode;
 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.support.hierarchical.HierarchicalTestExecutorServiceFactory;
 
 /**
  * Default implementation of the {@link JupiterConfiguration} API.
@@ -62,6 +63,10 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration {
        private static final InstantiatingConfigurationParameterConverter<TempDirFactory> tempDirFactoryConverter = //
                new InstantiatingConfigurationParameterConverter<>(TempDirFactory.class, "temp dir factory");
 
+       private static final InstantiatingConfigurationParameterConverter<HierarchicalTestExecutorServiceFactory> testExecutorServiceFactoryConverter = //
+               new InstantiatingConfigurationParameterConverter<>(HierarchicalTestExecutorServiceFactory.class,
+                       "test executor service factory");
+
        private final ConfigurationParameters configurationParameters;
 
        public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) {
@@ -141,4 +146,11 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration {
                return () -> supplier.get().orElse(TempDirFactory.Standard.INSTANCE);
        }
 
+       @Override
+       public Supplier<HierarchicalTestExecutorServiceFactory> getHierarchicalTestExecutorServiceFactory() {
+               Supplier<Optional<HierarchicalTestExecutorServiceFactory>> supplier = testExecutorServiceFactoryConverter.supply(
+                       configurationParameters, PARALLEL_EXECUTION_EXECUTOR_SERVICE_PROPERTY_NAME);
+
+               return () -> supplier.get().orElse(HierarchicalTestExecutorServiceFactory.Standard.INSTANCE);
+       }
 }
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java
index 559b4d7d57..9637a30c3b 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java
@@ -27,6 +27,7 @@ import org.junit.jupiter.api.io.CleanupMode;
 import org.junit.jupiter.api.io.TempDirFactory;
 import org.junit.jupiter.api.parallel.Execution;
 import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorServiceFactory;
 
 /**
  * @since 5.4
@@ -36,6 +37,7 @@ public interface JupiterConfiguration {
 
        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_SERVICE_PROPERTY_NAME = "junit.jupiter.execution.parallel.executorservice";
        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;
        String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled";
@@ -70,4 +72,6 @@ public interface JupiterConfiguration {
 
        Supplier<TempDirFactory> getDefaultTempDirFactorySupplier();
 
+       Supplier<HierarchicalTestExecutorServiceFactory> getHierarchicalTestExecutorServiceFactory();
+
 }

@marcphilipp marcphilipp modified the milestones: 5.11 M1, 5.12 Feb 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants