Skip to content

Surprisingly limited parallel test execution with ResourceLock and ResourceAccessMode.READ #2038

Closed as not planned
@bmalinowsky

Description

@bmalinowsky

I observed the following behavior as detailed below, where test methods using resource lock with READ mode appear to be unnecessarily sequenced.

Even though ResourceLock documents that [...] the annotated element _may_ be executed concurrently with other test classes or methods [...] (emphasis mine), the observed test executions are somewhat a surprise to me.

Steps to reproduce

Consider the following MWE:

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;


//@ResourceLock(value = "sharedResource", mode = ResourceAccessMode.READ)
class ResourceLockTest {
	private static AtomicInteger executingRead = new AtomicInteger();
	private static AtomicInteger maxRead = new AtomicInteger();

	@AfterAll
	static void afterAll() { System.out.println("max executing reads = " + maxRead); }

	@ParameterizedTest
	@MethodSource("ints")
//	@ResourceLock(value = "sharedResource", mode = ResourceAccessMode.READ)
	void test(final int arg) { rememberMax(); }

	@Test
//	@ResourceLock(value = "sharedResource", mode = ResourceAccessMode.READ)
	void test2() { rememberMax(); }

	@Test
//	@ResourceLock(value = "sharedResource", mode = ResourceAccessMode.READ)
	void test3() { rememberMax(); }

	@Test
//	@ResourceLock(value = "sharedResource", mode = ResourceAccessMode.READ)
	void test4() { rememberMax(); }

	@Test
//	@ResourceLock(value = "sharedResource", mode = ResourceAccessMode.READ_WRITE)
	void testWrite() { assertEquals(0, executingRead.get()); }

	private void rememberMax() {
		final int active = executingRead.incrementAndGet();
		maxRead.getAndUpdate(v -> v > active ? v : active);
		try {
			Thread.sleep(50);
		}
		catch (final InterruptedException e) {}
		executingRead.decrementAndGet();
	}

	private static IntStream ints() { return IntStream.rangeClosed(1, 100); }
}

Results

  • Baseline to check correct junit concurrency config, running with resource lock commented out: consistently results in max executing reads of 8 (on my machine), and testWrite can fail. Expected.

  • Enable ResourceLocks on methods:

    • normal test methods with READ run usually in parallel. Expected.
    • the parameterized test has consistently a max read of 1. Correct, but unexpected.
  • Enable ResourceLock on class: consistently a max read of 1. Correct, but very unexpected.

Especially the last point I find surprising. With many test methods, it makes sense to move the lock to the class (and not have it on each method). Such change seems valid but results in degraded execution time.

Context

junitJupiterVersion = '5.5.2'

Gradle 5.6.2
Build time:   2019-09-05 16:13:54 UTC
Revision:     55a5e53d855db8fc7b0e494412fc624051a8e781

Kotlin:       1.3.41
Groovy:       2.5.4
Ant:          Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM:          12.0.2-BellSoft (BellSoft 12.0.2-BellSoft+11)
OS:           Mac OS X 10.15 x86_64

junit.platform.properties

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent
junit.jupiter.execution.parallel.config.dynamic.factor = 1

Deliverables

  • Clarify if the current performance behavior of the built-in ResourceLock is intended

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions