1616import com .google .errorprone .annotations .ThreadSafe ;
1717import com .google .mu .util .graph .Walker ;
1818import com .google .mu .util .stream .BiStream ;
19+ import com .google .mu .util .stream .Joiner ;
1920
2021/**
2122 * A utility to manipulate temporal ordering (via {@link #checkpoint}) or happens-before (via {@link
@@ -69,15 +70,15 @@ public final class Happenstance<K> {
6970 private static final int SPIN_THRESHOLD = 1000 ;
7071 private final Map <K , Integer > pointToIndex ;
7172 private final int [][] predecessors ;
72- private final int [] completedStatus ; // completion counts
73+ private final int [] checkInStatus ; // 1 if checked in.
7374
7475 private Happenstance (Builder <K > builder ) {
7576 this .pointToIndex = BiStream .from (builder .pointToIndex ).toMap ();
7677 this .predecessors =
7778 builder .predecessors .stream ()
7879 .map (list -> list .stream ().mapToInt (Integer ::intValue ).toArray ())
7980 .toArray (int [][]::new );
80- this .completedStatus = new int [builder .pointToIndex .size ()];
81+ this .checkInStatus = new int [builder .pointToIndex .size ()];
8182 }
8283
8384 /**
@@ -117,15 +118,12 @@ public static final class Builder<K> {
117118 Builder () {}
118119
119120 /**
120- * Defines a happens-before relationship between consecutive {@code sequencePoints}. For
121- * example, {@code inOrder("A", "B", "C")} specifies that sequence points "A", "B", and "C" must
121+ * Defines a ordering between consecutive {@code sequencePoints}. For example,
122+ * {@code inOrder("A", "B", "C")} specifies that sequence points "A", "B", and "C" must
122123 * be completed in that order ("A" before "B", and "B" before "C").
123124 *
124125 * <p>This method should be called to define all sequence point orders before {@link #build} is
125126 * called.
126- *
127- * @throws IllegalArgumentException if adding an edge introduces a cycle in the dependency
128- * graph.
129127 */
130128 @ CanIgnoreReturnValue
131129 @ SafeVarargs
@@ -138,18 +136,24 @@ public final Builder<K> happenInOrder(K... sequencePoints) {
138136 int u = pointToIndex .get (predecessor );
139137 int v = pointToIndex .get (successor );
140138 if (u != v && successors .get (u ).add (v )) {
141- checkArgument (
142- !isReachable (v , u ),
143- "Adding edge %s -> %s creates a cycle" ,
144- predecessor ,
145- successor );
146139 predecessors .get (v ).add (u );
147140 }
148141 });
149142 return this ;
150143 }
151144
145+ /**
146+ * Builds a {@link Happenstance} instance respecting the sequence points ordering.
147+ *
148+ * @throws IllegalArgumentException if the ordering contains a cycle
149+ */
152150 public Happenstance <K > build () {
151+ Walker .inGraph ((Integer index ) -> successors .get (index ).stream ())
152+ .detectCycleFrom (pointToIndex .values ())
153+ .ifPresent (cycle -> {
154+ throw new IllegalArgumentException (
155+ "cycle detected: " + cycle .map (indexToPoint ::get ).collect (Joiner .on (" -> " )));
156+ });
153157 return new Happenstance <>(this );
154158 }
155159
@@ -165,12 +169,6 @@ private int declareSequencePoint(K id) {
165169 return index ;
166170 });
167171 }
168-
169- private boolean isReachable (int from , int to ) {
170- return Walker .inGraph ((Integer index ) -> successors .get (index ).stream ())
171- .breadthFirstFrom (from )
172- .anyMatch (node -> node .intValue () == to );
173- }
174172 }
175173
176174 /**
@@ -218,7 +216,7 @@ public void checkpoint(K sequencePoint) {
218216
219217 private void checkIn (K sequencePoint , Ordering ordering ) {
220218 int index = uponSequencePoint (sequencePoint );
221- int [] statuses = this .completedStatus ;
219+ int [] statuses = this .checkInStatus ;
222220 for (int predecessor : predecessors [index ]) {
223221 for (int spins = 0 ; ordering .read (statuses , predecessor ) == 0 ; spins ++) {
224222 if (spins < SPIN_THRESHOLD ) {
0 commit comments