1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 package com.sleepycat.je.rep.txn;
8 
9 import static org.junit.Assert.assertTrue;
10 
11 import java.util.ArrayList;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Random;
15 import java.util.Set;
16 
17 import com.sleepycat.je.CursorConfig;
18 import com.sleepycat.je.Database;
19 import com.sleepycat.je.DatabaseConfig;
20 import com.sleepycat.je.DatabaseException;
21 import com.sleepycat.je.Transaction;
22 import com.sleepycat.je.rep.ReplicatedEnvironment;
23 import com.sleepycat.je.rep.txn.Utils.RollbackData;
24 import com.sleepycat.je.rep.txn.Utils.SavedData;
25 import com.sleepycat.je.rep.txn.Utils.TestData;
26 import com.sleepycat.persist.EntityCursor;
27 import com.sleepycat.persist.EntityStore;
28 import com.sleepycat.persist.PrimaryIndex;
29 import com.sleepycat.persist.StoreConfig;
30 
31 /**
32  * A RollbackWorkload is a pattern of data operations designed to test the
33  * permutations of ReplayTxn rollback.
34  *
35  * Each workload defines a set of operations that will happen before and after
36  * the syncup matchpoint. The workload knows what should be rolled back and
37  * and what should be preserved, and can check the results. Concrete workload
38  * classes add themselves to the static set of WorkloadCombinations, and the
39  * RollbackTest generates a test case for each workload element.
40  */
41 abstract class RollbackWorkload {
42 
43     private static final String TEST_DB = "testdb";
44     private static final String DB_NAME_PREFIX = "persist#" + TEST_DB + "#";
45     private static final String TEST_DB2 = "testdb2";
46     private static final String DB_NAME_PREFIX2 = "persist#" + TEST_DB2 + "#";
47 
48     private static final boolean verbose = Boolean.getBoolean("verbose");
49 
50     final Set<TestData> saved;
51     final Set<TestData> rolledBack;
52 
53     /*
54      * Most tests use only a single store. A second store may be used when
55      * multiple databases are needed, but be careful not to use the same key in
56      * both stores, since the code that dumps the stores and compares records
57      * uses only the record key.
58      */
59     private EntityStore store;
60     private EntityStore store2;
61     PrimaryIndex<Long, TestData> testIndex;
62     PrimaryIndex<Long, TestData> testIndex2;
63 
64     final Random rand = new Random(10);
65 
RollbackWorkload()66     RollbackWorkload() {
67         saved = new HashSet<>();
68         rolledBack = new HashSet<>();
69     }
70 
isMasterDiesWorkload()71     boolean isMasterDiesWorkload() {
72         return true;
73     }
74 
masterSteadyWork(ReplicatedEnvironment master)75     void masterSteadyWork(ReplicatedEnvironment master) {
76     }
77 
beforeMasterCrash(ReplicatedEnvironment master)78     void beforeMasterCrash(ReplicatedEnvironment master)
79         throws DatabaseException {
80     }
81 
afterMasterCrashBeforeResumption(ReplicatedEnvironment master)82     void afterMasterCrashBeforeResumption(ReplicatedEnvironment master)
83         throws DatabaseException {
84     }
85 
afterReplicaCrash(ReplicatedEnvironment master)86     void afterReplicaCrash(ReplicatedEnvironment master)
87         throws DatabaseException {
88     }
89 
noLockConflict()90     boolean noLockConflict() {
91         return true;
92     }
93 
releaseDbLocks()94     void releaseDbLocks() {
95     }
96 
openStore(ReplicatedEnvironment replicator, boolean readOnly)97     boolean openStore(ReplicatedEnvironment replicator, boolean readOnly) {
98         store = openStoreByName(replicator, TEST_DB, readOnly);
99         if (store == null) {
100             testIndex = null;
101             return false;
102         }
103         testIndex = store.getPrimaryIndex(Long.class, TestData.class);
104         return true;
105     }
106 
openStore2(ReplicatedEnvironment replicator, boolean readOnly)107     boolean openStore2(ReplicatedEnvironment replicator, boolean readOnly) {
108         store2 = openStoreByName(replicator, TEST_DB2, readOnly);
109         if (store2 == null) {
110             testIndex2 = null;
111             return false;
112         }
113         testIndex2 = store2.getPrimaryIndex(Long.class, TestData.class);
114         return true;
115     }
116 
openStoreByName(ReplicatedEnvironment replicator, String storeName, boolean readOnly)117     EntityStore openStoreByName(ReplicatedEnvironment replicator,
118                                 String storeName,
119                                 boolean readOnly) {
120         if (readOnly) {
121             String catalogDbName =
122                 "persist#" + storeName + "#com.sleepycat.persist.formats";
123             if (!replicator.getDatabaseNames().contains(catalogDbName)) {
124                 return null;
125             }
126         }
127 
128         StoreConfig config = new StoreConfig();
129         config.setAllowCreate(true);
130         config.setTransactional(true);
131         config.setReadOnly(readOnly);
132         return new EntityStore(replicator, storeName, config);
133     }
134 
135     /**
136      * Dump all the data out of the test db on this replicator. Use
137      * READ_UNCOMMITTED so we can see the data for in-flight transactions.
138      */
dumpData(ReplicatedEnvironment replicator)139     Set<TestData> dumpData(ReplicatedEnvironment replicator)
140         throws DatabaseException {
141 
142         Set<TestData> dumpedData = new HashSet<>();
143 
144         if (openStore(replicator, true /* readOnly */)) {
145             dumpedData.addAll(dumpIndexData(testIndex, replicator));
146         }
147 
148         if (openStore2(replicator, true /* readOnly */)) {
149             dumpedData.addAll(dumpIndexData(testIndex2, replicator));
150         }
151 
152         close();
153 
154         if (verbose) {
155             System.out.println("Replicator " + replicator.getNodeName());
156             displayDump(dumpedData);
157         }
158 
159         return dumpedData;
160     }
161 
dumpIndexData(PrimaryIndex<Long, TestData> index, ReplicatedEnvironment replicator)162     Set<TestData> dumpIndexData(PrimaryIndex<Long, TestData> index,
163                                 ReplicatedEnvironment replicator) {
164 
165         Transaction txn = replicator.beginTransaction(null, null);
166 
167         EntityCursor<TestData> cursor =
168             index.entities(txn, CursorConfig.READ_UNCOMMITTED);
169 
170         Set<TestData> dumpedData = new HashSet<>();
171 
172         for (TestData t : cursor) {
173             dumpedData.add(t);
174         }
175 
176         cursor.close();
177         txn.commit();
178 
179         return dumpedData;
180     }
181 
displayDump(Set<TestData> data)182     private void displayDump(Set<TestData> data) {
183         for (TestData t : data) {
184             System.out.println(t);
185         }
186     }
187 
close()188     void close() throws DatabaseException {
189         if (store != null) {
190             store.close();
191             store = null;
192             testIndex = null;
193         }
194         if (store2 != null) {
195             store2.close();
196             store2 = null;
197             testIndex2 = null;
198         }
199     }
200 
removeStore(ReplicatedEnvironment master, String dbNamePrefix)201     void removeStore(ReplicatedEnvironment master,
202                              String dbNamePrefix) {
203         for (String dbName : master.getDatabaseNames()) {
204             if (dbName.startsWith(dbNamePrefix)) {
205                 master.removeDatabase(null, dbName);
206             }
207         }
208     }
209 
containsAllData(ReplicatedEnvironment replicator)210     boolean containsAllData(ReplicatedEnvironment replicator)
211         throws DatabaseException {
212 
213         Set<TestData> dataInStore = dumpData(replicator);
214         if (!checkSubsetAndRemove(dataInStore, saved, "saved")) {
215             return false;
216         }
217         if (!checkSubsetAndRemove(dataInStore, rolledBack, "rollback")) {
218             return false;
219         }
220         if (dataInStore.size() == 0) {
221             return true;
222         }
223         if (verbose) {
224             System.out.println("DataInStore has an unexpected " +
225                                "remainder: " + dataInStore);
226         }
227         return false;
228     }
229 
containsSavedData(ReplicatedEnvironment replicator)230     boolean containsSavedData(ReplicatedEnvironment replicator)
231         throws DatabaseException {
232 
233         Set<TestData> dataInStore = dumpData(replicator);
234         if (!checkSubsetAndRemove(dataInStore, saved, "saved")) {
235             return false;
236         }
237         if (dataInStore.size() == 0) {
238             return true;
239         }
240         if (verbose) {
241             System.out.println("DataInStore has an unexpected " +
242                 "remainder: " + dataInStore);
243         }
244         return false;
245     }
246 
checkSubsetAndRemove(Set<TestData> dataInStore, Set<TestData> subset, String checkType)247     private boolean checkSubsetAndRemove(Set<TestData> dataInStore,
248                                          Set<TestData> subset,
249                                          String checkType) {
250         if (dataInStore.containsAll(subset)) {
251             /*
252              * Doesn't work, why?
253              * boolean removed = dataInStore.removeAll(subset);
254              */
255             for (TestData t: subset) {
256                 boolean removed = dataInStore.remove(t);
257                 assert removed;
258             }
259             return true;
260         }
261 
262         if (verbose) {
263             System.out.println("DataInStore didn't contain " +
264                                " subset " + checkType +
265                                ". DataInStore=" + dataInStore +
266                                " subset = " + subset);
267         }
268         return false;
269     }
270 
insertRandom(PrimaryIndex<Long, TestData> index, Transaction txn, Set<TestData> addToSet)271     void insertRandom(PrimaryIndex<Long, TestData> index,
272                       Transaction txn,
273                       Set<TestData> addToSet) {
274         assertTrue(addToSet == saved || addToSet == rolledBack);
275         int payload = rand.nextInt();
276         TestData data = (addToSet == saved) ?
277             new SavedData(payload) : new RollbackData(payload);
278         /* Must call put() to assign primary key before adding to set. */
279         index.put(txn, data);
280         addToSet.add(data);
281     }
282 
283     /**
284      * This workload rolls back an unfinished transaction which is entirely
285      * after the matchpoint. It tests a complete undo.
286      */
287     static class IncompleteTxnAfterMatchpoint extends RollbackWorkload {
288 
IncompleteTxnAfterMatchpoint()289         IncompleteTxnAfterMatchpoint() {
290             super();
291         }
292 
293         @Override
beforeMasterCrash(ReplicatedEnvironment master)294         void beforeMasterCrash(ReplicatedEnvironment master)
295             throws DatabaseException {
296 
297             openStore(master, false /* readOnly */);
298 
299             Transaction matchpointTxn = master.beginTransaction(null, null);
300             insertRandom(testIndex, matchpointTxn, saved);
301             insertRandom(testIndex, matchpointTxn, saved);
302 
303             /* This commit will serve as the syncup matchpoint */
304             matchpointTxn.commit();
305 
306             /* This data is in an uncommitted txn, it will be rolled back. */
307             Transaction rollbackTxn = master.beginTransaction(null, null);
308             insertRandom(testIndex, rollbackTxn, rolledBack);
309             insertRandom(testIndex, rollbackTxn, rolledBack);
310             insertRandom(testIndex, rollbackTxn, rolledBack);
311             insertRandom(testIndex, rollbackTxn, rolledBack);
312 
313             close();
314         }
315 
316         /**
317          * The second workload should have a fewer number of updates from the
318          * incomplete, rolled back transaction from workloadBeforeNodeLeaves,
319          * so that we can check that the vlsn sequences have been rolled back
320          * too.  This work is happening while the crashed node is still down.
321          */
322         @Override
afterMasterCrashBeforeResumption(ReplicatedEnvironment master)323         void afterMasterCrashBeforeResumption(ReplicatedEnvironment master)
324             throws DatabaseException {
325 
326             openStore(master, false /* readOnly */);
327 
328             Transaction whileAsleepTxn = master.beginTransaction(null, null);
329             insertRandom(testIndex, whileAsleepTxn, saved);
330             whileAsleepTxn.commit();
331 
332             Transaction secondTxn = master.beginTransaction(null, null);
333             insertRandom(testIndex, secondTxn, saved);
334             close();
335         }
336 
337         @Override
afterReplicaCrash(ReplicatedEnvironment master)338         void afterReplicaCrash(ReplicatedEnvironment master)
339             throws DatabaseException {
340 
341             openStore(master, false /* readOnly */);
342 
343             Transaction whileReplicaDeadTxn =
344                 master.beginTransaction(null, null);
345             insertRandom(testIndex, whileReplicaDeadTxn, saved);
346             whileReplicaDeadTxn.commit();
347 
348             Transaction secondTxn = master.beginTransaction(null, null);
349             insertRandom(testIndex, secondTxn, saved);
350             close();
351         }
352     }
353 
354     /**
355      * This workload creates an unfinished transaction in which all operations
356      * exist before the matchpoint. It should be preserved, and then undone
357      * by an abort issued by the new master.
358      */
359     static class IncompleteTxnBeforeMatchpoint extends RollbackWorkload {
360 
IncompleteTxnBeforeMatchpoint()361         IncompleteTxnBeforeMatchpoint() {
362             super();
363         }
364 
365         @Override
beforeMasterCrash(ReplicatedEnvironment master)366         void beforeMasterCrash(ReplicatedEnvironment master)
367             throws DatabaseException {
368 
369             openStore(master, false /* readOnly */);
370 
371             Transaction matchpointTxn = master.beginTransaction(null, null);
372             insertRandom(testIndex, matchpointTxn, saved);
373             insertRandom(testIndex, matchpointTxn, saved);
374 
375             /* This data is in an uncommitted txn, it will be rolled back. */
376             Transaction rollbackTxnA = master.beginTransaction(null, null);
377             insertRandom(testIndex, rollbackTxnA, rolledBack);
378             insertRandom(testIndex, rollbackTxnA, rolledBack);
379 
380             /* This commit will serve as the syncup matchpoint */
381             matchpointTxn.commit();
382 
383             /* This data is in an uncommitted txn, it will be rolled back. */
384             Transaction rollbackTxnB = master.beginTransaction(null, null);
385             insertRandom(testIndex, rollbackTxnB, rolledBack);
386             close();
387         }
388 
389         /**
390          * The second workload will re-insert some of the data that
391          * was rolled back.
392          */
393         @Override
afterMasterCrashBeforeResumption(ReplicatedEnvironment master)394         void afterMasterCrashBeforeResumption(ReplicatedEnvironment master)
395             throws DatabaseException {
396 
397             openStore(master, false /* readOnly */);
398 
399             Transaction whileAsleepTxn = master.beginTransaction(null, null);
400             insertRandom(testIndex, whileAsleepTxn, saved);
401             whileAsleepTxn.commit();
402 
403             Transaction secondTxn =  master.beginTransaction(null, null);
404             insertRandom(testIndex, secondTxn, saved);
405             close();
406         }
407 
408         @Override
afterReplicaCrash(ReplicatedEnvironment master)409         void afterReplicaCrash(ReplicatedEnvironment master)
410             throws DatabaseException {
411 
412             openStore(master, false /* readOnly */);
413 
414             Transaction whileReplicaDeadTxn =
415                 master.beginTransaction(null, null);
416             insertRandom(testIndex, whileReplicaDeadTxn, saved);
417             whileReplicaDeadTxn.commit();
418 
419             Transaction secondTxn =  master.beginTransaction(null, null);
420             insertRandom(testIndex, secondTxn, saved);
421             close();
422         }
423     }
424 
425     /**
426      * This workload creates an unfinished transaction in which operations
427      * exist before and after the matchpoint. Only the operations after the
428      * matchpoint should be rolled back. Ultimately, the rollback transaction
429      * will be aborted, because the master is down.
430      */
431     static class IncompleteTxnStraddlesMatchpoint extends RollbackWorkload {
432 
IncompleteTxnStraddlesMatchpoint()433         IncompleteTxnStraddlesMatchpoint() {
434             super();
435         }
436 
437         @Override
beforeMasterCrash(ReplicatedEnvironment master)438         void beforeMasterCrash(ReplicatedEnvironment master)
439             throws DatabaseException {
440 
441             openStore(master, false /* readOnly */);
442 
443             Transaction matchpointTxn = master.beginTransaction(null, null);
444             insertRandom(testIndex, matchpointTxn, saved);
445             insertRandom(testIndex, matchpointTxn, saved);
446 
447             /* This data is in an uncommitted txn, it will be rolled back. */
448             Transaction rollbackTxn = master.beginTransaction(null, null);
449             insertRandom(testIndex, rollbackTxn, rolledBack);
450             insertRandom(testIndex, rollbackTxn, rolledBack);
451 
452             /* This commit will serve as the syncup matchpoint */
453             matchpointTxn.commit();
454 
455             /* This data is in an uncommitted txn, it will be rolled back. */
456             insertRandom(testIndex, rollbackTxn, rolledBack);
457             close();
458         }
459 
460         /**
461          * The second workload will re-insert some of the data that
462          * was rolled back.
463          */
464         @Override
afterMasterCrashBeforeResumption(ReplicatedEnvironment master)465         void afterMasterCrashBeforeResumption(ReplicatedEnvironment master)
466             throws DatabaseException {
467 
468             openStore(master, false /* readOnly */);
469 
470             Transaction whileAsleepTxn = master.beginTransaction(null, null);
471             insertRandom(testIndex, whileAsleepTxn, saved);
472             whileAsleepTxn.commit();
473 
474             Transaction secondTxn =  master.beginTransaction(null, null);
475             insertRandom(testIndex, secondTxn, saved);
476             close();
477         }
478 
479         @Override
afterReplicaCrash(ReplicatedEnvironment master)480         void afterReplicaCrash(ReplicatedEnvironment master)
481             throws DatabaseException {
482 
483             openStore(master, false /* readOnly */);
484 
485             Transaction whileReplicaDeadTxn =
486                 master.beginTransaction(null, null);
487             insertRandom(testIndex, whileReplicaDeadTxn, saved);
488             whileReplicaDeadTxn.commit();
489 
490             Transaction secondTxn =  master.beginTransaction(null, null);
491             insertRandom(testIndex, secondTxn, saved);
492             close();
493         }
494     }
495 
496     /**
497      * Exercise the rollback of database operations.
498      */
499     static class DatabaseOpsStraddlesMatchpoint extends RollbackWorkload {
500 
501         private DatabaseConfig dbConfig;
502 
503         private List<String> expectedDbNames;
504         private List<String> allDbNames;
505         private Transaction incompleteTxn;
506 
DatabaseOpsStraddlesMatchpoint()507         DatabaseOpsStraddlesMatchpoint() {
508             super();
509             dbConfig = new DatabaseConfig();
510             dbConfig.setAllowCreate(true);
511             dbConfig.setTransactional(true);
512         }
513 
514         /* Executed by node that is the first master. */
515         @Override
beforeMasterCrash(ReplicatedEnvironment master)516         void beforeMasterCrash(ReplicatedEnvironment master)
517             throws DatabaseException {
518 
519             Transaction txn1 = master.beginTransaction(null, null);
520             Transaction txn2 = master.beginTransaction(null, null);
521             Transaction txn3 = master.beginTransaction(null, null);
522 
523             Database dbA = master.openDatabase(txn1, "AAA", dbConfig);
524             dbA.close();
525             Database dbB = master.openDatabase(txn2, "BBB", dbConfig);
526             dbB.close();
527 
528             /*
529              * This will be the syncpoint.
530              * txn 1 is commmited.
531              * txn 2 will be partially rolled back.
532              * txn 3 will be fully rolled back.
533              */
534             txn1.commit();
535 
536             /* Txn 2 will have to be partially rolled back. */
537             master.removeDatabase(txn2, "BBB");
538 
539             /* Txn 3 will be fully rolled back. */
540             master.removeDatabase(txn3, "AAA");
541 
542             expectedDbNames = new ArrayList<>();
543             expectedDbNames.add("AAA");
544 
545             allDbNames = new ArrayList<>();
546             allDbNames.add("AAA");
547         }
548 
549         /* Executed by node that was a replica, and then became master */
550         @Override
afterMasterCrashBeforeResumption(ReplicatedEnvironment master)551         void afterMasterCrashBeforeResumption(ReplicatedEnvironment master)
552             throws DatabaseException {
553 
554             Transaction whileAsleepTxn = master.beginTransaction(null, null);
555             Database dbC = master.openDatabase(whileAsleepTxn, "CCC",
556                                                dbConfig);
557             dbC.close();
558             whileAsleepTxn.commit();
559 
560             incompleteTxn = master.beginTransaction(null, null);
561             Database dbD = master.openDatabase(incompleteTxn, "DDD", dbConfig);
562             dbD.close();
563 
564             expectedDbNames = new ArrayList<>();
565             expectedDbNames.add("AAA");
566             expectedDbNames.add("CCC");
567             expectedDbNames.add("DDD");
568         }
569 
570         @Override
releaseDbLocks()571         void releaseDbLocks() {
572             incompleteTxn.commit();
573         }
574 
575         /* Executed while node that has never been a master is asleep. */
576         @Override
afterReplicaCrash(ReplicatedEnvironment master)577         void afterReplicaCrash(ReplicatedEnvironment master)
578             throws DatabaseException {
579 
580             Transaction whileReplicaDeadTxn =
581                 master.beginTransaction(null, null);
582             master.renameDatabase(whileReplicaDeadTxn,
583                                   "CCC", "renamedCCC");
584             whileReplicaDeadTxn.commit();
585             expectedDbNames = new ArrayList<>();
586             expectedDbNames.add("AAA");
587             expectedDbNames.add("renamedCCC");
588             expectedDbNames.add("DDD");
589         }
590 
noLockConflict()591         boolean noLockConflict() {
592             return false;
593         }
594 
595         @Override
containsSavedData(ReplicatedEnvironment master)596         boolean containsSavedData(ReplicatedEnvironment master) {
597             List<String> names = master.getDatabaseNames();
598             if (!(names.containsAll(expectedDbNames) &&
599                   expectedDbNames.containsAll(names))) {
600                 System.out.println("master names = " + names +
601                                    " expected= " + expectedDbNames);
602                 return false;
603             }
604             return true;
605         }
606 
607         @Override
containsAllData(ReplicatedEnvironment master)608         boolean containsAllData(ReplicatedEnvironment master)
609             throws DatabaseException {
610 
611             List<String> names = master.getDatabaseNames();
612             return names.containsAll(allDbNames) &&
613                 allDbNames.containsAll(names);
614         }
615     }
616 
617     /**
618      * An incomplete transaction containing LN writes is rolled back, and then
619      * the database containing those LNs is removed.  Recovery of this entire
620      * sequence requires rollback to process the LNs belonging to the removed
621      * database.  When this test was written, rollback threw an NPE when such
622      * LNs were encountered (due to the removed database), and a bug fix was
623      * required [#22052].
624      */
625     static class RemoveDatabaseAfterRollback
626         extends IncompleteTxnAfterMatchpoint {
627 
628         @Override
afterReplicaCrash(ReplicatedEnvironment master)629         void afterReplicaCrash(ReplicatedEnvironment master)
630             throws DatabaseException {
631 
632             super.afterReplicaCrash(master);
633             removeStore(master, DB_NAME_PREFIX);
634             rolledBack.addAll(saved);
635             saved.clear();
636         }
637     }
638 
639     /**
640      * Similar to RemoveDatabaseAfterRollback except that the txn contains LNs
641      * in multiple databases (two in this test) and only some databases (one in
642      * this test), not all, are removed after rollback.
643      *
644      * When undoing an LN in recovery, if the LN is in a removed database,
645      * the undo code will do nothing.  This is the case handed by the earlier
646      * test, RemoveDatabaseAfterRollback.
647      *
648      * But when the LN is not in a removed database, the undo must proceed.
649      * The tricky thing is that a TxnChain must be created even though some of
650      * the entries in the actual txn chain (in the data log) will be for LNs in
651      * removed databases.
652      *
653      * This test does not subclass RemoveDatabaseAfterRollback or
654      * IncompleteTxnAfterMatchpoint because those tests only use one database.
655      *
656      * [#22071]
657      */
658     static class RemoveSomeDatabasesAfterRollback extends RollbackWorkload {
659 
660         @Override
beforeMasterCrash(ReplicatedEnvironment master)661         void beforeMasterCrash(ReplicatedEnvironment master)
662             throws DatabaseException {
663 
664             openStore(master, false /* readOnly */);
665             openStore2(master, false /* readOnly */);
666 
667             Transaction matchpointTxn = master.beginTransaction(null, null);
668             insertRandom(testIndex, matchpointTxn, saved);
669             insertRandom(testIndex2, matchpointTxn, saved);
670             insertRandom(testIndex, matchpointTxn, saved);
671             insertRandom(testIndex2, matchpointTxn, saved);
672 
673             /* This commit will serve as the syncup matchpoint */
674             matchpointTxn.commit();
675 
676             /* This data is in an uncommitted txn, it will be rolled back. */
677             Transaction rollbackTxn = master.beginTransaction(null, null);
678             insertRandom(testIndex, rollbackTxn, rolledBack);
679             insertRandom(testIndex2, rollbackTxn, rolledBack);
680             insertRandom(testIndex, rollbackTxn, rolledBack);
681             insertRandom(testIndex2, rollbackTxn, rolledBack);
682 
683             close();
684         }
685 
686         /**
687          * @see IncompleteTxnAfterMatchpoint#afterMasterCrashBeforeResumption
688          */
689         @Override
afterMasterCrashBeforeResumption(ReplicatedEnvironment master)690         void afterMasterCrashBeforeResumption(ReplicatedEnvironment master)
691             throws DatabaseException {
692 
693             openStore(master, false /* readOnly */);
694             openStore2(master, false /* readOnly */);
695 
696             Transaction whileAsleepTxn = master.beginTransaction(null, null);
697             insertRandom(testIndex, whileAsleepTxn, saved);
698             insertRandom(testIndex2, whileAsleepTxn, saved);
699             whileAsleepTxn.commit();
700 
701             Transaction secondTxn = master.beginTransaction(null, null);
702             insertRandom(testIndex, secondTxn, saved);
703             insertRandom(testIndex2, secondTxn, saved);
704             close();
705         }
706 
707         @Override
afterReplicaCrash(ReplicatedEnvironment master)708         void afterReplicaCrash(ReplicatedEnvironment master)
709             throws DatabaseException {
710 
711             openStore(master, false /* readOnly */);
712             openStore2(master, false /* readOnly */);
713 
714             Transaction whileReplicaDeadTxn =
715                 master.beginTransaction(null, null);
716             insertRandom(testIndex, whileReplicaDeadTxn, saved);
717             insertRandom(testIndex2, whileReplicaDeadTxn, saved);
718             whileReplicaDeadTxn.commit();
719 
720             Transaction secondTxn = master.beginTransaction(null, null);
721             insertRandom(testIndex, secondTxn, saved);
722             insertRandom(testIndex2, secondTxn, saved);
723 
724             Set<TestData> index2Data = dumpIndexData(testIndex2, master);
725             close();
726             removeStore(master, DB_NAME_PREFIX2);
727             rolledBack.addAll(index2Data);
728             saved.removeAll(index2Data);
729         }
730     }
731 
732     /**
733      * This workload simulates a master that is just doing a steady stream of
734      * work.
735      */
736     static class SteadyWork extends RollbackWorkload {
737 
738         private Transaction straddleTxn = null;
739 
740         @Override
isMasterDiesWorkload()741         boolean isMasterDiesWorkload() {
742             return false;
743         }
744 
745         @Override
masterSteadyWork(ReplicatedEnvironment master)746         void masterSteadyWork(ReplicatedEnvironment master)
747             throws DatabaseException {
748 
749             if (straddleTxn != null) {
750                 straddleTxn.commit();
751             }
752 
753             openStore(master, false /* readOnly */);
754 
755             Transaction matchpointTxn = master.beginTransaction(null, null);
756             insertRandom(testIndex, matchpointTxn, saved);
757             insertRandom(testIndex, matchpointTxn, saved);
758 
759             /* This transaction straddles the matchpoint. */
760             straddleTxn = master.beginTransaction(null, null);
761             insert();
762             insert();
763 
764             /* This commit will serve as the syncup matchpoint */
765             matchpointTxn.commit();
766 
767             for (int i = 0; i < 10; i++) {
768                 insert();
769             }
770 
771             close();
772         }
773 
insert()774         private void insert()  {
775             TestData d = new SavedData(12);
776             /* Must call put() to assign primary key before adding to set. */
777             testIndex.put(straddleTxn, d);
778             saved.add(d);
779         }
780     }
781 }
782