1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: SecondaryDeadlockTest.java,v 12.9 2008/02/07 17:12:31 mark Exp $
7 */
8
9package com.sleepycat.collections.test;
10
11import junit.framework.Test;
12import junit.framework.TestCase;
13import junit.framework.TestSuite;
14
15import com.sleepycat.db.Database;
16import com.sleepycat.db.DeadlockException;
17import com.sleepycat.db.Environment;
18import com.sleepycat.db.TransactionConfig;
19import com.sleepycat.collections.StoredSortedMap;
20import com.sleepycat.collections.TransactionRunner;
21import com.sleepycat.collections.TransactionWorker;
22import com.sleepycat.util.ExceptionUnwrapper;
23import com.sleepycat.util.test.TestEnv;
24
25/**
26 * Tests whether secondary access can cause a self-deadlock when reading via a
27 * secondary because the collections API secondary implementation in DB 4.2
28 * opens two cursors.   Part of the problem in [#10516] was because the
29 * secondary get() was not done in a txn.  This problem should not occur in DB
30 * 4.3 and JE -- an ordinary deadlock occurs instead and is detected.
31 *
32 * @author Mark Hayes
33 */
34public class SecondaryDeadlockTest extends TestCase {
35
36    private static final Long N_ONE = new Long(1);
37    private static final Long N_101 = new Long(101);
38    private static final int N_ITERS = 20;
39    private static final int MAX_RETRIES = 1000;
40
41    public static void main(String[] args)
42        throws Exception {
43
44        junit.framework.TestResult tr =
45            junit.textui.TestRunner.run(suite());
46        if (tr.errorCount() > 0 ||
47            tr.failureCount() > 0) {
48            System.exit(1);
49        } else {
50            System.exit(0);
51        }
52    }
53
54    public static Test suite()
55        throws Exception {
56
57        TestSuite suite = new TestSuite(SecondaryDeadlockTest.class);
58        return suite;
59    }
60
61    private Environment env;
62    private Database store;
63    private Database index;
64    private StoredSortedMap storeMap;
65    private StoredSortedMap indexMap;
66    private Exception exception;
67
68    public SecondaryDeadlockTest(String name) {
69
70        super(name);
71    }
72
73    public void setUp()
74        throws Exception {
75
76        env = TestEnv.TXN.open("SecondaryDeadlockTest");
77        store = TestStore.BTREE_UNIQ.open(env, "store.db");
78        index = TestStore.BTREE_UNIQ.openIndex(store, "index.db");
79        storeMap = new StoredSortedMap(store,
80                                       TestStore.BTREE_UNIQ.getKeyBinding(),
81                                       TestStore.BTREE_UNIQ.getValueBinding(),
82                                       true);
83        indexMap = new StoredSortedMap(index,
84                                       TestStore.BTREE_UNIQ.getKeyBinding(),
85                                       TestStore.BTREE_UNIQ.getValueBinding(),
86                                       true);
87    }
88
89    public void tearDown() {
90
91        if (index != null) {
92            try {
93                index.close();
94            } catch (Exception e) {
95                System.out.println("Ignored exception during tearDown: " + e);
96            }
97        }
98        if (store != null) {
99            try {
100                store.close();
101            } catch (Exception e) {
102                System.out.println("Ignored exception during tearDown: " + e);
103            }
104        }
105        if (env != null) {
106            try {
107                env.close();
108            } catch (Exception e) {
109                System.out.println("Ignored exception during tearDown: " + e);
110            }
111        }
112        /* Allow GC of DB objects in the test case. */
113        env = null;
114        store = null;
115        index = null;
116        storeMap = null;
117        indexMap = null;
118    }
119
120    public void testSecondaryDeadlock()
121        throws Exception {
122
123        final TransactionRunner runner = new TransactionRunner(env);
124        runner.setMaxRetries(MAX_RETRIES);
125
126        /*
127         * This test deadlocks a lot at degree 3 serialization.  In debugging
128         * this I discovered it was not due to phantom prevention per se but
129         * just to a change in timing.
130         */
131        TransactionConfig txnConfig = new TransactionConfig();
132        runner.setTransactionConfig(txnConfig);
133
134        /*
135         * A thread to do put() and delete() via the primary, which will lock
136         * the primary first then the secondary.  Uses transactions.
137         */
138        final Thread thread1 = new Thread(new Runnable() {
139            public void run() {
140                try {
141                    /* The TransactionRunner performs retries. */
142                    for (int i = 0; i < N_ITERS; i +=1 ) {
143                        runner.run(new TransactionWorker() {
144                            public void doWork() throws Exception {
145                                assertEquals(null, storeMap.put(N_ONE, N_101));
146                            }
147                        });
148                        runner.run(new TransactionWorker() {
149                            public void doWork() throws Exception {
150                                assertEquals(N_101, storeMap.remove(N_ONE));
151                            }
152                        });
153                    }
154                } catch (Exception e) {
155                    e.printStackTrace();
156                    exception = e;
157                }
158            }
159        }, "ThreadOne");
160
161        /*
162         * A thread to get() via the secondary, which will lock the secondary
163         * first then the primary.  Does not use a transaction.
164         */
165        final Thread thread2 = new Thread(new Runnable() {
166            public void run() {
167                try {
168                    for (int i = 0; i < N_ITERS; i +=1 ) {
169                        for (int j = 0; j < MAX_RETRIES; j += 1) {
170                            try {
171                                Object value = indexMap.get(N_ONE);
172                                assertTrue(value == null ||
173                                           N_101.equals(value));
174                                break;
175                            } catch (Exception e) {
176                                e = ExceptionUnwrapper.unwrap(e);
177                                if (e instanceof DeadlockException) {
178                                    continue; /* Retry on deadlock. */
179                                } else {
180                                    throw e;
181                                }
182                            }
183                        }
184                    }
185                } catch (Exception e) {
186                    e.printStackTrace();
187                    exception = e;
188                }
189            }
190        }, "ThreadTwo");
191
192        thread1.start();
193        thread2.start();
194        thread1.join();
195        thread2.join();
196
197        index.close();
198        index = null;
199        store.close();
200        store = null;
201        env.close();
202        env = null;
203
204        if (exception != null) {
205            fail(exception.toString());
206        }
207    }
208}
209