I am trying to get to grips with JCStress. To ensure I understand it, I decided to write some simple tests for something that I know must be correct: java.util.concurrent.locks.ReentrantReadWriteLock
.
I wrote some very simple tests to check lock mode compatibility. Unfortunately two of the stress tests are failing:
X_S
:true, true 32,768 FORBIDDEN No default case provided, assume FORBIDDEN
X_X
:true, true 32,767 FORBIDDEN No default case provided, assume FORBIDDEN
It seems to me that one thread should not be able to hold the read lock, whilst another thread also holds the write lock. Likewise, it should be impossible for two threads to simultaneously hold the write lock.
I realise that the problem is likely not with ReentrantReadWriteLock
. I figure that I am probably making some stupid mistake in my jcstress tests with regards to the JMM and reading the state of the locks.
Unfortunately, I am not able to spot the problem. Can someone please help me understand the (stupid?) mistake that I have made?
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.ZZ_Result;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
* |-----------------|
* | COMPATIBILITY |
* |-----------------|
* | | S | X |
* |-----------------|
* | S | YES | NO |
* | X | NO | NO |
* |-----------------|
*/
public class ReentrantReadWriteLockBooleanCompatibilityTest {
@State
public static class S {
public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public boolean shared() {
return lock.readLock().tryLock();
}
public boolean exclusive() {
return lock.writeLock().tryLock();
}
}
@JCStressTest
@Outcome(id = "true, true", expect = Expect.ACCEPTABLE, desc = "T1 and T2 are both acquired S")
public static class S_S {
@Actor
public void actor1(S s, ZZ_Result r) { r.r1 = s.shared(); }
@Actor
public void actor2(S s, ZZ_Result r) { r.r2 = s.shared(); }
}
@JCStressTest
@Outcome(id = "true, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired S, and T2 could not acquire X")
@Outcome(id = "false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired X, and T1 could not acquire S")
public static class S_X {
@Actor
public void actor1(S s, ZZ_Result r) { r.r1 = s.shared(); }
@Actor
public void actor2(S s, ZZ_Result r) { r.r2 = s.exclusive(); }
}
@JCStressTest
@Outcome(id = "true, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire S")
@Outcome(id = "false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired S and T1 could not acquire X")
public static class X_S {
@Actor
public void actor1(S s, ZZ_Result r) { r.r1 = s.exclusive(); }
@Actor
public void actor2(S s, ZZ_Result r) { r.r2 = s.shared(); }
}
@JCStressTest
@Outcome(id = "true, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire X")
@Outcome(id = "false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired X and T1 could not acquire X")
public static class X_X {
@Actor
public void actor1(S s, ZZ_Result r) { r.r1 = s.exclusive(); }
@Actor
public void actor2(S s, ZZ_Result r) { r.r2 = s.exclusive(); }
}
}
I did try asking about this on the jcstress-dev
but never received a response - http://mail.openjdk.java.net/pipermail/jcstress-dev/2018-August/000346.html. Apologies for cross-posting, but I need help with this, and so I am reposting to StackOverflow in the hope of getting attention from a larger audience.