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