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