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

Parallelism option ignored when using own ForkJoinPool #1858

Closed
fredericOfTestfabrik opened this issue Apr 11, 2019 · 37 comments · Fixed by #3044
Closed

Parallelism option ignored when using own ForkJoinPool #1858

fredericOfTestfabrik opened this issue Apr 11, 2019 · 37 comments · Fixed by #3044

Comments

@fredericOfTestfabrik
Copy link

I'm using Junit 5.4.1 and am experiencing a weird issue related to parallelism.

I have a set of Tests that I want to execute in parallel. 4 of these tests are tagged with the tag "main". Which I want to execute using maven. However I only want to execute 2 of them at the same time. Thus I configured jUnit 5 as follows:

junit.jupiter.execution.parallel.enabled=true junit.jupiter.execution.parallel.config.strategy=fixed junit.jupiter.execution.parallel.config.fixed.parallelism=2

This config works when I invoke the following mvn command:

mvn test

However, when i add the parameter to only execute the Tests tagged with main:

mvn test -Dgroups=main

ALL of the tests (meaning 4) tagged with "main" are executed in parallel, when only 2 should be executed at the same time. It seems that somehow, the parallelism count is ignored. Unfortunately I can't verify which setting is used by JUnit5 or if this bug is related to mvn surefire or Junit5

Steps to reproduce

  • Create a Test Suite with 5 or more Tests
  • Tag 4 of them with a tag (e.g main)
  • invoke mvn test -Dgroups=main

Context

  • Used versions (Jupiter/Vintage/Platform): 5.4.1 Jupiter / no vintage / 1.4.1 platform
  • Build Tool/IDE: maven 3.6 surefire 2.22.1
@marcphilipp
Copy link
Member

Are the 4 tests all in the same test class or different test classes?

Could you please provide a small sample project?

@fredericOfTestfabrik
Copy link
Author

fredericOfTestfabrik commented Apr 11, 2019

Yes all 4 tests are in the same classes.

As I can't share the original test code I wrote a small sample snippet which tried to reproduce the problem. This is when I found the actual problem. This isn't related to tags but more to concurrency and the meaning of the junit.jupiter.execution.parallel.config.fixed.parallelism parameter. I try to explain:

  @Test @Tag("main")
    void done1() throws InterruptedException {

        System.out.println("BEGIN1");
        System.out.println(Thread.currentThread().getName());
        ForkJoinPool customThreadPool = new ForkJoinPool(4);
        RecursiveAction a = new RecursiveAction() {
            @Override
            protected void compute() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName());}
            }
        };
        customThreadPool.invoke(a);

        System.out.println("DONE1");


    }

    @Test @Tag("main")
    void done2() throws InterruptedException {
        System.out.println("BEGIN2");
        System.out.println(Thread.currentThread().getName());
        String name = Thread.currentThread().getName();
        threadNames.add(name);
        ForkJoinPool customThreadPool = new ForkJoinPool(4);
        RecursiveAction a = new RecursiveAction() {
            @Override
            protected void compute() {
                while (true) {
                    try {

When setting junit.jupiter.execution.parallel.config.fixed.parallelism=1 my expectation was that the second test would never start to execute, since the task in the custom thread pool does never return a result and thus the calling thread executing the test would block forever. In reality this happens:

---------- Run Test-Before ----------
BEGIN2
ForkJoinPool-1-worker-1

---------- Run Test-Before ----------
BEGIN1
ForkJoinPool-1-worker-0
ForkJoinPool-2-worker-1
ForkJoinPool-3-worker-1

My knowledge about ForkJoinPools is unfortunately limited as I didn't work with them before but it seems that when worker-1 is forced to wait on the result of the invoke, another worker (worker-0) is spawned to start with the second test. This essentially means that all tests in my class would run in parallel. So i guess the junit.jupiter.execution.parallel.config.fixed.parallelism controls how many tests can be active at the same time, meaning that if fewer tests are active (because the executing Thread yields), JUnit spawns additional threads to keep the number of active tests at this maximum level?

If this is not what happens I would be happy if you could help me understand what exactly is going on here.

In any case this behavior surprised me as my intuition would be that the tests that are executed in parallel must actually complete before another test can be started. While I can see that there might be reasons why one does not want to do this, is there a way to enforce the behavior I expect (i.e that I can control how many tests are started in parallel) ?

@marcphilipp
Copy link
Member

The idea behind ForkJoinPool's parallelism setting and by extension also JUnit's is trying to ensure that about n threads are doing work at all times. Thus, if one of the threads is about to be blocked, it spawns an additional one to ensure that work is still being done.

@fredericOfTestfabrik Do your actual tests also start operations that use ForkJoinPool in one way or another?

@fredericOfTestfabrik
Copy link
Author

fredericOfTestfabrik commented Apr 12, 2019

First of all thank you for your explanation.

Regarding your Question:

Yes the actual test create fork join pools to let long running operations execute in parallel.

My tests use selenium to test a website. The test code is built in such a way that it can be run in parallel for different browsers. Each test thus runs the selenium code in parallel for 2 different browsers. When I don't use junit5s parallelism, this runs fine.

Now want I want to do is to run multiple testcases at the same time. However the total amount of available browsers is limited. E.g I have 4 browsers available so I want to run 2 testcases at most at the same time, because each of the testcase again needs 2 browsers.

My guess is that at some point the testcode needs to yield (because it waits on a response or there is a thread sleep needed to ensure some stability). And this is when the additional tests are run. So what I need would be a setting or functionality to ensure that no more than 2 tests are run in parallel even if one test case is suspended for a certain amount of time.

Edit: I'm sorry for the mess. I am writing this from my phone and accidentally hit the close button

@marcphilipp marcphilipp self-assigned this Apr 12, 2019
@marcphilipp marcphilipp added this to the 5.5 M2 milestone Apr 12, 2019
@marcphilipp
Copy link
Member

Thanks for the explanation! I'll take a closer look in the next few days.

@marcphilipp marcphilipp modified the milestones: 5.5 M2, 5.5 Backlog Apr 18, 2019
@fredericOfTestfabrik
Copy link
Author

Is there an update on this? Do you need additional Information?

@marcphilipp
Copy link
Member

@fredericOfTestfabrik I should know better than to make promises like that, sorry! Unfortunately, I haven't had a chance to investigate further, yet.

@marcphilipp marcphilipp modified the milestones: 5.5 Backlog, 5.5 M2 May 4, 2019
@sormuras
Copy link
Member

sormuras commented Jun 4, 2019

Created https://github.com/sormuras/junit5-1858-fixed-parallelism to investigate this issue.

Some notes:

  • I could verify the described behaviour - spawning a custom ForkJoinPool inside a test enables other tests to be executed in parallel, exceeding the configured fixed maximum value.
  • Using a dead-simple Thread.sleep(long) to simulate a long duration test execution works out perfectly.
  • TODO Try an ExecutorService to simulate some concurrent work within a test method...

@sormuras sormuras changed the title Parallelism Count ignored when using Tags and maven Parallelism option ignored when using own ForkJoinPool Jun 5, 2019
@sormuras
Copy link
Member

sormuras commented Jun 5, 2019

Changed the title to reflect that the underlying issue doesn't need tags or Maven to show up.

@sormuras
Copy link
Member

sormuras commented Jun 5, 2019

Using an ExecutorService service = Executors.newFixedThreadPool(4); within a test method works as expected.

Seems like multiple ForkJoinPool instances steal work from each other...

Does anybody know a way to prevent this? By tagging/marking tasks as belonging to a specific pool only?

@sormuras
Copy link
Member

sormuras commented Jun 5, 2019

Using Stream#parallel() seems not to mess with ForkJoinPool, though...

@sormuras
Copy link
Member

sormuras commented Jun 5, 2019

work-around

Using a custom strategy that also fixes the maximum (and core?) pool size to same value as the parallelism option, solves the issue described here.

The default FIXED strategy adds 256 to get the maximum pool size:

return new DefaultParallelExecutionConfiguration(parallelism, parallelism, 256 + parallelism, parallelism,

@marcphilipp
Copy link
Member

IIRC the problem with that is that the ForkJoinPool sometimes spawns additional threads to reach the target parallelism level, e.g. when one is about to be blocked (see ManagedBlocker).

@marcphilipp
Copy link
Member

@fredericOfTestfabrik Please give the workaround posted by @sormuras above a try and let us know if that works for you.

@christophs78
Copy link

christophs78 commented Aug 2, 2022

There is no (good) fix for Java 8 aside from serializing tests.
see SeleniumHQ/selenium#10113 (comment)

(Maybe you can work arround this issue by e.g. running your selenium-tests on Java 11/17 but still compile against language level 8.)

@sangameshwar-balur
Copy link

@christophs78 I am using Gradle instead maven. Do you recommend any specific version for Gradle to be used to mitigate this issue.

From your comment you mean to say that below setting should work fine without any issue. It works based configurations in junit-platform.properties file.

Java version - > 11
junit-jupiter.version -> 5.9.0
selenium-java.version -> 4.3.0
selenium-jupiter.version -> 4.2.0

@christophs78
Copy link

Gradle-version IMO should not matter for this. On my day job we are using Gradle 7.x

@sangameshwar-balur
Copy link

sangameshwar-balur commented Aug 26, 2022

@christophs78 : Can you please help to resolve this.

I am using JAVA 11 and below version for each but still I see undefined number threads are opening
junit-jupiter.version -> 5.9.0
selenium-java.version -> 4.3.0
selenium-jupiter.version -> 4.2.0
image

Parallel setting looks like this:
image

Note : I am using WebDriverManager also

@jmrg82
Copy link

jmrg82 commented Aug 29, 2022

Hello @sangameshwar-balur! after @christophs78 's suggestion, I tried to compile with Java 11 and 17, but still no luck with the fixed parallelism strategy :(

However, after a long investigation with many pokes in the dark... I found that setting a Custom Strategy works for me:

maven.compiler.source/target -> 1.8 / 11 / 17 (I did not find any difference... IMPORTANT! as long as you execute them using at least java11 as @christophs78 said. I mean, you can compile against 8, but maven needs to point to java 11)
junit-jupiter.version -> 5.9.0
selenium-java.version -> 4.4.0
selenium-jupiter.version -> 4.3.1
maven-surefire-plugin.version -> 3.0.0-M7

For these config parameters, I moved from the pom.xml to an external \src\main\resources\junit-platform.properties

<configurationParameters>
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = same_thread
junit.jupiter.execution.parallel.mode.classes.default = concurrent
#junit.jupiter.execution.parallel.config.strategy = fixed
#junit.jupiter.execution.parallel.config.fixed.parallelism=4
junit.jupiter.execution.parallel.config.strategy=custom
junit.jupiter.execution.parallel.config.custom.class=com.path.to.my.CustomStrategy
</configurationParameters>

I included a new class with this content: https://github.com/sormuras/junit5-1858-fixed-parallelism/blob/master/src/test/java/CustomStrategy.java

Changing the value of the returned overrides, you will probably manage to limit the number of parallel threads. The only disadvantage is that you cannot define the number of threads dynamically/externally from maven/jenkins

mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 9, 2022
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
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 9, 2022
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
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 9, 2022
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
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 24, 2022
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
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 24, 2022
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
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 24, 2022
JUnit Jupiter (and The JUnit Platform) now support limiting the
maximum number of concurrently executing tests via the
`junit.jupiter.execution.parallel.config.fixed.max-pool-size` property.

With Java 9+ the `ForkJoinPool` used by JUnit can be configured with a maximum
pool size. While the number of concurrently executing tests may exceed the
configured parallelism when tests become blocked, it will not exceed the
maximum pool size.

With the following configuration:

```properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=2
```

This example will report between 2-5 tests running concurrently.

```java
class ExampleTest {

    private static final AtomicInteger running = new AtomicInteger();

    @beforeeach
    void increment() {
        System.out.println("Running " + running.incrementAndGet());
    }

    @AfterEach
    void decrement() {
        running.decrementAndGet();
    }

    static IntStream numbers() {
        return IntStream.range(0, 1000);
    }

    @ParameterizedTest
    @MethodSource("numbers")
    void test(int i) throws ExecutionException, InterruptedException {
        Runnable sleep = () -> {
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
        ForkJoinPool.commonPool().submit(sleep).get();
    }
}
```

By also configuring the `max-pool-size` we can ensure the concurrently
executing test does not exceed the configured 2.

```properties
junit.jupiter.execution.parallel.config.fixed.max-pool-size=2
```

Additionally, because the `ForkJoinPool` will by default reject tasks that
would exceed the maximum pool size the
`junit.jupiter.execution.parallel.config.fixed.saturate` property has been
added and will default to `true`. There appears to be no reason to ever set
this `false` but it is there should someone depend on the old behaviour.

These changes were intentionally not made to the `dynamic` strategy to limit
the scope of this pull request. While I can reasonably predict what behaviour
users of the `fixed` strategy might expect, I can not say the same about the
`dynamic` strategy.

Fixes: junit-team#2545
Fixes: junit-team#1858
Fixes: junit-team#3026
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 24, 2022
JUnit Jupiter (and The JUnit Platform) now support limiting the
maximum number of concurrently executing tests via the
`junit.jupiter.execution.parallel.config.fixed.max-pool-size` property.

With Java 9+ the `ForkJoinPool` used by JUnit can be configured with a maximum
pool size. While the number of concurrently executing tests may exceed the
configured parallelism when tests become blocked, it will not exceed the
maximum pool size.

With the following configuration:

```properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=2
```

This example will report between 2-5 tests running concurrently.

```java
class ExampleTest {

    private static final AtomicInteger running = new AtomicInteger();

    @beforeeach
    void increment() {
        System.out.println("Running " + running.incrementAndGet());
    }

    @AfterEach
    void decrement() {
        running.decrementAndGet();
    }

    static IntStream numbers() {
        return IntStream.range(0, 1000);
    }

    @ParameterizedTest
    @MethodSource("numbers")
    void test(int i) throws ExecutionException, InterruptedException {
        Runnable sleep = () -> {
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
        ForkJoinPool.commonPool().submit(sleep).get();
    }
}
```

By also configuring the `max-pool-size` we can ensure the concurrently
executing test does not exceed the configured 2.

```properties
junit.jupiter.execution.parallel.config.fixed.max-pool-size=2
```

Additionally, because the `ForkJoinPool` will by default reject tasks that
would exceed the maximum pool size the
`junit.jupiter.execution.parallel.config.fixed.saturate` property has been
added and will default to `true`. There appears to be no reason to ever set
this `false` but it is there should someone depend on the old behaviour.

These changes were intentionally not made to the `dynamic` strategy to limit
the scope of this pull request. While I can reasonably predict what behaviour
users of the `fixed` strategy might expect, I can not say the same about the
`dynamic` strategy.

Fixes: junit-team#2545
Fixes: junit-team#1858
Fixes: junit-team#3026
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 24, 2022
JUnit Jupiter (and The JUnit Platform) now support limiting the
maximum number of concurrently executing tests via the
`junit.jupiter.execution.parallel.config.fixed.max-pool-size` property.

With Java 9+ the `ForkJoinPool` used by JUnit can be configured with a maximum
pool size. While the number of concurrently executing tests may exceed the
configured parallelism when tests become blocked, it will not exceed the
maximum pool size.

With the following configuration:

```properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=2
```

This example will report between 2-5 tests running concurrently.

```java
class ExampleTest {

    private static final AtomicInteger running = new AtomicInteger();

    @beforeeach
    void increment() {
        System.out.println("Running " + running.incrementAndGet());
    }

    @AfterEach
    void decrement() {
        running.decrementAndGet();
    }

    static IntStream numbers() {
        return IntStream.range(0, 1000);
    }

    @ParameterizedTest
    @MethodSource("numbers")
    void test(int i) throws ExecutionException, InterruptedException {
        Runnable sleep = () -> {
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
        ForkJoinPool.commonPool().submit(sleep).get();
    }
}
```

By also configuring the `max-pool-size` we can ensure the concurrently
executing test does not exceed the configured 2.

```properties
junit.jupiter.execution.parallel.config.fixed.max-pool-size=2
```

Additionally, because the `ForkJoinPool` will by default reject tasks that
would exceed the maximum pool size the
`junit.jupiter.execution.parallel.config.fixed.saturate` property has been
added and will default to `true`. There appears to be no reason to ever set
this `false` but it is there should someone depend on the old behaviour.

These changes were intentionally not made to the `dynamic` strategy to limit
the scope of this pull request. While I can reasonably predict what behaviour
users of the `fixed` strategy might expect, I can not say the same about the
`dynamic` strategy.

Fixes: junit-team#3026
Fixes: junit-team#2545
Fixes: junit-team#1858
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 24, 2022
JUnit Jupiter (and The JUnit Platform) now support limiting the
maximum number of concurrently executing tests via the
`junit.jupiter.execution.parallel.config.fixed.max-pool-size` property.

With Java 9+ the `ForkJoinPool` used by JUnit can be configured with a maximum
pool size. While the number of concurrently executing tests may exceed the
configured parallelism when tests become blocked, it will not exceed the
maximum pool size.

With the following configuration:

```properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=2
```

This example will report between 2-5 tests running concurrently.

```java
class ExampleTest {

    private static final AtomicInteger running = new AtomicInteger();

    @beforeeach
    void increment() {
        System.out.println("Running " + running.incrementAndGet());
    }

    @AfterEach
    void decrement() {
        running.decrementAndGet();
    }

    static IntStream numbers() {
        return IntStream.range(0, 1000);
    }

    @ParameterizedTest
    @MethodSource("numbers")
    void test(int i) throws ExecutionException, InterruptedException {
        Runnable sleep = () -> {
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
        ForkJoinPool.commonPool().submit(sleep).get();
    }
}
```

By also configuring the `max-pool-size` we can ensure the concurrently
executing test does not exceed the configured 2.

```properties
junit.jupiter.execution.parallel.config.fixed.max-pool-size=2
```

Additionally, because the `ForkJoinPool` will by default reject tasks that
would exceed the maximum pool size the
`junit.jupiter.execution.parallel.config.fixed.saturate` property has been
added and will default to `true`. There appears to be no reason to ever set
this `false` but it is there should someone depend on the old behaviour.

These changes were intentionally not made to the `dynamic` strategy to limit
the scope of this pull request. While I can reasonably predict what behaviour
users of the `fixed` strategy might expect, I can not say the same about the
`dynamic` strategy.

Fixes: junit-team#3026
Fixes: junit-team#2545
Fixes: junit-team#1858
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Sep 24, 2022
JUnit Jupiter (and The JUnit Platform) now support limiting the
maximum number of concurrently executing tests via the
`junit.jupiter.execution.parallel.config.fixed.max-pool-size` property.

With Java 9+ the `ForkJoinPool` used by JUnit can be configured with a maximum
pool size. While the number of concurrently executing tests may exceed the
configured parallelism when tests become blocked, it will not exceed the
maximum pool size.

With the following configuration:

```properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=2
```

This example will report between 2-5 tests running concurrently.

```java
class ExampleTest {

    private static final AtomicInteger running = new AtomicInteger();

    @beforeeach
    void increment() {
        System.out.println("Running " + running.incrementAndGet());
    }

    @AfterEach
    void decrement() {
        running.decrementAndGet();
    }

    static IntStream numbers() {
        return IntStream.range(0, 1000);
    }

    @ParameterizedTest
    @MethodSource("numbers")
    void test(int i) throws ExecutionException, InterruptedException {
        Runnable sleep = () -> {
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
        ForkJoinPool.commonPool().submit(sleep).get();
    }
}
```

By also configuring the `max-pool-size` we can ensure the concurrently
executing test does not exceed the configured 2.

```properties
junit.jupiter.execution.parallel.config.fixed.max-pool-size=2
```

Additionally, because the `ForkJoinPool` will by default reject tasks that
would exceed the maximum pool size the
`junit.jupiter.execution.parallel.config.fixed.saturate` property has been
added and will default to `true`. There appears to be no reason to ever set
this `false` but it is there should someone depend on the old behaviour.

These changes were intentionally not made to the `dynamic` strategy to limit
the scope of this pull request. While I can reasonably predict what behaviour
users of the `fixed` strategy might expect, I can not say the same about the
`dynamic` strategy.

Fixes: junit-team#3026
Fixes: junit-team#2545
Fixes: junit-team#1858
mpkorstanje added a commit to mpkorstanje/junit5 that referenced this issue Oct 3, 2022
JUnit Jupiter (and The JUnit Platform) now support limiting the
maximum number of concurrently executing tests via the
`junit.jupiter.execution.parallel.config.fixed.max-pool-size` property.

With Java 9+ the `ForkJoinPool` used by JUnit can be configured with a maximum
pool size. While the number of concurrently executing tests may exceed the
configured parallelism when tests become blocked, it will not exceed the
maximum pool size.

With the following configuration:

```properties
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=2
```

This example will report between 2-5 tests running concurrently.

```java
class ExampleTest {

    private static final AtomicInteger running = new AtomicInteger();

    @beforeeach
    void increment() {
        System.out.println("Running " + running.incrementAndGet());
    }

    @AfterEach
    void decrement() {
        running.decrementAndGet();
    }

    static IntStream numbers() {
        return IntStream.range(0, 1000);
    }

    @ParameterizedTest
    @MethodSource("numbers")
    void test(int i) throws ExecutionException, InterruptedException {
        Runnable sleep = () -> {
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };
        ForkJoinPool.commonPool().submit(sleep).get();
    }
}
```

By also configuring the `max-pool-size` we can ensure the concurrently
executing test does not exceed the configured 2.

```properties
junit.jupiter.execution.parallel.config.fixed.max-pool-size=2
```

Additionally, because the `ForkJoinPool` will by default reject tasks that
would exceed the maximum pool size the
`junit.jupiter.execution.parallel.config.fixed.saturate` property has been
added and will default to `true`. There appears to be no reason to ever set
this `false` but it is there should someone depend on the old behaviour.

These changes were intentionally not made to the `dynamic` strategy to limit
the scope of this pull request. While I can reasonably predict what behaviour
users of the `fixed` strategy might expect, I can not say the same about the
`dynamic` strategy.

Fixes: junit-team#3026
Fixes: junit-team#2545
Fixes: junit-team#1858
@OPeyrusse
Copy link

Because it has not been mentioned yet and, IMO, is related the problem of parallelism, we can even explode the limited parallelism even with one thread.

I attached a stacktrace file to my comment, showing a stackoverflow in one of JUnit test thread because 41 tests were started consecutively by the same thread.
stacktrace.txt

Our product extensively use ForkJoinPools, to run CPU-bound computation as well as IO-bound computations, like database queries. We have our custom pools and manage them well.
What happens in our tests is something that looks as follow (short extract):

	at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService$ExclusiveTask.compute(ForkJoinPoolHierarchicalTestExecutorService.java:185)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base/java.util.concurrent.ForkJoinPool.helpJoin(ForkJoinPool.java:1883)
	at java.base/java.util.concurrent.ForkJoinTask.awaitDone(ForkJoinTask.java:440)
	at java.base/java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:979)
	at com.activeviam.database.sql.internal.jdbc.query.JdbcQueryPreparator.runQuery(JdbcQueryPreparator.java:83)
	... (cut)
	at com.activeviam.database.tck.OurTestClass.optional_testVectorTableWithCondition(AggregateQueriesWithUnnestedVectorsTck.java:187)
	... (cut)
org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)

JUnit launches one test as a ForkJoinTask. It runs code in the thread and finally hits our method JdbcQueryPreparator.runQuery. Our method creates calls ForkJoinTask#get to await for the completion of this task. By design of the ForkJoinPool, the ForkJoinThread will detect this and will try to help. Because JUnit ForkJoinTask and our task are in different pool, JUnit task cannot steal work. The only work it finds is another test ... and so begin the execution of the "next" test before the current is completed.

Seeing @christophs78 here, having a thread-pool parallel executor would have help.
If you consider that this particular case needs its own issue, please tell me and I will open a new issue.

@mpkorstanje
Copy link
Contributor

@OPeyrusse because your issue has a different root cause (not webdriver) and can not be solved/worked around by fixing the max-poolsize I think it would be good to create a separate ticket.

@marcphilipp marcphilipp added this to the 5.10 M1 milestone Oct 21, 2022
marcphilipp pushed a commit that referenced this issue Jan 6, 2023
JUnit Jupiter (and The JUnit Platform) now support limiting the maximum
number of concurrently executing tests via the
`junit.jupiter.execution.parallel.config.fixed.max-pool-size`
configuration parameter.

With Java 9+ the `ForkJoinPool` used by JUnit can be configured with a
maximum pool size. While the number of concurrently executing tests may
exceed the configured parallelism when tests become blocked, it will
not exceed the maximum pool size.

Additionally, because the `ForkJoinPool` will by default reject tasks
that would exceed the maximum pool size the
`junit.jupiter.execution.parallel.config.fixed.saturate` property has
been added and will default to `true`. There appears to be no reason to
ever set this `false` but it is there should someone depend on the old
behavior.

These changes were intentionally not made to the `dynamic` strategy to
limit the scope of this pull request. While I can reasonably predict
what behavior users of the `fixed` strategy might expect, I can not say
the same about the `dynamic` strategy.

Fixes #3026.
Fixes #2545.
Fixes #1858.
@sbrannen sbrannen changed the title Parallelism option ignored when using own ForkJoinPool Parallelism option ignored when using own ForkJoinPool Apr 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment