Смущен тестом jcstress при сбое ReentrantReadWriteLock # tryLock - PullRequest
0 голосов
/ 25 сентября 2018

Я пытаюсь справиться с JCStress.Чтобы убедиться, что я это понимаю, я решил написать несколько простых тестов для того, что, как я знаю, должно быть правильным: java.util.concurrent.locks.ReentrantReadWriteLock.

Я написал несколько очень простых тестов для проверки совместимости режима блокировки.К сожалению, два стресс-теста не пройдены:

  1. X_S:

    true, true        32,768     FORBIDDEN  No default case provided, assume FORBIDDEN
    
  2. X_X:

    true, true        32,767     FORBIDDEN  No default case provided, assume FORBIDDEN
    

Мне кажется, что один поток не может удерживать блокировку чтения, в то время как другой поток также удерживает блокировку записи.Аналогичным образом, два потока не должны одновременно удерживать блокировку записи.

Я понимаю, что проблема, вероятно, не в ReentrantReadWriteLock.Я полагаю, что я, возможно, делаю какую-то глупую ошибку в моих тестах jcstress в отношении JMM и чтения состояния блокировок.

К сожалению, я не могу определить проблему.Может кто-нибудь помочь мне понять (глупую?) Ошибку, которую я совершил?

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(); }
    }
}

Я пытался спросить об этом на jcstress-dev, но так и не получил ответ - http://mail.openjdk.java.net/pipermail/jcstress-dev/2018-August/000346.html. Извинениядля кросс-постинга, но мне нужна помощь с этим, поэтому я делаю репост в StackOverflow в надежде привлечь внимание широкой аудитории.

1 Ответ

0 голосов
/ 01 октября 2018

Ваши тесты проходят при запуске с jcstress 0.3.В версии 0.4 поведение изменено и теперь включает результаты проверок работоспособности, запускаемых при запуске (см. this commit против ошибки jcstress пропускает образцы, собранные во время проверок работоспособности ).

Некоторые проверки работоспособности выполняются в одном потоке, и ваш тест не обрабатывает случай, когда оба актера вызываются одним и тем же потоком;вы тестируете блокировку reentrant , поэтому блокировка чтения пройдет, если блокировка записи уже удерживается.

Возможно, это ошибка в 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 в сгенерированном тестовом классе).

...