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 happens-before relationships (via join(K)) 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()
             .sequence("writtenB", "readingA", "writtenA")
             .sequence("readingB", "writtenB")
             .build();
     Stream.of("A", "B")
         .parallel()
         .forEach(
             input -> {
               happens.join("reading" + input);
               sut.read(input);
               sut.write(input);
               happens.join("written" + input);
               sut.finish(input);
             });
   }
 }
 

Implementation note: when waiting for predecessors, a three-stage back-off strategy is employed: Thread.onSpinWait() is called up to 1000 times to catch tight races in CPU-bound tests without triggering a context switch; if the predecessor is still not ready, Thread.yield() is called up to 100 times to prevent deadlocks or extreme performance degradation in heavily over-provisioned environments; beyond that the thread parks for 50µs at a time, so that an I/O-bound SUT operation taking hundreds of milliseconds doesn't burn a core per waiter.

The Happenstance.Builder.sequence(K...) method is intended to be called from the main thread to set up the DAG of relationships between sequence points before the 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.sequence(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.sequence(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 establishes happens-before relationship between sequence points, which means writes happening before join(A) are visible to code after join(B) as long as sequence(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.

      If the calling thread is interrupted while waiting, this method throws AssertionError without checking in, and leaves the thread's interrupt status set. An Error is used so that the bail-out isn't swallowed by a catch (Exception) in the code under test.

      Parameters:
      sequencePoint - the sequence point to wait for and mark as completed.
      Throws:
      IllegalArgumentException - if sequencePoint wasn't defined via Happenstance.Builder.sequence(K...).
      IllegalStateException - if sequencePoint has already been marked as completed.
      AssertionError - if the calling thread is interrupted while waiting for predecessors.
    • checkpoint

      @Deprecated public void checkpoint(K sequencePoint)
      Deprecated.
      The JIT and the CPU can move the surrounding plain reads and writes across a checkpoint, so the ordering applies to the check-in calls themselves, not to the SUT code around them. Use join(K) instead.
      Waits for all predecessors of sequencePoint to have checked in, then marks sequencePoint as checked-in and returns.

      If the calling thread is interrupted while waiting, this method throws AssertionError without checking in, and leaves the thread's interrupt status set. An Error is used so that the bail-out isn't swallowed by a catch (Exception) in the code under test. The interrupt status is only consulted while actually waiting, so a sequence point with no predecessors, or whose predecessors have already checked in, checks in normally even on an interrupted thread.

      Parameters:
      sequencePoint - the sequence point to wait for and mark as completed.
      Throws:
      IllegalArgumentException - if sequencePoint wasn't defined via Happenstance.Builder.sequence(K...).
      IllegalStateException - if sequencePoint has already been marked as completed.
      AssertionError - if the calling thread is interrupted while waiting for predecessors.