/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002, 2015 Oracle and/or its affiliates. All rights reserved. * */ package com.sleepycat.collections.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.sleepycat.collections.StoredSortedMap; import com.sleepycat.collections.TransactionRunner; import com.sleepycat.collections.TransactionWorker; import com.sleepycat.db.Database; import com.sleepycat.db.Environment; import com.sleepycat.db.DeadlockException; import com.sleepycat.db.TransactionConfig; import com.sleepycat.util.ExceptionUnwrapper; import com.sleepycat.util.test.TestBase; import com.sleepycat.util.test.TestEnv; /** * Tests whether secondary access can cause a self-deadlock when reading via a * secondary because the collections API secondary implementation in DB 4.2 * opens two cursors. Part of the problem in [#10516] was because the * secondary get() was not done in a txn. This problem should not occur in DB * 4.3 and JE -- an ordinary deadlock occurs instead and is detected. * * @author Mark Hayes */ public class SecondaryDeadlockTest extends TestBase { private static final Long N_ONE = new Long(1); private static final Long N_101 = new Long(101); private static final int N_ITERS = 20; private static final int MAX_RETRIES = 1000; private Environment env; private Database store; private Database index; private StoredSortedMap storeMap; private StoredSortedMap indexMap; private Exception exception; public SecondaryDeadlockTest() { customName = "SecondaryDeadlockTest"; } @Before public void setUp() throws Exception { super.setUp(); env = TestEnv.TXN.open("SecondaryDeadlockTest"); store = TestStore.BTREE_UNIQ.open(env, "store.db"); index = TestStore.BTREE_UNIQ.openIndex(store, "index.db"); storeMap = new StoredSortedMap(store, TestStore.BTREE_UNIQ.getKeyBinding(), TestStore.BTREE_UNIQ.getValueBinding(), true); indexMap = new StoredSortedMap(index, TestStore.BTREE_UNIQ.getKeyBinding(), TestStore.BTREE_UNIQ.getValueBinding(), true); } @After public void tearDown() { if (index != null) { try { index.close(); } catch (Exception e) { System.out.println("Ignored exception during tearDown: " + e); } } if (store != null) { try { store.close(); } catch (Exception e) { System.out.println("Ignored exception during tearDown: " + e); } } if (env != null) { try { env.close(); } catch (Exception e) { System.out.println("Ignored exception during tearDown: " + e); } } /* Allow GC of DB objects in the test case. */ env = null; store = null; index = null; storeMap = null; indexMap = null; } @Test public void testSecondaryDeadlock() throws Exception { final TransactionRunner runner = new TransactionRunner(env); runner.setMaxRetries(MAX_RETRIES); /* * This test deadlocks a lot at degree 3 serialization. In debugging * this I discovered it was not due to phantom prevention per se but * just to a change in timing. */ TransactionConfig txnConfig = new TransactionConfig(); runner.setTransactionConfig(txnConfig); /* * A thread to do put() and delete() via the primary, which will lock * the primary first then the secondary. Uses transactions. */ final Thread thread1 = new Thread(new Runnable() { public void run() { try { /* The TransactionRunner performs retries. */ for (int i = 0; i < N_ITERS; i +=1 ) { runner.run(new TransactionWorker() { public void doWork() { assertEquals(null, storeMap.put(N_ONE, N_101)); } }); runner.run(new TransactionWorker() { public void doWork() { assertEquals(N_101, storeMap.remove(N_ONE)); } }); } } catch (Exception e) { e.printStackTrace(); exception = e; } } }, "ThreadOne"); /* * A thread to get() via the secondary, which will lock the secondary * first then the primary. Does not use a transaction. */ final Thread thread2 = new Thread(new Runnable() { public void run() { try { for (int i = 0; i < N_ITERS; i +=1 ) { for (int j = 0; j < MAX_RETRIES; j += 1) { try { Object value = indexMap.get(N_ONE); assertTrue(value == null || N_101.equals(value)); break; } catch (Exception e) { e = ExceptionUnwrapper.unwrap(e); if (e instanceof DeadlockException) { continue; /* Retry on deadlock. */ } else { throw e; } } } } } catch (Exception e) { e.printStackTrace(); exception = e; } } }, "ThreadTwo"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); index.close(); index = null; store.close(); store = null; env.close(); env = null; if (exception != null) { fail(exception.toString()); } } }