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 8 package com.sleepycat.je.txn; 9 10 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_ABORTS; 11 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_ACTIVE; 12 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_ACTIVE_TXNS; 13 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_BEGINS; 14 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_COMMITS; 15 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_XAABORTS; 16 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_XACOMMITS; 17 import static com.sleepycat.je.dbi.TxnStatDefinition.TXN_XAPREPARES; 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertSame; 20 import static org.junit.Assert.assertTrue; 21 import static org.junit.Assert.fail; 22 23 import java.io.File; 24 import java.util.Arrays; 25 import java.util.Date; 26 27 import org.junit.After; 28 import org.junit.Before; 29 import org.junit.Test; 30 31 import com.sleepycat.je.Cursor; 32 import com.sleepycat.je.CursorConfig; 33 import com.sleepycat.je.Database; 34 import com.sleepycat.je.DatabaseConfig; 35 import com.sleepycat.je.DatabaseEntry; 36 import com.sleepycat.je.DatabaseException; 37 import com.sleepycat.je.DbInternal; 38 import com.sleepycat.je.Environment; 39 import com.sleepycat.je.EnvironmentConfig; 40 import com.sleepycat.je.EnvironmentStats; 41 import com.sleepycat.je.LockMode; 42 import com.sleepycat.je.OperationStatus; 43 import com.sleepycat.je.Transaction; 44 import com.sleepycat.je.TransactionStats; 45 import com.sleepycat.je.VerifyConfig; 46 import com.sleepycat.je.dbi.CursorImpl; 47 import com.sleepycat.je.dbi.DatabaseImpl; 48 import com.sleepycat.je.junit.JUnitThread; 49 import com.sleepycat.je.log.FileManager; 50 import com.sleepycat.je.util.TestUtils; 51 import com.sleepycat.je.utilint.ActiveTxnArrayStat; 52 import com.sleepycat.je.utilint.IntStat; 53 import com.sleepycat.je.utilint.LongStat; 54 import com.sleepycat.je.utilint.StatGroup; 55 import com.sleepycat.util.test.SharedTestUtils; 56 import com.sleepycat.util.test.TestBase; 57 58 /* 59 * @excludeDualMode 60 * This test checks the value of nAborts, but the replication environment may 61 * legitimately have a nAborts value of 1 or 0, due to the 62 * transaction handling in RepImpl.openGroupDB. Exclude the test. 63 * 64 * Test transaction aborts and commits. 65 */ 66 public class TxnEndTest extends TestBase { 67 private static final int NUM_DBS = 1; 68 private Environment env; 69 private final File envHome; 70 private Database[] dbs; 71 private Cursor[] cursors; 72 private JUnitThread junitThread; 73 TxnEndTest()74 public TxnEndTest() { 75 envHome = SharedTestUtils.getTestDir(); 76 } 77 78 @Before setUp()79 public void setUp() 80 throws Exception { 81 82 /* 83 * Run environment without in compressor on so we can check the 84 * compressor queue in a deterministic way. 85 */ 86 super.setUp(); 87 EnvironmentConfig envConfig = TestUtils.initEnvConfig(); 88 envConfig.setTransactional(true); 89 envConfig.setConfigParam(EnvironmentConfig.NODE_MAX_ENTRIES, "6"); 90 envConfig.setConfigParam( 91 EnvironmentConfig.ENV_RUN_IN_COMPRESSOR, "false"); 92 envConfig.setConfigParam( 93 EnvironmentConfig.ENV_RUN_CHECKPOINTER, "false"); 94 envConfig.setConfigParam( 95 EnvironmentConfig.ENV_RUN_CLEANER, "false"); 96 envConfig.setConfigParam( 97 EnvironmentConfig.ENV_RUN_EVICTOR, "false"); 98 envConfig.setAllowCreate(true); 99 env = new Environment(envHome, envConfig); 100 } 101 102 @After tearDown()103 public void tearDown() { 104 if (junitThread != null) { 105 junitThread.shutdown(); 106 junitThread = null; 107 } 108 109 if (env != null) { 110 try { 111 env.close(); 112 } catch (Exception e) { 113 System.out.println("tearDown: " + e); 114 } 115 } 116 env = null; 117 TestUtils.removeFiles("TearDown", envHome, FileManager.JE_SUFFIX); 118 } 119 createDbs()120 private void createDbs() 121 throws DatabaseException { 122 123 dbs = new Database[NUM_DBS]; 124 cursors = new Cursor[NUM_DBS]; 125 126 DatabaseConfig dbConfig = new DatabaseConfig(); 127 dbConfig.setTransactional(true); 128 dbConfig.setAllowCreate(true); 129 for (int i = 0; i < NUM_DBS; i++) { 130 dbs[i] = env.openDatabase(null, "testDB" + i, dbConfig); 131 } 132 } 133 closeAll()134 private void closeAll() 135 throws DatabaseException { 136 137 for (int i = 0; i < NUM_DBS; i++) { 138 dbs[i].close(); 139 } 140 dbs = null; 141 env.close(); 142 env = null; 143 } 144 145 /** 146 * Create cursors with this owning transaction 147 */ createCursors(Transaction txn)148 private void createCursors(Transaction txn) 149 throws DatabaseException { 150 151 for (int i = 0; i < cursors.length; i++) { 152 cursors[i] = dbs[i].openCursor(txn, null); 153 } 154 } 155 156 /** 157 * Close the current set of cursors 158 */ closeCursors()159 private void closeCursors() 160 throws DatabaseException { 161 162 for (int i = 0; i < cursors.length; i++) { 163 cursors[i].close(); 164 } 165 } 166 167 /** 168 * Insert keys from i=start; i <end using a cursor 169 */ cursorInsertData(int start, int end)170 private void cursorInsertData(int start, int end) 171 throws DatabaseException { 172 173 DatabaseEntry key = new DatabaseEntry(); 174 DatabaseEntry data = new DatabaseEntry(); 175 for (int i = 0; i < NUM_DBS; i++) { 176 for (int d = start; d < end; d++) { 177 key.setData(TestUtils.getTestArray(d)); 178 data.setData(TestUtils.getTestArray(d)); 179 cursors[i].put(key, data); 180 } 181 } 182 } 183 /** 184 * Insert keys from i=start; i < end using a db 185 */ dbInsertData(int start, int end, Transaction txn)186 private void dbInsertData(int start, int end, Transaction txn) 187 throws DatabaseException { 188 189 DatabaseEntry key = new DatabaseEntry(); 190 DatabaseEntry data = new DatabaseEntry(); 191 for (int i = 0; i < NUM_DBS; i++) { 192 for (int d = start; d < end; d++) { 193 key.setData(TestUtils.getTestArray(d)); 194 data.setData(TestUtils.getTestArray(d)); 195 dbs[i].put(txn, key, data); 196 } 197 } 198 } 199 200 /** 201 * Modify keys from i=start; i <end 202 */ cursorModifyData(int start, int end, int valueOffset)203 private void cursorModifyData(int start, int end, int valueOffset) 204 throws DatabaseException { 205 206 DatabaseEntry key = new DatabaseEntry(); 207 DatabaseEntry data = new DatabaseEntry(); 208 for (int i = 0; i < NUM_DBS; i++) { 209 OperationStatus status = 210 cursors[i].getFirst(key, data, LockMode.DEFAULT); 211 for (int d = start; d < end; d++) { 212 assertEquals(OperationStatus.SUCCESS, status); 213 byte[] changedVal = 214 TestUtils.getTestArray(d + valueOffset); 215 data.setData(changedVal); 216 cursors[i].putCurrent(data); 217 status = cursors[i].getNext(key, data, LockMode.DEFAULT); 218 } 219 } 220 } 221 222 /** 223 * Delete records from i = start; i < end. 224 */ cursorDeleteData(int start, int end)225 private void cursorDeleteData(int start, int end) 226 throws DatabaseException { 227 228 DatabaseEntry key = new DatabaseEntry(); 229 DatabaseEntry foundData = new DatabaseEntry(); 230 for (int i = 0; i < NUM_DBS; i++) { 231 for (int d = start; d < end; d++) { 232 byte[] searchValue = 233 TestUtils.getTestArray(d); 234 key.setData(searchValue); 235 OperationStatus status = 236 cursors[i].getSearchKey(key, foundData, LockMode.DEFAULT); 237 assertEquals(OperationStatus.SUCCESS, status); 238 assertEquals(OperationStatus.SUCCESS, cursors[i].delete()); 239 } 240 } 241 } 242 243 /** 244 * Delete records with a db. 245 */ dbDeleteData(int start, int end, Transaction txn)246 private void dbDeleteData(int start, int end, Transaction txn) 247 throws DatabaseException { 248 249 DatabaseEntry key = new DatabaseEntry(); 250 for (int i = 0; i < NUM_DBS; i++) { 251 for (int d = start; d < end; d++) { 252 byte[] searchValue = 253 TestUtils.getTestArray(d); 254 key.setData(searchValue); 255 dbs[i].delete(txn, key); 256 } 257 } 258 } 259 260 /** 261 * Check that there are numKeys records in each db, and their value 262 * is i + offset. 263 */ verifyData(int numKeys, int valueOffset)264 private void verifyData(int numKeys, int valueOffset) 265 throws DatabaseException { 266 267 for (int i = 0; i < NUM_DBS; i++) { 268 /* Run verify */ 269 DatabaseImpl dbImpl = DbInternal.getDatabaseImpl(dbs[i]); 270 assertTrue(dbImpl.verify(new VerifyConfig(), 271 dbImpl.getEmptyStats())); 272 273 Cursor verifyCursor = 274 dbs[i].openCursor(null, CursorConfig.READ_UNCOMMITTED); 275 DatabaseEntry key = new DatabaseEntry(); 276 DatabaseEntry data = new DatabaseEntry(); 277 OperationStatus status = 278 verifyCursor.getFirst(key, data, LockMode.DEFAULT); 279 for (int d = 0; d < numKeys; d++) { 280 assertEquals("key=" + d, OperationStatus.SUCCESS, status); 281 byte[] expected = TestUtils.getTestArray(d + valueOffset); 282 assertTrue(Arrays.equals(expected, key.getData())); 283 assertTrue("Expected= " + TestUtils.dumpByteArray(expected) + 284 " saw=" + TestUtils.dumpByteArray(data.getData()), 285 Arrays.equals(expected, data.getData())); 286 status = verifyCursor.getNext(key, data, LockMode.DEFAULT); 287 } 288 /* Should be the end of this database. */ 289 assertTrue("More data than expected", 290 (status != OperationStatus.SUCCESS)); 291 verifyCursor.close(); 292 } 293 } 294 295 /** 296 * Test basic commits, aborts with cursors 297 */ 298 @Test testBasicCursor()299 public void testBasicCursor() 300 throws Throwable { 301 302 try { 303 int numKeys = 7; 304 createDbs(); 305 306 /* Insert more data with a user transaction, commit. */ 307 Transaction txn = env.beginTransaction(null, null); 308 createCursors(txn); 309 cursorInsertData(0, numKeys*2); 310 closeCursors(); 311 txn.commit(); 312 verifyData(numKeys*2, 0); 313 314 /* Insert more data, abort, check that data is unchanged. */ 315 txn = env.beginTransaction(null, null); 316 createCursors(txn); 317 cursorInsertData(numKeys*2, numKeys*3); 318 closeCursors(); 319 txn.abort(); 320 verifyData(numKeys*2, 0); 321 322 /* 323 * Check the in compressor queue, we should have some number of 324 * bins on. If the queue size is 0, then check the processed stats, 325 * the in compressor thread may have already woken up and dealt 326 * with the entries. 327 */ 328 EnvironmentStats envStat = env.getStats(TestUtils.FAST_STATS); 329 long queueSize = envStat.getInCompQueueSize(); 330 assertTrue(queueSize > 0); 331 332 /* Modify data, abort, check that data is unchanged. */ 333 txn = env.beginTransaction(null, null); 334 createCursors(txn); 335 cursorModifyData(0, numKeys * 2, 1); 336 closeCursors(); 337 txn.abort(); 338 verifyData(numKeys*2, 0); 339 340 /* Delete data, abort, check that data is still there. */ 341 txn = env.beginTransaction(null, null); 342 createCursors(txn); 343 cursorDeleteData(numKeys+1, numKeys*2); 344 closeCursors(); 345 txn.abort(); 346 verifyData(numKeys*2, 0); 347 /* Check the in compressor queue, nothing should be loaded. */ 348 envStat = env.getStats(TestUtils.FAST_STATS); 349 assertEquals(queueSize, envStat.getInCompQueueSize()); 350 351 /* Delete data, commit, check that data is gone. */ 352 txn = env.beginTransaction(null, null); 353 createCursors(txn); 354 cursorDeleteData(numKeys, numKeys*2); 355 closeCursors(); 356 txn.commit(); 357 verifyData(numKeys, 0); 358 359 /* Check the inCompressor queue, there should be more entries. */ 360 envStat = env.getStats(TestUtils.FAST_STATS); 361 assertTrue(envStat.getInCompQueueSize() > queueSize); 362 363 closeAll(); 364 365 } catch (Throwable t) { 366 /* Print stacktrace before attempt to run tearDown. */ 367 t.printStackTrace(); 368 throw t; 369 } 370 } 371 372 /** 373 * Test that txn commit fails with open cursors. 374 */ 375 @Test testTxnClose()376 public void testTxnClose() 377 throws DatabaseException { 378 379 createDbs(); 380 Transaction txn = env.beginTransaction(null, null); 381 createCursors(txn); 382 383 try { 384 txn.commit(); 385 fail("Commit should fail, cursors are open."); 386 } catch (IllegalStateException e) { 387 txn.abort(); 388 } 389 closeCursors(); 390 391 txn = env.beginTransaction(null, null); 392 createCursors(txn); 393 closeCursors(); 394 txn.commit(); 395 396 try { 397 txn.abort(); 398 } catch (RuntimeException e) { 399 fail("Txn abort after commit shouldn't fail."); 400 } 401 402 txn = env.beginTransaction(null, null); 403 createCursors(txn); 404 closeCursors(); 405 txn.abort(); 406 407 try { 408 txn.abort(); 409 } catch (RuntimeException e) { 410 fail("Double abort shouldn't fail."); 411 } 412 413 closeAll(); 414 } 415 416 /** 417 * Test use through db. 418 */ 419 @Test testBasicDb()420 public void testBasicDb() 421 throws Throwable { 422 423 try { 424 TransactionStats stats = 425 env.getTransactionStats(TestUtils.FAST_STATS); 426 int initialAborts = 0; 427 assertEquals(initialAborts, stats.getNAborts()); 428 /* 1 commit for adding UP database. */ 429 int initialCommits = 1; 430 assertEquals(initialCommits, stats.getNCommits()); 431 432 long locale = new Date().getTime(); 433 TransactionStats.Active[] at = new TransactionStats.Active[4]; 434 435 for(int i = 0; i < 4; i++) { 436 at[i] = new TransactionStats.Active("TransactionStatForTest", 437 i, i - 1); 438 } 439 440 StatGroup group = new StatGroup("test", "test"); 441 ActiveTxnArrayStat arrayStat = 442 new ActiveTxnArrayStat(group, TXN_ACTIVE_TXNS, at); 443 new LongStat(group, TXN_ABORTS, 12); 444 new LongStat(group, TXN_XAABORTS, 15); 445 new IntStat(group, TXN_ACTIVE, 20); 446 new LongStat(group, TXN_BEGINS, 25); 447 new LongStat(group, TXN_COMMITS, 1); 448 new LongStat(group, TXN_XACOMMITS, 30); 449 new LongStat(group, TXN_XAPREPARES, 20); 450 stats = new TransactionStats(group); 451 452 TransactionStats.Active[] at1 = stats.getActiveTxns(); 453 454 for(int i = 0; i < 4; i++) { 455 assertEquals("TransactionStatForTest", at1[i].getName()); 456 assertEquals(i, at1[i].getId()); 457 assertEquals(i - 1, at1[i].getParentId()); 458 at1[i].toString(); 459 } 460 assertEquals(12, stats.getNAborts()); 461 assertEquals(15, stats.getNXAAborts()); 462 assertEquals(20, stats.getNActive()); 463 assertEquals(25, stats.getNBegins()); 464 assertEquals(1, stats.getNCommits()); 465 assertEquals(30, stats.getNXACommits()); 466 assertEquals(20, stats.getNXAPrepares()); 467 stats.toString(); 468 469 arrayStat.set(null); 470 stats.toString(); 471 472 int numKeys = 7; 473 createDbs(); 474 475 /* Insert data with autocommit. */ 476 dbInsertData(0, numKeys, null); 477 verifyData(numKeys, 0); 478 479 /* Insert data with a txn. */ 480 Transaction txn = env.beginTransaction(null, null); 481 dbInsertData(numKeys, numKeys*2, txn); 482 txn.commit(); 483 verifyData(numKeys*2, 0); 484 485 stats = env.getTransactionStats(TestUtils.FAST_STATS); 486 assertEquals(initialAborts, stats.getNAborts()); 487 assertEquals((initialCommits + 1 + // 1 explicit commit above 488 (1 * NUM_DBS) + // 1 per create/open 489 (numKeys*NUM_DBS)), // 1 per record, using autotxn 490 stats.getNCommits()); 491 492 /* Delete data with a txn, abort. */ 493 txn = env.beginTransaction(null, null); 494 dbDeleteData(numKeys, numKeys * 2, txn); 495 verifyData(numKeys, 0); // verify w/dirty read 496 txn.abort(); 497 498 closeAll(); 499 } catch (Throwable t) { 500 t.printStackTrace(); 501 throw t; 502 } 503 } 504 505 /** 506 * Test TransactionStats. 507 */ 508 @Test testTxnStats()509 public void testTxnStats() 510 throws Throwable { 511 512 try { 513 TransactionStats stats = 514 env.getTransactionStats(TestUtils.FAST_STATS); 515 int initialAborts = 0; 516 assertEquals(initialAborts, stats.getNAborts()); 517 /* 1 commit for adding UP database. */ 518 int numBegins = 1; 519 int numCommits = 1; 520 assertEquals(numBegins, stats.getNBegins()); 521 assertEquals(numCommits, stats.getNCommits()); 522 523 int numKeys = 7; 524 createDbs(); 525 numBegins += NUM_DBS; // 1 begins per database 526 numCommits += NUM_DBS; // 1 commits per database 527 stats = env.getTransactionStats(TestUtils.FAST_STATS); 528 assertEquals(numBegins, stats.getNBegins()); 529 assertEquals(numCommits, stats.getNCommits()); 530 531 /* Insert data with autocommit. */ 532 dbInsertData(0, numKeys, null); 533 numBegins += (numKeys * NUM_DBS); 534 numCommits += (numKeys * NUM_DBS); 535 stats = env.getTransactionStats(TestUtils.FAST_STATS); 536 assertEquals(numBegins, stats.getNBegins()); 537 assertEquals(numCommits, stats.getNCommits()); 538 verifyData(numKeys, 0); 539 540 /* Insert data with a txn. */ 541 Transaction txn = env.beginTransaction(null, null); 542 numBegins++; 543 stats = env.getTransactionStats(TestUtils.FAST_STATS); 544 assertEquals(numBegins, stats.getNBegins()); 545 assertEquals(numCommits, stats.getNCommits()); 546 assertEquals(1, stats.getNActive()); 547 dbInsertData(numKeys, numKeys*2, txn); 548 txn.commit(); 549 numCommits++; 550 stats = env.getTransactionStats(TestUtils.FAST_STATS); 551 assertEquals(numBegins, stats.getNBegins()); 552 assertEquals(numCommits, stats.getNCommits()); 553 assertEquals(0, stats.getNActive()); 554 verifyData(numKeys*2, 0); 555 556 /* Delete data with a txn, abort. */ 557 txn = env.beginTransaction(null, null); 558 numBegins++; 559 stats = env.getTransactionStats(TestUtils.FAST_STATS); 560 assertEquals(numBegins, stats.getNBegins()); 561 assertEquals(numCommits, stats.getNCommits()); 562 assertEquals(1, stats.getNActive()); 563 564 dbDeleteData(numKeys, numKeys * 2, txn); 565 verifyData(numKeys, 0); // verify w/dirty read 566 txn.abort(); 567 stats = env.getTransactionStats(TestUtils.FAST_STATS); 568 assertEquals(numBegins, stats.getNBegins()); 569 assertEquals(numCommits, stats.getNCommits()); 570 assertEquals(initialAborts + 1, stats.getNAborts()); 571 assertEquals(0, stats.getNActive()); 572 573 closeAll(); 574 } catch (Throwable t) { 575 t.printStackTrace(); 576 throw t; 577 } 578 } 579 580 /** 581 * Test db creation and deletion 582 */ 583 584 @Test testDbCreation()585 public void testDbCreation() 586 throws DatabaseException { 587 588 Transaction txnA = env.beginTransaction(null, null); 589 Transaction txnB = env.beginTransaction(null, null); 590 591 DatabaseConfig dbConfig = new DatabaseConfig(); 592 dbConfig.setAllowCreate(true); 593 dbConfig.setTransactional(true); 594 Database dbA = 595 env.openDatabase(txnA, "foo", dbConfig); 596 597 /* Try to see this database with another txn -- we should not see it. */ 598 599 dbConfig.setAllowCreate(false); 600 601 try { 602 txnB.setLockTimeout(1000); 603 604 env.openDatabase(txnB, "foo", dbConfig); 605 fail("Shouldn't be able to open foo"); 606 } catch (DatabaseException e) { 607 } 608 609 /* txnB must be aborted since openDatabase timed out. */ 610 txnB.abort(); 611 612 /* Open this database with the same txn and another handle. */ 613 Database dbC = 614 env.openDatabase(txnA, "foo", dbConfig); 615 616 /* Now commit txnA and txnB should be able to open this. */ 617 txnA.commit(); 618 txnB = env.beginTransaction(null, null); 619 Database dbB = 620 env.openDatabase(txnB, "foo", dbConfig); 621 txnB.commit(); 622 623 /* XXX, test db deletion. */ 624 625 dbA.close(); 626 dbB.close(); 627 dbC.close(); 628 } 629 630 /* Test that the transaction is unusable after a close. */ 631 @Test testClose()632 public void testClose() 633 throws DatabaseException { 634 635 Transaction txnA = env.beginTransaction(null, null); 636 txnA.commit(); 637 638 try { 639 env.openDatabase(txnA, "foo", null); 640 fail("Should not be able to use a closed transaction"); 641 } catch (IllegalArgumentException expected) { 642 } 643 } 644 645 /** 646 * Simulates a race condition between two threads that previously caused a 647 * latch deadlock. [#19321] 648 * 649 * One thread is aborting a txn. The other thread is using the same txn to 650 * perform a cursor operation. While the BIN is held, it attempts to get a 651 * non-blocking lock. 652 */ 653 @Test testAbortLatchDeadlock()654 public void testAbortLatchDeadlock() { 655 656 /* Create DB. */ 657 final DatabaseConfig dbConfig = new DatabaseConfig(); 658 dbConfig.setAllowCreate(true); 659 dbConfig.setTransactional(true); 660 final Database db = env.openDatabase(null, "foo", dbConfig); 661 662 /* Insert one record. */ 663 final DatabaseEntry key = new DatabaseEntry(new byte[1]); 664 final DatabaseEntry data = new DatabaseEntry(new byte[1]); 665 assertSame(OperationStatus.SUCCESS, 666 db.putNoOverwrite(null, key, data)); 667 668 /* Begin txn, to be shared by both threads. */ 669 final Transaction txn = env.beginTransaction(null, null); 670 671 /* Simulate cursor operation that latches BIN. */ 672 final Cursor cursor = db.openCursor(txn, null); 673 assertSame(OperationStatus.SUCCESS, cursor.put(key, data)); 674 final CursorImpl cursorImpl = DbInternal.getCursorImpl(cursor); 675 cursorImpl.latchBIN(); 676 677 /* Run abort in a separate thread. */ 678 junitThread = new JUnitThread("testAbortLatchDeadlock") { 679 @Override 680 public void testBody() { 681 682 /* 683 * The cursor is not closed before the abort is allowed to 684 * continue, sometimes causing an "open cursors" exception, 685 * depending on timing. This is acceptable for this test, 686 * since we are checking that a hang does not occur. 687 */ 688 try { 689 txn.abort(); 690 } catch (IllegalStateException e) { 691 assertTrue(e.getMessage().contains 692 ("detected open cursors")); 693 } 694 } 695 }; 696 junitThread.start(); 697 698 /* Wait for abort to attempt latch on BIN. */ 699 while (cursorImpl.getBIN().getLatchNWaiters() != 1) { 700 try { 701 Thread.sleep(1); 702 } catch (InterruptedException e) { 703 fail(); 704 } 705 } 706 707 /* 708 * Simulate cursor operation that gets non-blocking lock. Before the 709 * fix [#19321], a latch deadlock would occur here. 710 */ 711 try { 712 cursorImpl.getLocker().nonBlockingLock 713 (123L, LockType.WRITE, false, DbInternal.getDatabaseImpl(db)); 714 fail(); 715 } catch (IllegalStateException expected) { 716 } finally { 717 /* Release latch, allow abort to continue. */ 718 cursorImpl.releaseBIN(); 719 cursor.close(); 720 } 721 722 /* Finish test. */ 723 Throwable t = null; 724 try { 725 junitThread.finishTest(); 726 } catch (Throwable e) { 727 t = e; 728 } finally { 729 junitThread = null; 730 } 731 if (t != null) { 732 t.printStackTrace(); 733 fail(t.toString()); 734 } 735 736 db.close(); 737 } 738 739 /* 740 * Test the case where truncateDatabase and removeDatabase operations are 741 * done on the same database in the same txn. [#19636] 742 */ 743 @Test testTruncateDeleteDB()744 public void testTruncateDeleteDB() { 745 /* Create test DB. */ 746 final DatabaseConfig dbConfig = new DatabaseConfig(); 747 dbConfig.setAllowCreate(true); 748 dbConfig.setTransactional(true); 749 String dbName = "test-db"; 750 751 /* Test single truncation and removement. */ 752 Database db = env.openDatabase(null, dbName, dbConfig); 753 db.close(); 754 Transaction txn = env.beginTransaction(null, null); 755 /* Truncate and remove a database in the same txn. */ 756 env.truncateDatabase(txn, dbName, false); 757 env.removeDatabase(txn, dbName); 758 txn.abort(); 759 /* No database is removed after aborting the txn.. */ 760 assertEquals(1, env.getDatabaseNames().size()); 761 txn = env.beginTransaction(null, null); 762 env.truncateDatabase(txn, dbName, false); 763 env.removeDatabase(txn, dbName); 764 txn.commit(); 765 /* the database has been removed after committing the txn. */ 766 assertEquals(0, env.getDatabaseNames().size()); 767 768 /* Test multiple truncations before single removement. */ 769 db = env.openDatabase(null, dbName, dbConfig); 770 db.close(); 771 txn = env.beginTransaction(null, null); 772 /* Truncate a database three times then remove it in the same txn. */ 773 env.truncateDatabase(txn, dbName, false); 774 env.truncateDatabase(txn, dbName, false); 775 env.truncateDatabase(txn, dbName, false); 776 env.removeDatabase(txn, dbName); 777 txn.abort(); 778 /* No database is removed after aborting the txn. */ 779 assertEquals(1, env.getDatabaseNames().size()); 780 txn = env.beginTransaction(null, null); 781 env.truncateDatabase(txn, dbName, false); 782 env.truncateDatabase(txn, dbName, false); 783 env.truncateDatabase(txn, dbName, false); 784 env.removeDatabase(txn, dbName); 785 txn.commit(); 786 /* the database has been removed after committing the txn. */ 787 assertEquals(0, env.getDatabaseNames().size()); 788 } 789 } 790