junit5 icon indicating copy to clipboard operation
junit5 copied to clipboard

Fix potential double acquisition in `SingleLock`

Open leonard84 opened this issue 1 year ago • 10 comments

SingleLock uses a ForkJoinPool.ManagedBlocker for efficient blocking in a fork-join pool. In the isReleasable() method it calls tryLock() without remembering the outcome. The block method also unconditionally called lockInterruptibly() even if the lock was already acquired. The problem that can arise is that the lock is acquired multiple times, but only released once.

CompositeLockManagedBlocker is also updated, so that block() checks for acquired before locking.

Overview


I hereby agree to the terms of the JUnit Contributor License Agreement.


Definition of Done

leonard84 avatar Aug 26 '24 11:08 leonard84

Spock started to experience strange deadlocks the thread dumps point to the locking infrastructure being the culprit.

I checked the JavaDoc of ManagedBlocker again, and it was updated, or we didn't implement it correctly back then.

class ManagedLocker implements ManagedBlocker {
  final ReentrantLock lock;
  boolean hasLock = false;
  ManagedLocker(ReentrantLock lock) { this.lock = lock; }
  public boolean block() {
    if (!hasLock)
      lock.lock();
    return true;
  }
  public boolean isReleasable() {
    return hasLock || (hasLock = lock.tryLock());
  }
}

The JavaDoc suggests that this isn't the issue, although the example implementation guards against repeated locking, so I think we should follow its guidance.

Toward this end, implementations of method isReleasable must be amenable to repeated invocation. Neither method is invoked after a prior invocation of isReleasable or block returns true.

leonard84 avatar Aug 26 '24 11:08 leonard84

I tried to test if this branch fixes the issue in Spock's CI jobs with the help of jitpack.io. Unfortunately, it fails to build. As they only use Java 8 by default https://docs.jitpack.io/building/#java-version

leonard84 avatar Aug 26 '24 12:08 leonard84

There was a time we used to provide a custom .jitpack.yml file...

  • #2815

sormuras avatar Aug 26 '24 14:08 sormuras

I went ahead and (temporarily) added such a configuration file to your branch in order to fix the build, right? Or does it have to be present in the main branch? Let's find out.

.jitpack.yml could read:

jdk:
  - openjdk21
before_install:
  - sdk install java 21.0.2-open
  - sdk use java 21.0.2-open
install:
  - ./gradlew --version
  - ./gradlew publishToMavenLocal -x test

sormuras avatar Aug 26 '24 14:08 sormuras

https://jitpack.io/com/github/junit-team/junit5/leo~fix-potential-lock-isssue-SNAPSHOT/build.log now reports:

Git error. Retrying.
Git error. Max repo size 500MB exceeded

Progress... now the Gradle Daemon "stalls" or so: https://jitpack.io/com/github/junit-team/junit5/leo~fix-potential-lock-isssue-r5.11.0-ge71983c-15/build.log

[...]
Build tool exit code: 0
Looking for artifacts...
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 -Dhttps.protocols=TLSv1.2
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 -Dhttps.protocols=TLSv1.2
Could not write standard input to Gradle build daemon.
java.io.IOException: Stream closed
	at java.lang.ProcessBuilder$NullOutputStream.write(ProcessBuilder.java:433)
	at java.io.OutputStream.write(OutputStream.java:116)
[...]

sormuras avatar Aug 26 '24 15:08 sormuras

The actual build was successful:

BUILD SUCCESSFUL in 2m 43s
441 actionable tasks: 299 executed, 140 from cache, 2 up-to-date
Configuration cache entry stored.
Build tool exit code: 0

I think this comes from jitpack

Looking for artifacts...

as well as the actual error a bit further down

Unrecognized option: --add-exports
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

From the help message it looks like they use an ancient gradle version to do something with the artifacts.

https://docs.gradle.org/4.8.1/userguide/gradle_daemon.html

Maybe it would help if we reset Java back to 8 after building.

leonard84 avatar Aug 27 '24 17:08 leonard84

Back to

Git error. Retrying.
Git error. Max repo size 500MB exceeded

did you do something to fix it?

leonard84 avatar Aug 27 '24 18:08 leonard84

Mh, nothing special. Let me touch the file to trigger a recompilation...

sormuras avatar Aug 27 '24 18:08 sormuras

Stop! java 8.0.382-open is not available.

https://jitpack.io/com/github/junit-team/junit5/leo~fix-potential-lock-isssue-r5.11.0-gc4a1e22-18/build.log

sormuras avatar Aug 27 '24 19:08 sormuras

Yeah, I don't have sdk man, so I was just using whatever version Gemini suggested for Java 8.

leonard84 avatar Aug 28 '24 08:08 leonard84

Reopening to backport to the 5.10.x branch:

  • [ ] backport code changes (40bc1c4146d91b595668bd06402dba0a83d05b9f and 044f5cf4b066d3f2121a33db4a8d3952355d5225?)
  • [ ] move release notes entry to appropriate file(s)
  • [ ] backport release notes

sbrannen avatar Sep 13 '24 15:09 sbrannen

Reopening to backport to the 5.10.x branch:

Oops. This is a PR which cannot be reopened.

See #3988 instead.

sbrannen avatar Sep 13 '24 15:09 sbrannen