1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: TransactionTest.java,v 12.11 2008/02/07 17:12:31 mark Exp $
7 */
8
9package com.sleepycat.collections.test;
10
11import java.io.File;
12import java.io.IOException;
13import java.util.Iterator;
14import java.util.List;
15import java.util.SortedSet;
16
17import junit.framework.Test;
18import junit.framework.TestCase;
19import junit.framework.TestSuite;
20
21import com.sleepycat.collections.CurrentTransaction;
22import com.sleepycat.collections.StoredCollections;
23import com.sleepycat.collections.StoredContainer;
24import com.sleepycat.collections.StoredIterator;
25import com.sleepycat.collections.StoredList;
26import com.sleepycat.collections.StoredSortedMap;
27import com.sleepycat.collections.TransactionRunner;
28import com.sleepycat.collections.TransactionWorker;
29import com.sleepycat.compat.DbCompat;
30import com.sleepycat.db.CursorConfig;
31import com.sleepycat.db.Database;
32import com.sleepycat.db.DatabaseConfig;
33import com.sleepycat.db.DatabaseEntry;
34import com.sleepycat.db.DatabaseException;
35import com.sleepycat.db.Environment;
36import com.sleepycat.db.EnvironmentConfig;
37import com.sleepycat.db.Transaction;
38import com.sleepycat.db.TransactionConfig;
39import com.sleepycat.util.RuntimeExceptionWrapper;
40import com.sleepycat.util.test.SharedTestUtils;
41import com.sleepycat.util.test.TestEnv;
42
43/**
44 * @author Mark Hayes
45 */
46public class TransactionTest extends TestCase {
47
48    private static final Long ONE = new Long(1);
49    private static final Long TWO = new Long(2);
50    private static final Long THREE = new Long(3);
51
52    /**
53     * Runs a command line collection test.
54     * @see #usage
55     */
56    public static void main(String[] args)
57        throws Exception {
58
59        if (args.length == 1 &&
60            (args[0].equals("-h") || args[0].equals("-help"))) {
61            usage();
62        } else {
63            junit.framework.TestResult tr =
64                junit.textui.TestRunner.run(suite());
65            if (tr.errorCount() > 0 ||
66                tr.failureCount() > 0) {
67                System.exit(1);
68            } else {
69                System.exit(0);
70            }
71        }
72    }
73
74    private static void usage() {
75
76        System.out.println(
77              "Usage: java com.sleepycat.collections.test.TransactionTest"
78            + " [-h | -help]\n");
79        System.exit(2);
80    }
81
82    public static Test suite()
83        throws Exception {
84
85        TestSuite suite = new TestSuite(TransactionTest.class);
86        return suite;
87    }
88
89    private Environment env;
90    private CurrentTransaction currentTxn;
91    private Database store;
92    private StoredSortedMap map;
93    private TestStore testStore = TestStore.BTREE_UNIQ;
94
95    public TransactionTest(String name) {
96
97        super(name);
98    }
99
100    public void setUp()
101        throws Exception {
102
103        SharedTestUtils.printTestName(SharedTestUtils.qualifiedTestName(this));
104        env = TestEnv.TXN.open("TransactionTests");
105        currentTxn = CurrentTransaction.getInstance(env);
106        store = testStore.open(env, dbName(0));
107        map = new StoredSortedMap(store, testStore.getKeyBinding(),
108                                  testStore.getValueBinding(), true);
109    }
110
111    public void tearDown() {
112
113        try {
114            if (store != null) {
115                store.close();
116            }
117            if (env != null) {
118                env.close();
119            }
120        } catch (Exception e) {
121            System.out.println("Ignored exception during tearDown: " + e);
122        } finally {
123            /* Ensure that GC can cleanup. */
124            store = null;
125            env = null;
126            currentTxn = null;
127            map = null;
128            testStore = null;
129        }
130    }
131
132    private String dbName(int i) {
133
134        return "txn-test-" + getName() + '-' + i;
135    }
136
137    public void testGetters()
138        throws Exception {
139
140        assertNotNull(env);
141        assertNotNull(currentTxn);
142        assertNull(currentTxn.getTransaction());
143
144        currentTxn.beginTransaction(null);
145        assertNotNull(currentTxn.getTransaction());
146        currentTxn.commitTransaction();
147        assertNull(currentTxn.getTransaction());
148
149        currentTxn.beginTransaction(null);
150        assertNotNull(currentTxn.getTransaction());
151        currentTxn.abortTransaction();
152        assertNull(currentTxn.getTransaction());
153
154        // read-uncommitted property should be inherited
155
156        assertTrue(!isReadUncommitted(map));
157        assertTrue(!isReadUncommitted(map.values()));
158        assertTrue(!isReadUncommitted(map.keySet()));
159        assertTrue(!isReadUncommitted(map.entrySet()));
160
161        StoredSortedMap other = (StoredSortedMap)
162            StoredCollections.configuredMap
163                (map, CursorConfig.READ_UNCOMMITTED);
164        assertTrue(isReadUncommitted(other));
165        assertTrue(isReadUncommitted(other.values()));
166        assertTrue(isReadUncommitted(other.keySet()));
167        assertTrue(isReadUncommitted(other.entrySet()));
168        assertTrue(!isReadUncommitted(map));
169        assertTrue(!isReadUncommitted(map.values()));
170        assertTrue(!isReadUncommitted(map.keySet()));
171        assertTrue(!isReadUncommitted(map.entrySet()));
172
173        // read-committed property should be inherited
174
175        assertTrue(!isReadCommitted(map));
176        assertTrue(!isReadCommitted(map.values()));
177        assertTrue(!isReadCommitted(map.keySet()));
178        assertTrue(!isReadCommitted(map.entrySet()));
179
180        other = (StoredSortedMap)
181            StoredCollections.configuredMap
182                (map, CursorConfig.READ_COMMITTED);
183        assertTrue(isReadCommitted(other));
184        assertTrue(isReadCommitted(other.values()));
185        assertTrue(isReadCommitted(other.keySet()));
186        assertTrue(isReadCommitted(other.entrySet()));
187        assertTrue(!isReadCommitted(map));
188        assertTrue(!isReadCommitted(map.values()));
189        assertTrue(!isReadCommitted(map.keySet()));
190        assertTrue(!isReadCommitted(map.entrySet()));
191    }
192
193    public void testTransactional()
194        throws Exception {
195
196        // is transactional because DB_AUTO_COMMIT was passed to
197        // Database.open()
198        //
199        assertTrue(map.isTransactional());
200        store.close();
201        store = null;
202
203        // is not transactional
204        //
205        DatabaseConfig dbConfig = new DatabaseConfig();
206        DbCompat.setTypeBtree(dbConfig);
207        dbConfig.setAllowCreate(true);
208        Database db = DbCompat.testOpenDatabase
209            (env, null, dbName(1), null, dbConfig);
210        map = new StoredSortedMap(db, testStore.getKeyBinding(),
211                                      testStore.getValueBinding(), true);
212        assertTrue(!map.isTransactional());
213        map.put(ONE, ONE);
214        readCheck(map, ONE, ONE);
215        db.close();
216
217        // is transactional
218        //
219        dbConfig.setTransactional(true);
220        currentTxn.beginTransaction(null);
221        db = DbCompat.testOpenDatabase
222            (env, currentTxn.getTransaction(), dbName(2), null, dbConfig);
223        currentTxn.commitTransaction();
224        map = new StoredSortedMap(db, testStore.getKeyBinding(),
225                                      testStore.getValueBinding(), true);
226        assertTrue(map.isTransactional());
227        currentTxn.beginTransaction(null);
228        map.put(ONE, ONE);
229        readCheck(map, ONE, ONE);
230        currentTxn.commitTransaction();
231        db.close();
232    }
233
234    public void testExceptions()
235        throws Exception {
236
237        try {
238            currentTxn.commitTransaction();
239            fail();
240        } catch (IllegalStateException expected) {}
241
242        try {
243            currentTxn.abortTransaction();
244            fail();
245        } catch (IllegalStateException expected) {}
246    }
247
248    public void testNested()
249        throws Exception {
250
251        if (!DbCompat.NESTED_TRANSACTIONS) {
252            return;
253        }
254        assertNull(currentTxn.getTransaction());
255
256        Transaction txn1 = currentTxn.beginTransaction(null);
257        assertNotNull(txn1);
258        assertTrue(txn1 == currentTxn.getTransaction());
259
260        assertNull(map.get(ONE));
261        assertNull(map.put(ONE, ONE));
262        assertEquals(ONE, map.get(ONE));
263
264        Transaction txn2 = currentTxn.beginTransaction(null);
265        assertNotNull(txn2);
266        assertTrue(txn2 == currentTxn.getTransaction());
267        assertTrue(txn1 != txn2);
268
269        assertNull(map.put(TWO, TWO));
270        assertEquals(TWO, map.get(TWO));
271
272        Transaction txn3 = currentTxn.beginTransaction(null);
273        assertNotNull(txn3);
274        assertTrue(txn3 == currentTxn.getTransaction());
275        assertTrue(txn1 != txn2);
276        assertTrue(txn1 != txn3);
277        assertTrue(txn2 != txn3);
278
279        assertNull(map.put(THREE, THREE));
280        assertEquals(THREE, map.get(THREE));
281
282        Transaction txn = currentTxn.abortTransaction();
283        assertTrue(txn == txn2);
284        assertTrue(txn == currentTxn.getTransaction());
285        assertNull(map.get(THREE));
286        assertEquals(TWO, map.get(TWO));
287
288        txn3 = currentTxn.beginTransaction(null);
289        assertNotNull(txn3);
290        assertTrue(txn3 == currentTxn.getTransaction());
291        assertTrue(txn1 != txn2);
292        assertTrue(txn1 != txn3);
293        assertTrue(txn2 != txn3);
294
295        assertNull(map.put(THREE, THREE));
296        assertEquals(THREE, map.get(THREE));
297
298        txn = currentTxn.commitTransaction();
299        assertTrue(txn == txn2);
300        assertTrue(txn == currentTxn.getTransaction());
301        assertEquals(THREE, map.get(THREE));
302        assertEquals(TWO, map.get(TWO));
303
304        txn = currentTxn.commitTransaction();
305        assertTrue(txn == txn1);
306        assertTrue(txn == currentTxn.getTransaction());
307        assertEquals(THREE, map.get(THREE));
308        assertEquals(TWO, map.get(TWO));
309        assertEquals(ONE, map.get(ONE));
310
311        txn = currentTxn.commitTransaction();
312        assertNull(txn);
313        assertNull(currentTxn.getTransaction());
314        assertEquals(THREE, map.get(THREE));
315        assertEquals(TWO, map.get(TWO));
316        assertEquals(ONE, map.get(ONE));
317    }
318
319    public void testRunnerCommit()
320        throws Exception {
321
322        commitTest(false);
323    }
324
325    public void testExplicitCommit()
326        throws Exception {
327
328        commitTest(true);
329    }
330
331    private void commitTest(final boolean explicit)
332        throws Exception {
333
334        final TransactionRunner runner = new TransactionRunner(env);
335        runner.setAllowNestedTransactions(DbCompat.NESTED_TRANSACTIONS);
336
337        assertNull(currentTxn.getTransaction());
338
339        runner.run(new TransactionWorker() {
340            public void doWork() throws Exception {
341                final Transaction txn1 = currentTxn.getTransaction();
342                assertNotNull(txn1);
343                assertNull(map.put(ONE, ONE));
344                assertEquals(ONE, map.get(ONE));
345
346                runner.run(new TransactionWorker() {
347                    public void doWork() throws Exception {
348                        final Transaction txn2 = currentTxn.getTransaction();
349                        assertNotNull(txn2);
350                        if (DbCompat.NESTED_TRANSACTIONS) {
351                            assertTrue(txn1 != txn2);
352                        } else {
353                            assertTrue(txn1 == txn2);
354                        }
355                        assertNull(map.put(TWO, TWO));
356                        assertEquals(TWO, map.get(TWO));
357                        assertEquals(ONE, map.get(ONE));
358                        if (DbCompat.NESTED_TRANSACTIONS && explicit) {
359                            currentTxn.commitTransaction();
360                        }
361                    }
362                });
363
364                Transaction txn3 = currentTxn.getTransaction();
365                assertSame(txn1, txn3);
366
367                assertEquals(TWO, map.get(TWO));
368                assertEquals(ONE, map.get(ONE));
369            }
370        });
371
372        assertNull(currentTxn.getTransaction());
373    }
374
375    public void testRunnerAbort()
376        throws Exception {
377
378        abortTest(false);
379    }
380
381    public void testExplicitAbort()
382        throws Exception {
383
384        abortTest(true);
385    }
386
387    private void abortTest(final boolean explicit)
388        throws Exception {
389
390        final TransactionRunner runner = new TransactionRunner(env);
391        runner.setAllowNestedTransactions(DbCompat.NESTED_TRANSACTIONS);
392
393        assertNull(currentTxn.getTransaction());
394
395        runner.run(new TransactionWorker() {
396            public void doWork() throws Exception {
397                final Transaction txn1 = currentTxn.getTransaction();
398                assertNotNull(txn1);
399                assertNull(map.put(ONE, ONE));
400                assertEquals(ONE, map.get(ONE));
401
402                if (DbCompat.NESTED_TRANSACTIONS) {
403                    try {
404                        runner.run(new TransactionWorker() {
405                            public void doWork() throws Exception {
406                                final Transaction txn2 =
407                                        currentTxn.getTransaction();
408                                assertNotNull(txn2);
409                                assertTrue(txn1 != txn2);
410                                assertNull(map.put(TWO, TWO));
411                                assertEquals(TWO, map.get(TWO));
412                                if (explicit) {
413                                    currentTxn.abortTransaction();
414                                } else {
415                                    throw new IllegalArgumentException(
416                                                                "test-abort");
417                                }
418                            }
419                        });
420                        assertTrue(explicit);
421                    } catch (IllegalArgumentException e) {
422                        assertTrue(!explicit);
423                        assertEquals("test-abort", e.getMessage());
424                    }
425                }
426
427                Transaction txn3 = currentTxn.getTransaction();
428                assertSame(txn1, txn3);
429
430                assertEquals(ONE, map.get(ONE));
431                assertNull(map.get(TWO));
432            }
433        });
434
435        assertNull(currentTxn.getTransaction());
436    }
437
438    public void testReadCommittedCollection()
439        throws Exception {
440
441        StoredSortedMap degree2Map = (StoredSortedMap)
442            StoredCollections.configuredSortedMap
443                (map, CursorConfig.READ_COMMITTED);
444
445        // original map is not read-committed
446        assertTrue(!isReadCommitted(map));
447
448        // all read-committed containers are read-uncommitted
449        assertTrue(isReadCommitted(degree2Map));
450        assertTrue(isReadCommitted
451            (StoredCollections.configuredMap
452                (map, CursorConfig.READ_COMMITTED)));
453        assertTrue(isReadCommitted
454            (StoredCollections.configuredCollection
455                (map.values(), CursorConfig.READ_COMMITTED)));
456        assertTrue(isReadCommitted
457            (StoredCollections.configuredSet
458                (map.keySet(), CursorConfig.READ_COMMITTED)));
459        assertTrue(isReadCommitted
460            (StoredCollections.configuredSortedSet
461                ((SortedSet) map.keySet(),
462                 CursorConfig.READ_COMMITTED)));
463
464        if (DbCompat.RECNO_METHOD) {
465            // create a list just so we can call configuredList()
466            Database listStore = TestStore.RECNO_RENUM.open(env, null);
467            List list = new StoredList(listStore, TestStore.VALUE_BINDING,
468                                       true);
469            assertTrue(isReadCommitted
470                (StoredCollections.configuredList
471                    (list, CursorConfig.READ_COMMITTED)));
472            listStore.close();
473        }
474
475        map.put(ONE, ONE);
476        doReadCommitted(degree2Map, null);
477    }
478
479    private static boolean isReadCommitted(Object container) {
480        StoredContainer storedContainer = (StoredContainer) container;
481        /* We can't use getReadCommitted until is is added to DB core. */
482        return storedContainer.getCursorConfig() != null &&
483               storedContainer.getCursorConfig().getReadCommitted();
484    }
485
486    public void testReadCommittedTransaction()
487        throws Exception {
488
489        TransactionConfig config = new TransactionConfig();
490        config.setReadCommitted(true);
491        doReadCommitted(map, config);
492    }
493
494    private void doReadCommitted(final StoredSortedMap degree2Map,
495                                 TransactionConfig txnConfig)
496        throws Exception {
497
498        map.put(ONE, ONE);
499        TransactionRunner runner = new TransactionRunner(env);
500        runner.setTransactionConfig(txnConfig);
501        assertNull(currentTxn.getTransaction());
502        runner.run(new TransactionWorker() {
503            public void doWork() throws Exception {
504                assertNotNull(currentTxn.getTransaction());
505
506                /* Do a read-committed get(), the lock is not retained. */
507                assertEquals(ONE, degree2Map.get(ONE));
508
509                /*
510                 * If we were not using read-committed, the following write of
511                 * key ONE with an auto-commit transaction would self-deadlock
512                 * since two transactions in the same thread would be
513                 * attempting to lock the same key, one for write and one for
514                 * read.  This test passes if we do not deadlock.
515                 */
516                DatabaseEntry key = new DatabaseEntry();
517                DatabaseEntry value = new DatabaseEntry();
518                testStore.getKeyBinding().objectToEntry(ONE, key);
519                testStore.getValueBinding().objectToEntry(TWO, value);
520                store.put(null, key, value);
521            }
522        });
523        assertNull(currentTxn.getTransaction());
524    }
525
526    public void testReadUncommittedCollection()
527        throws Exception {
528
529        StoredSortedMap dirtyMap = (StoredSortedMap)
530            StoredCollections.configuredSortedMap
531                (map, CursorConfig.READ_UNCOMMITTED);
532
533        // original map is not read-uncommitted
534        assertTrue(!isReadUncommitted(map));
535
536        // all read-uncommitted containers are read-uncommitted
537        assertTrue(isReadUncommitted(dirtyMap));
538        assertTrue(isReadUncommitted
539            (StoredCollections.configuredMap
540                (map, CursorConfig.READ_UNCOMMITTED)));
541        assertTrue(isReadUncommitted
542            (StoredCollections.configuredCollection
543                (map.values(), CursorConfig.READ_UNCOMMITTED)));
544        assertTrue(isReadUncommitted
545            (StoredCollections.configuredSet
546                (map.keySet(), CursorConfig.READ_UNCOMMITTED)));
547        assertTrue(isReadUncommitted
548            (StoredCollections.configuredSortedSet
549                ((SortedSet) map.keySet(), CursorConfig.READ_UNCOMMITTED)));
550
551        if (DbCompat.RECNO_METHOD) {
552            // create a list just so we can call configuredList()
553            Database listStore = TestStore.RECNO_RENUM.open(env, null);
554            List list = new StoredList(listStore, TestStore.VALUE_BINDING,
555                                       true);
556            assertTrue(isReadUncommitted
557                (StoredCollections.configuredList
558                    (list, CursorConfig.READ_UNCOMMITTED)));
559            listStore.close();
560        }
561
562        doReadUncommitted(dirtyMap);
563    }
564
565    private static boolean isReadUncommitted(Object container) {
566        StoredContainer storedContainer = (StoredContainer) container;
567        return storedContainer.getCursorConfig() != null &&
568               storedContainer.getCursorConfig().getReadUncommitted();
569    }
570
571    public void testReadUncommittedTransaction()
572        throws Exception {
573
574        TransactionRunner runner = new TransactionRunner(env);
575        TransactionConfig config = new TransactionConfig();
576        config.setReadUncommitted(true);
577        runner.setTransactionConfig(config);
578        assertNull(currentTxn.getTransaction());
579        runner.run(new TransactionWorker() {
580            public void doWork() throws Exception {
581                assertNotNull(currentTxn.getTransaction());
582                doReadUncommitted(map);
583            }
584        });
585        assertNull(currentTxn.getTransaction());
586    }
587
588    /**
589     * Tests that the CurrentTransaction static WeakHashMap does indeed allow
590     * GC to reclaim tine environment when it is closed.  At one point this was
591     * not working because the value object in the map has a reference to the
592     * environment.  This was fixed by wrapping the Environment in a
593     * WeakReference.  [#15444]
594     *
595     * This test only succeeds intermittently, probably due to its reliance
596     * on the GC call.
597     */
598    public void testCurrentTransactionGC()
599        throws Exception {
600
601        /*
602         * This test can have indeterminate results because it depends on
603         * a finalize count, so it's not part of the default run.
604         */
605        if (!SharedTestUtils.runLongTests()) {
606            return;
607        }
608
609        final StringBuffer finalizedFlag = new StringBuffer();
610
611        class MyEnv extends Environment {
612
613            MyEnv(File home, EnvironmentConfig config)
614                throws IOException, DatabaseException {
615
616                super(home, config);
617            }
618
619            protected void finalize() {
620                finalizedFlag.append('.');
621            }
622        }
623
624        MyEnv myEnv = new MyEnv(env.getHome(), env.getConfig());
625        CurrentTransaction myCurrTxn = CurrentTransaction.getInstance(myEnv);
626
627        store.close();
628        store = null;
629        map = null;
630
631        env.close();
632        env = null;
633
634        myEnv.close();
635        myEnv = null;
636
637        myCurrTxn = null;
638        currentTxn = null;
639
640        for (int i = 0; i < 10; i += 1) {
641            byte[] x = null;
642            try {
643                 x = new byte[Integer.MAX_VALUE - 1];
644            } catch (OutOfMemoryError expected) {
645            }
646            assertNull(x);
647            System.gc();
648        }
649
650        for (int i = 0; i < 10; i += 1) {
651            System.gc();
652        }
653
654        assertTrue(finalizedFlag.length() > 0);
655    }
656
657    private synchronized void doReadUncommitted(StoredSortedMap dirtyMap)
658        throws Exception {
659
660        // start thread one
661        ReadUncommittedThreadOne t1 = new ReadUncommittedThreadOne(env, this);
662        t1.start();
663        wait();
664
665        // put ONE
666        synchronized (t1) { t1.notify(); }
667        wait();
668        readCheck(dirtyMap, ONE, ONE);
669        assertTrue(!dirtyMap.isEmpty());
670
671        // abort ONE
672        synchronized (t1) { t1.notify(); }
673        t1.join();
674        readCheck(dirtyMap, ONE, null);
675        assertTrue(dirtyMap.isEmpty());
676
677        // start thread two
678        ReadUncommittedThreadTwo t2 = new ReadUncommittedThreadTwo(env, this);
679        t2.start();
680        wait();
681
682        // put TWO
683        synchronized (t2) { t2.notify(); }
684        wait();
685        readCheck(dirtyMap, TWO, TWO);
686        assertTrue(!dirtyMap.isEmpty());
687
688        // commit TWO
689        synchronized (t2) { t2.notify(); }
690        t2.join();
691        readCheck(dirtyMap, TWO, TWO);
692        assertTrue(!dirtyMap.isEmpty());
693    }
694
695    private static class ReadUncommittedThreadOne extends Thread {
696
697        private CurrentTransaction currentTxn;
698        private TransactionTest parent;
699        private StoredSortedMap map;
700
701        private ReadUncommittedThreadOne(Environment env,
702                                         TransactionTest parent) {
703
704            this.currentTxn = CurrentTransaction.getInstance(env);
705            this.parent = parent;
706            this.map = parent.map;
707        }
708
709        public synchronized void run() {
710
711            try {
712                assertNull(currentTxn.getTransaction());
713                assertNotNull(currentTxn.beginTransaction(null));
714                assertNotNull(currentTxn.getTransaction());
715                readCheck(map, ONE, null);
716                synchronized (parent) { parent.notify(); }
717                wait();
718
719                // put ONE
720                assertNull(map.put(ONE, ONE));
721                readCheck(map, ONE, ONE);
722                synchronized (parent) { parent.notify(); }
723                wait();
724
725                // abort ONE
726                assertNull(currentTxn.abortTransaction());
727                assertNull(currentTxn.getTransaction());
728            } catch (Exception e) {
729                throw new RuntimeExceptionWrapper(e);
730            }
731        }
732    }
733
734    private static class ReadUncommittedThreadTwo extends Thread {
735
736        private Environment env;
737        private CurrentTransaction currentTxn;
738        private TransactionTest parent;
739        private StoredSortedMap map;
740
741        private ReadUncommittedThreadTwo(Environment env,
742                                         TransactionTest parent) {
743
744            this.env = env;
745            this.currentTxn = CurrentTransaction.getInstance(env);
746            this.parent = parent;
747            this.map = parent.map;
748        }
749
750        public synchronized void run() {
751
752            try {
753                final TransactionRunner runner = new TransactionRunner(env);
754                final Object thread = this;
755                assertNull(currentTxn.getTransaction());
756
757                runner.run(new TransactionWorker() {
758                    public void doWork() throws Exception {
759                        assertNotNull(currentTxn.getTransaction());
760                        readCheck(map, TWO, null);
761                        synchronized (parent) { parent.notify(); }
762                        thread.wait();
763
764                        // put TWO
765                        assertNull(map.put(TWO, TWO));
766                        readCheck(map, TWO, TWO);
767                        synchronized (parent) { parent.notify(); }
768                        thread.wait();
769
770                        // commit TWO
771                    }
772                });
773                assertNull(currentTxn.getTransaction());
774            } catch (Exception e) {
775                throw new RuntimeExceptionWrapper(e);
776            }
777        }
778    }
779
780    private static void readCheck(StoredSortedMap checkMap, Object key,
781                                  Object expect) {
782        if (expect == null) {
783            assertNull(checkMap.get(key));
784            assertTrue(checkMap.tailMap(key).isEmpty());
785            assertTrue(!checkMap.tailMap(key).containsKey(key));
786            assertTrue(!checkMap.keySet().contains(key));
787            assertTrue(checkMap.duplicates(key).isEmpty());
788            Iterator i = checkMap.keySet().iterator();
789            try {
790                while (i.hasNext()) {
791                    assertTrue(!key.equals(i.next()));
792                }
793            } finally { StoredIterator.close(i); }
794        } else {
795            assertEquals(expect, checkMap.get(key));
796            assertEquals(expect, checkMap.tailMap(key).get(key));
797            assertTrue(!checkMap.tailMap(key).isEmpty());
798            assertTrue(checkMap.tailMap(key).containsKey(key));
799            assertTrue(checkMap.keySet().contains(key));
800            assertTrue(checkMap.values().contains(expect));
801            assertTrue(!checkMap.duplicates(key).isEmpty());
802            assertTrue(checkMap.duplicates(key).contains(expect));
803            Iterator i = checkMap.keySet().iterator();
804            try {
805                boolean found = false;
806                while (i.hasNext()) {
807                    if (expect.equals(i.next())) {
808                        found = true;
809                    }
810                }
811                assertTrue(found);
812            }
813            finally { StoredIterator.close(i); }
814        }
815    }
816}
817