Class Happenstance<K>

java.lang.Object
com.google.mu.testing.concurrent.Happenstance<K>
Type Parameters:
K - the type of the sequence points

@ThreadSafe public final class Happenstance<K> extends Object
A utility to manipulate temporal ordering (via checkpoint(K)) or happens-before (via join(K)) relationships between events in concurrent operations. This is useful for testing, where you want to ensure that certain actions are executed in a specific order.

Example:


 class MyConcurrentTest {
   @Test
   public void testConcurrent() {
     var happens =
         Happenstance.<String>builder()
             .happenInOrder("writtenB", "readingA", "writtenA")
             .happenInOrder("readingB", "writtenB")
             .build();
     Stream.of("A", "B")
         .parallel()
         .forEach(
             input -> {
               happens.join("reading" + input);
               sut.read(input);
               sut.write(input);
               happens.join("written" + input);
               dut.finish(input);
             });
   }
 }
 

Implementation note: this class uses VarHandle instead of high-level synchronization primitives to avoid introducing unintended memory barrier that may result in false negative tests (the test would have failed without the sequence points). When waiting for predecessors, a two-stage back-off strategy is employed: Thread.onSpinWait() is called up to 1000 times to catch tight visibility races in CPU-bound tests without triggering a context switch; if the predecessor is still not ready, Thread.yield() is called to prevent deadlocks or extreme performance degradation in I/O-bound or heavily over-provisioned environments.

The Happenstance.Builder.happenInOrder(K...) method is intended to be called from the main thread to set up the DAG of relationships between sequence points before the checkpoint() or join() method is called from any threads.

Since:
9.9.3
  • Method Details

    • builder

      @SafeVarargs public static <K> Happenstance.Builder<K> builder(K... sequencePoints)
      Returns a new Happenstance.Builder initialized with sequencePoints. No order is defined among these sequence points, until you explicitly call Happenstance.Builder.happenInOrder(K...).
    • builder

      public static <K> Happenstance.Builder<K> builder(Iterable<? extends K> sequencePoints)
      Returns a new Happenstance.Builder initialized with sequencePoints. No order is defined among these sequence points, until you explicitly call Happenstance.Builder.happenInOrder(K...).
    • join

      public void join(K sequencePoint)
      Joins until all predecessors of sequencePoint have checked in, then marks sequencePoint as checked-in and returns.

      This method differs from checkpoint(K) in that it establishes happens-before relationship between sequence points, which means writes happening before join(A) are visible to code after join(B) as long as happenInOrder(A, B) is specified.

      Warning:Using join() inappropriately may result in false negative tests if the SUT has a bug that writes to non-volatile state, because the join() call will accidentally "fix" the bug by making the write visible to other threads.

      Parameters:
      sequencePoint - the sequence point to wait for and mark as completed.
      Throws:
      IllegalArgumentException - if sequencePoint wasn't defined via Happenstance.Builder.happenInOrder(K...).
      IllegalStateException - if sequencePoint has already been marked as completed.
    • checkpoint

      public void checkpoint(K sequencePoint)
      Waits for all predecessors of sequencePoint to have checked in, then marks sequencePoint as checked-in and returns.

      To avoid introducing unintended memory barriers, this method only establishes temporal ordering; no additional happens-before relationship between sequence points is established, which means writes before the checkpoint A may still be invisible to reads after checkpoint B even with happenInOrder(A, B). The SUT itself should establish happens-before relationship if necessary.

      If extra memory barrier doesn't defeat your concurrency tests, and you need to establish happens-before relationships, use join(K) instead.

      Parameters:
      sequencePoint - the sequence point to wait for and mark as completed.
      Throws:
      IllegalArgumentException - if sequencePoint wasn't defined via Happenstance.Builder.happenInOrder(K...).
      IllegalStateException - if sequencePoint has already been marked as completed.