Skip to content

Commit ddaaca8

Browse files
committed
Fixes bug with FateInterleavingIT.testInterleaving
Rewrote the test. Seemed to expect an interleave to occur at every opportunity when this may not always occur. Now, just expect at least one interleave to occur. This still accomplishes the same goal of the test: test a fate thread interleaving work on multiple fate ids and ensure the order of operations for any given fate id is as expected even with interleaving.
1 parent f2f7030 commit ddaaca8

File tree

1 file changed

+63
-42
lines changed

1 file changed

+63
-42
lines changed

test/src/main/java/org/apache/accumulo/test/fate/FateInterleavingIT.java

+63-42
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626

2727
import java.time.Duration;
2828
import java.util.AbstractMap;
29+
import java.util.ArrayList;
2930
import java.util.Iterator;
31+
import java.util.List;
32+
import java.util.Map;
3033
import java.util.Map.Entry;
3134
import java.util.Set;
3235
import java.util.SortedMap;
@@ -54,7 +57,6 @@
5457
import org.apache.accumulo.core.fate.Repo;
5558
import org.apache.accumulo.harness.SharedMiniClusterBase;
5659
import org.apache.accumulo.server.ServerContext;
57-
import org.apache.accumulo.test.fate.FateTestRunner.TestEnv;
5860
import org.apache.hadoop.io.Text;
5961
import org.junit.jupiter.api.AfterAll;
6062
import org.junit.jupiter.api.BeforeAll;
@@ -101,12 +103,17 @@ protected static void insertTrackingData(FateId tid, FilTestEnv env, String step
101103

102104
@Override
103105
public long isReady(FateId tid, FilTestEnv env) throws Exception {
106+
// First call to isReady will return that it's not ready (defer time of 100ms), inserting
107+
// the data 'isReady1' so we know isReady was called once. The second attempt (after the
108+
// deferral time) will pass as ready (return 0) and insert the data 'isReady2' so we know
109+
// the second call to isReady was made
104110
Thread.sleep(50);
105111
var step = this.getName() + "::isReady";
106-
if (isTrackingDataSet(tid, env, step)) {
112+
if (isTrackingDataSet(tid, env, step + "1")) {
113+
insertTrackingData(tid, env, step + "2");
107114
return 0;
108115
} else {
109-
insertTrackingData(tid, env, step);
116+
insertTrackingData(tid, env, step + "1");
110117
return 100;
111118
}
112119
}
@@ -192,9 +199,9 @@ protected Fate<FilTestEnv> initializeFate(AccumuloClient client, FateStore<FilTe
192199
return new Fate<>(new FilTestEnv(client), store, false, r -> r + "", config);
193200
}
194201

195-
private static Entry<String,String> toIdStep(Entry<Key,Value> e) {
196-
return new AbstractMap.SimpleImmutableEntry<>(e.getKey().getColumnFamily().toString(),
197-
e.getValue().toString());
202+
private static Entry<FateId,String> toIdStep(Entry<Key,Value> e) {
203+
return new AbstractMap.SimpleImmutableEntry<>(
204+
FateId.from(e.getKey().getColumnFamily().toString()), e.getValue().toString());
198205
}
199206

200207
@Test
@@ -208,9 +215,10 @@ protected void testInterleaving(FateStore<FilTestEnv> store, ServerContext sctx)
208215
// This test verifies that fates will interleave in time when their isReady() returns >0 and
209216
// then 0.
210217

211-
FateId[] fateIds = new FateId[3];
218+
final int numFateIds = 3;
219+
FateId[] fateIds = new FateId[numFateIds];
212220

213-
for (int i = 0; i < 3; i++) {
221+
for (int i = 0; i < numFateIds; i++) {
214222
fateIds[i] = store.create();
215223
var txStore = store.reserve(fateIds[i]);
216224
try {
@@ -235,38 +243,50 @@ protected void testInterleaving(FateStore<FilTestEnv> store, ServerContext sctx)
235243
waitFor(store, fateId);
236244
}
237245

238-
var expectedIds =
239-
Set.of(fateIds[0].canonical(), fateIds[1].canonical(), fateIds[2].canonical());
240-
241246
Scanner scanner = client.createScanner(FATE_TRACKING_TABLE);
242-
Iterator<Entry<String,String>> iter = scanner.stream().map(FateInterleavingIT::toIdStep)
243-
.filter(e -> e.getValue().contains("::call")).iterator();
244-
245-
SortedMap<String,String> subset = new TreeMap<>();
246-
247-
Iterators.limit(iter, 3).forEachRemaining(e -> subset.put(e.getKey(), e.getValue()));
248-
249-
// Should see the call() for the first steps of all three fates come first in time
250-
assertTrue(subset.values().stream().allMatch(v -> v.startsWith("FirstOp")));
251-
assertEquals(expectedIds, subset.keySet());
252-
253-
subset.clear();
254-
255-
Iterators.limit(iter, 3).forEachRemaining(e -> subset.put(e.getKey(), e.getValue()));
256-
257-
// Should see the call() for the second steps of all three fates come second in time
258-
assertTrue(subset.values().stream().allMatch(v -> v.startsWith("SecondOp")));
259-
assertEquals(expectedIds, subset.keySet());
260-
261-
subset.clear();
262-
263-
Iterators.limit(iter, 3).forEachRemaining(e -> subset.put(e.getKey(), e.getValue()));
264-
265-
// Should see the call() for the last steps of all three fates come last in time
266-
assertTrue(subset.values().stream().allMatch(v -> v.startsWith("LastOp")));
267-
assertEquals(expectedIds, subset.keySet());
247+
var iter = scanner.stream().map(FateInterleavingIT::toIdStep).iterator();
248+
249+
// we should see the following execution order for all fate ids:
250+
// FirstOp::isReady1, FirstOp::isReady2, FirstOp::call,
251+
// SecondOp::isReady1, SecondOp::isReady2, SecondOp::call,
252+
// LastOp::isReady1, LastOp::isReady2, LastOp::call
253+
// the first isReady of each op will defer the op to be executed later, allowing for the FATE
254+
// thread to interleave and work on another fate id, but may not always interleave.
255+
// It is unlikely that the FATE will not interleave at least once in a run, so we will check
256+
// for at least one occurrence.
257+
int interleaves = 0;
258+
int i = 0;
259+
Map.Entry<FateId,String> prevOp = null;
260+
var expRunOrder = List.of("FirstOp::isReady1", "FirstOp::isReady2", "FirstOp::call",
261+
"SecondOp::isReady1", "SecondOp::isReady2", "SecondOp::call", "LastOp::isReady1",
262+
"LastOp::isReady2", "LastOp::call");
263+
var fateIdsToExpRunOrder = Map.of(fateIds[0], new ArrayList<>(expRunOrder), fateIds[1],
264+
new ArrayList<>(expRunOrder), fateIds[2], new ArrayList<>(expRunOrder));
265+
266+
while (iter.hasNext()) {
267+
var currOp = iter.next();
268+
FateId fateId = currOp.getKey();
269+
String currStep = currOp.getValue();
270+
var expRunOrderFateId = fateIdsToExpRunOrder.get(fateId);
271+
272+
// An interleave occurred if we do not see <FateIdX, OpN::isReady2> immediately after
273+
// <FateIdX, OpN::isReady1>
274+
if (prevOp != null && prevOp.getValue().contains("isReady1")
275+
&& !currOp.equals(new AbstractMap.SimpleImmutableEntry<>(prevOp.getKey(),
276+
prevOp.getValue().replace('1', '2')))) {
277+
interleaves++;
278+
}
279+
assertEquals(currStep, expRunOrderFateId.remove(0));
280+
prevOp = currOp;
281+
i++;
282+
}
268283

269-
assertFalse(iter.hasNext());
284+
assertTrue(interleaves > 0);
285+
assertEquals(i, expRunOrder.size() * numFateIds);
286+
assertEquals(numFateIds, fateIdsToExpRunOrder.size());
287+
for (var expRunOrderFateId : fateIdsToExpRunOrder.values()) {
288+
assertTrue(expRunOrderFateId.isEmpty());
289+
}
270290

271291
} finally {
272292
if (fate != null) {
@@ -329,9 +349,10 @@ protected void testNonInterleaving(FateStore<FilTestEnv> store, ServerContext sc
329349
// This test ensures that when isReady() always returns zero that all the fate steps will
330350
// execute immediately
331351

332-
FateId[] fateIds = new FateId[3];
352+
final int numFateIds = 3;
353+
FateId[] fateIds = new FateId[numFateIds];
333354

334-
for (int i = 0; i < 3; i++) {
355+
for (int i = 0; i < numFateIds; i++) {
335356
fateIds[i] = store.create();
336357
var txStore = store.reserve(fateIds[i]);
337358
try {
@@ -386,10 +407,10 @@ private FateId verifySameIds(Iterator<Entry<Key,Value>> iter, SortedMap<Key,Valu
386407
Text fateId = subset.keySet().iterator().next().getColumnFamily();
387408
assertTrue(subset.keySet().stream().allMatch(k -> k.getColumnFamily().equals(fateId)));
388409

389-
var expectedVals = Set.of("FirstNonInterleavingOp::isReady", "FirstNonInterleavingOp::call",
410+
var expectedVals = List.of("FirstNonInterleavingOp::isReady", "FirstNonInterleavingOp::call",
390411
"SecondNonInterleavingOp::isReady", "SecondNonInterleavingOp::call",
391412
"LastNonInterleavingOp::isReady", "LastNonInterleavingOp::call");
392-
var actualVals = subset.values().stream().map(Value::toString).collect(Collectors.toSet());
413+
var actualVals = subset.values().stream().map(Value::toString).collect(Collectors.toList());
393414
assertEquals(expectedVals, actualVals);
394415

395416
return FateId.from(fateId.toString());

0 commit comments

Comments
 (0)