You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ka...@apache.org on 2009/02/23 11:48:25 UTC

svn commit: r746954 - /db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/IndexSplitDeadlockTest.java

Author: kahatlen
Date: Mon Feb 23 10:48:24 2009
New Revision: 746954

URL: http://svn.apache.org/viewvc?rev=746954&view=rev
Log:
DERBY-2991: Index split deadlock

Added more test cases for BTreeForwardScan.fetchRows() where the
current leaf page was split while the index scan was waiting for a row
lock.

Modified:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/IndexSplitDeadlockTest.java

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/IndexSplitDeadlockTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/IndexSplitDeadlockTest.java?rev=746954&r1=746953&r2=746954&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/IndexSplitDeadlockTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/IndexSplitDeadlockTest.java Mon Feb 23 10:48:24 2009
@@ -44,8 +44,10 @@
  */
 public class IndexSplitDeadlockTest extends BaseJDBCTestCase {
 
-    /** List of obstructor threads to wait for after running the test. */
-    private List obstructors;
+    /**
+     * List of threads (AsyncThread objects) to wait for after running the test.
+     */
+    private List threads = new ArrayList();
 
     public IndexSplitDeadlockTest(String name) {
         super(name);
@@ -64,15 +66,13 @@
         getConnection().setAutoCommit(false); // required by JDBC.dropSchema()
         JDBC.dropSchema(getConnection().getMetaData(), "APP");
 
-        // Go through all the obstructors and call waitFor() so that we
+        // Go through all the threads and call waitFor() so that we
         // detect errors that happened in another thread.
-        if (obstructors != null) {
-            for (Iterator it = obstructors.iterator(); it.hasNext(); ) {
-                Obstructor o = (Obstructor) it.next();
-                o.waitFor();
-            }
+        for (Iterator it = threads.iterator(); it.hasNext();) {
+            AsyncThread thread = (AsyncThread) it.next();
+            thread.waitFor();
         }
-        obstructors = null;
+        threads = null;
 
         super.tearDown();
     }
@@ -327,8 +327,67 @@
         rs.close();
     }
 
-    // TODO: add a similar case as the one above, only that it should
-    // cause a split before the index scan wakes up
+    /**
+     * Test that BTreeForwardScan.fetchRows() can reposition after releasing
+     * latches because it had to wait for a lock, and the leaf page on which
+     * the scan is positioned has been split. This tests the third call
+     * to reposition() in fetchRows(), which is only called if the index is
+     * unique.
+     */
+    public void testBTreeForwardScan_fetchRows_resumeAfterWait_unique_split()
+            throws Exception {
+        getConnection().setAutoCommit(false);
+
+        // Populate a table with a unique index
+        Statement s = createStatement();
+        s.executeUpdate("create table t (x int, constraint c primary key(x))");
+        PreparedStatement ins = prepareStatement("insert into t values ?");
+        for (int i = 0; i < 300; i++) {
+            ins.setInt(1, i);
+            ins.executeUpdate();
+        }
+        commit();
+
+        // Lock a row on the first page in a different thread to stop the
+        // index scan. Then split the first leaf by inserting many values
+        // less than zero.
+        new AsyncThread(new AsyncTask() {
+            public void doWork(Connection conn) throws Exception {
+                conn.setAutoCommit(false);
+                Statement s = conn.createStatement();
+                s.executeUpdate("update t set x = x where x = 40");
+                s.close();
+                // Give the index scan time to start and position on
+                // the row we have locked. (Give it two seconds, since the
+                // main thread sleeps for one second first before it starts
+                // the index scan.)
+                Thread.sleep(2000);
+                // Split the first leaf
+                PreparedStatement ps = conn.prepareStatement(
+                        "insert into t values ?");
+                for (int i = -1; i > -300; i--) {
+                    ps.setInt(1, i);
+                    ps.executeUpdate();
+                }
+                ps.close();
+                conn.commit();
+            }
+        });
+
+        // Give the other thread time to obtain the lock
+        Thread.sleep(1000);
+
+        // Perform an index scan. Will be blocked for a while when fetching
+        // the row where x=100, but should be able to resume the scan.
+        ResultSet rs = s.executeQuery(
+                "select * from t --DERBY-PROPERTIES constraint=C");
+        for (int i = 0; i < 300; i++) {
+            assertTrue(rs.next());
+            assertEquals(i, rs.getInt(1));
+        }
+        assertFalse(rs.next());
+        rs.close();
+    }
 
     /**
      * Test that BTreeForwardScan.fetchRows() can reposition after releasing
@@ -369,8 +428,64 @@
         rs.close();
     }
 
-    // TODO: add a similar case as the one above, only that it should
-    // cause a split before the index scan wakes up
+    /**
+     * Test that BTreeForwardScan.fetchRows() can reposition after releasing
+     * latches because it had to wait for a lock, and the leaf page on which
+     * the scan is positioned has been split. This tests the fourth call
+     * to reposition() in fetchRows(), which is only called if the index is
+     * non-unique.
+     */
+    public void testBTreeForwardScan_fetchRows_resumeAfterWait_nonUnique_split()
+            throws Exception {
+        getConnection().setAutoCommit(false);
+
+        // Populate a table with a non-unique index
+        Statement s = createStatement();
+        s.executeUpdate("create table t (x int)");
+        s.executeUpdate("create index idx on t(x)");
+        PreparedStatement ins = prepareStatement("insert into t values ?");
+        for (int i = 0; i < 300; i++) {
+            ins.setInt(1, i);
+            ins.executeUpdate();
+        }
+        commit();
+
+        // Hold a lock in a different thread to stop the index scan, then
+        // split the first leaf (on which the scan is positioned) before the
+        // lock is released.
+        new AsyncThread(new AsyncTask() {
+            public void doWork(Connection conn) throws Exception {
+                conn.setAutoCommit(false);
+                Statement s = conn.createStatement();
+                s.executeUpdate("update t set x = x where x = 40");
+                // Give the index scan time to start and position on
+                // the row we have locked. (Give it two seconds, since the
+                // main thread sleeps for one second first before it starts
+                // the index scan.)
+                Thread.sleep(2000);
+                // Split the first leaf by inserting more zeros
+                for (int i = -1; i > -300; i--) {
+                    s.executeUpdate("insert into t values 0");
+                }
+                s.close();
+                conn.commit();
+            }
+        });
+
+        // Give the other thread time to obtain the lock
+        Thread.sleep(1000);
+
+        // Perform an index scan. Will be blocked for a while when fetching
+        // the row where x=100, but should be able to resume the scan.
+        ResultSet rs = s.executeQuery(
+                "select * from t --DERBY-PROPERTIES index=IDX");
+        for (int i = 0; i < 300; i++) {
+            assertTrue(rs.next());
+            assertEquals(i, rs.getInt(1));
+        }
+        assertFalse(rs.next());
+        rs.close();
+    }
 
     // --------------------------------------------------------------------
     // Helpers
@@ -394,52 +509,63 @@
      * @param blockMillis how many milliseconds to wait until the transaction
      * is rolled back
      */
-    private void obstruct(String sql, long blockMillis) {
-        if (obstructors == null) {
-            obstructors = new ArrayList();
-        }
-        obstructors.add(new Obstructor(sql, blockMillis));
+    private void obstruct(final String sql, final long blockMillis) {
+        AsyncTask task = new AsyncTask() {
+            public void doWork(Connection conn) throws Exception {
+                conn.setAutoCommit(false);
+                Statement s = conn.createStatement();
+                s.execute(sql);
+                s.close();
+                Thread.sleep(blockMillis);
+            }
+        };
+        new AsyncThread(task);
+    }
+
+    /**
+     * Interface that should be implemented by classes that define a
+     * database task that is to be executed asynchronously in a separate
+     * transaction.
+     */
+    private static interface AsyncTask {
+        void doWork(Connection conn) throws Exception;
     }
 
     /**
-     * Helper class for the obstruct() method. Executes SQL in a separate
-     * thread and stores any exceptions thrown.
+     * Class that executes an {@code AsyncTask} object.
      */
-    private class Obstructor implements Runnable {
-        private final String sql;
-        private final long blockMillis;
-        private final Thread thread;
+    private class AsyncThread implements Runnable {
+
+        private final Thread thread = new Thread(this);
+        private final AsyncTask task;
         private Exception error;
 
         /**
-         * Create and start an obstructor thread.
-         * @param sql the SQL text to execute
-         * @param blockMillis the time in milliseconds to keep the
-         * transaction active
+         * Create an {@code AsyncThread} object and starts a thread executing
+         * the task. Also put the {@code AsyncThread} object in the list of
+         * threads in the parent object to make sure the thread is waited for
+         * and its errors detected in the {@code tearDown()} method.
+         *
+         * @param task the task to perform
          */
-        Obstructor(String sql, long blockMillis) {
-            this.sql = sql;
-            this.blockMillis = blockMillis;
-            thread = new Thread(this);
+        public AsyncThread(AsyncTask task) {
+            this.task = task;
             thread.start();
+            threads.add(this);
         }
 
         /**
-         * Run the SQL in a separate transaction and block for the specified
-         * amount of time.
+         * Open a database connection and perform the task. Roll back the
+         * transaction when finished. Any exception thrown will be caught and
+         * rethrown when the {@code waitFor()} method is called.
          */
         public void run() {
             try {
-                Connection c = openDefaultConnection();
+                Connection conn = openDefaultConnection();
                 try {
-                    c.setAutoCommit(false);
-                    Statement s = c.createStatement();
-                    s.execute(sql);
-                    s.close();
-                    Thread.sleep(blockMillis);
+                    task.doWork(conn);
                 } finally {
-                    c.rollback();
-                    c.close();
+                    JDBC.cleanup(conn);
                 }
             } catch (Exception e) {
                 error = e;
@@ -447,21 +573,15 @@
         }
 
         /**
-         * Wait for the obstructor thread to complete. If an error occurred
-         * while the thread was running, the exception will be rethrown by
-         * this method.
-         *
-         * @throws Exception if an error occurred while the thread was running
+         * Wait for the thread to complete. If an error was thrown during
+         * execution, rethrow the execption here.
+         * @throws Exception if an error happened while performing the task
          */
         void waitFor() throws Exception {
             thread.join();
-            Exception e = error;
-            error = null;
-            if (e != null) {
-                throw e;
+            if (error != null) {
+                throw error;
             }
         }
-
     }
-
 }