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