/*- * 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.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Iterator; import java.util.ListIterator; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.sleepycat.bind.ByteArrayBinding; import com.sleepycat.collections.StoredIterator; import com.sleepycat.collections.StoredSortedMap; import com.sleepycat.collections.TransactionRunner; import com.sleepycat.collections.TransactionWorker; import com.sleepycat.compat.DbCompat; import com.sleepycat.db.Database; import com.sleepycat.db.DatabaseConfig; import com.sleepycat.db.Environment; import com.sleepycat.db.DeadlockException; import com.sleepycat.util.test.TestBase; import com.sleepycat.util.test.TestEnv; /** * Tests the fix for [#10516], where the StoredIterator constructor was not * closing the cursor when an exception occurred. For example, a deadlock * exception might occur if the constructor was unable to move the cursor to * the first element. * @author Mark Hayes */ public class IterDeadlockTest extends TestBase { private static final byte[] ONE = { 1 }; private Environment env; private Database store1; private Database store2; private StoredSortedMap map1; private StoredSortedMap map2; private final ByteArrayBinding binding = new ByteArrayBinding(); @Before public void setUp() throws Exception { env = TestEnv.TXN.open("IterDeadlockTest"); store1 = openDb("store1.db"); store2 = openDb("store2.db"); map1 = new StoredSortedMap(store1, binding, binding, true); map2 = new StoredSortedMap(store2, binding, binding, true); } @After public void tearDown() { if (store1 != null) { try { store1.close(); } catch (Exception e) { System.out.println("Ignored exception during tearDown: " + e); } } if (store2 != null) { try { store2.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; store1 = null; store2 = null; map1 = null; map2 = null; } private Database openDb(String file) throws Exception { DatabaseConfig config = new DatabaseConfig(); DbCompat.setTypeBtree(config); config.setTransactional(true); config.setAllowCreate(true); return DbCompat.testOpenDatabase(env, null, file, null, config); } @Test public void testIterDeadlock() throws Exception { final Object parent = new Object(); final Object child1 = new Object(); final Object child2 = new Object(); final TransactionRunner runner = new TransactionRunner(env); runner.setMaxRetries(0); /* Write a record in each db. */ runner.run(new TransactionWorker() { public void doWork() { assertNull(map1.put(ONE, ONE)); assertNull(map2.put(ONE, ONE)); } }); /* * A thread to open iterator 1, then wait to be notified, then open * iterator 2. */ final Thread thread1 = new Thread(new Runnable() { public void run() { try { runner.run(new TransactionWorker() { public void doWork() throws Exception { synchronized (child1) { ListIterator i1 = (ListIterator) map1.values().iterator(); i1.next(); i1.set(ONE); /* Write lock. */ StoredIterator.close(i1); synchronized (parent) { parent.notify(); } child1.wait(); Iterator i2 = map2.values().iterator(); assertTrue(i2.hasNext()); StoredIterator.close(i2); } } }); } catch (DeadlockException expected) { } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } } }); /* * A thread to open iterator 2, then wait to be notified, then open * iterator 1. */ final Thread thread2 = new Thread(new Runnable() { public void run() { try { runner.run(new TransactionWorker() { public void doWork() throws Exception { synchronized (child2) { ListIterator i2 = (ListIterator) map2.values().iterator(); i2.next(); i2.set(ONE); /* Write lock. */ StoredIterator.close(i2); synchronized (parent) { parent.notify(); } child2.wait(); Iterator i1 = map1.values().iterator(); assertTrue(i1.hasNext()); StoredIterator.close(i1); } } }); } catch (DeadlockException expected) { } catch (Exception e) { e.printStackTrace(); fail(e.toString()); } } }); /* * Open iterator 1 in thread 1, then iterator 2 in thread 2, then let * the threads run to open the other iterators and cause a deadlock. */ synchronized (parent) { thread1.start(); parent.wait(); thread2.start(); parent.wait(); synchronized (child1) { child1.notify(); } synchronized (child2) { child2.notify(); } thread1.join(); thread2.join(); } /* * Before the fix for [#10516] we would get an exception indicating * that cursors were not closed, when closing the stores below. */ store1.close(); store1 = null; store2.close(); store2 = null; env.close(); env = null; } }