Ответ 1
Ваши тесты проходят при запуске против jcstress 0.3. В версии 0.4 поведение изменилось, чтобы включить результаты проверок здравомыслия, которые запускаются при запуске (см. Эту фиксацию против ошибки jcstress, которая не отображает образцы, собранные во время проверок здравомыслия).
Некоторые проверки работоспособности выполняются в одном потоке, и ваш тест не обрабатывает случай, когда оба участника вызываются одним и тем же потоком; вы проверяете блокировку реентера, поэтому блокировка чтения будет проходить, если блокировка записи уже сохранена.
Это, возможно, ошибка в jcstress, поскольку в документации по @Actor
утверждают, что инварианты:
- Каждый метод вызывается только одним конкретным потоком.
- Каждый метод вызывается ровно один раз на экземпляр
State
.
Хотя документация не так четко сформулирована, генерируемый источник дает понять, что намерение состоит в том, чтобы запускать каждого актера в своем потоке.
Одним из способов обойти это было бы однопроходное дело:
@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();
}
public boolean locked() {
return lock.isWriteLockedByCurrentThread();
}
}
@JCStressTest
@Outcome(id = "true, false, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire S")
@Outcome(id = "false, false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired S and T1 could not acquire X")
@Outcome(id = "true, true, true", expect = Expect.ACCEPTABLE, desc = "T1 acquired X and then acquired S")
public static class X_S {
@Actor
public void actor1(S s, ZZZ_Result r) {
r.r1 = s.exclusive();
}
@Actor
public void actor2(S s, ZZZ_Result r) {
r.r2 = s.locked();
r.r3 = s.shared();
}
}
Или проверьте однопоточный корпус и отметьте его как "интересный", а не принятый:
@State
public static class S {
public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public AtomicReference<Thread> firstThread = new AtomicReference<>();
public boolean shared() {
firstThread.compareAndSet(null, Thread.currentThread());
return lock.readLock().tryLock();
}
public boolean exclusive() {
firstThread.compareAndSet(null, Thread.currentThread());
return lock.writeLock().tryLock();
}
public boolean sameThread() {
return Thread.currentThread().equals(firstThread.get());
}
public boolean locked() {
return lock.isWriteLockedByCurrentThread();
}
}
@JCStressTest
@Outcome(id = "false, true, false, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire X")
@Outcome(id = "false, false, false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired X and T1 could not acquire X")
@Outcome(id = "false, true, true, true", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Both actors ran in the same thread!")
@Outcome(id = "true, true, false, true", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Both actors ran in the same thread!")
public static class X_X {
@Actor
public void actor1(S s, ZZZZ_Result r) {
r.r1 = s.sameThread();
r.r2 = s.exclusive();
}
@Actor
public void actor2(S s, ZZZZ_Result r) {
r.r3 = s.sameThread();
r.r4 = s.exclusive();
}
}
Как вы отметили в комментариях, окончательный @Outcome
в вышеупомянутом тесте никогда не произойдет. Это связано с тем, что однопоточная проверка sanityCheck_Footprints
не перетасовывает участников перед их запуском (см. Метод sanityCheck_Footprints
на сгенерированном тестовом классе).