| /* |
| * Derby - Class org.apache.derbyTesting.functionTests.tests.lang.DeadlockDetectionTest |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, |
| * either express or implied. See the License for the specific |
| * language governing permissions and limitations under the License. |
| */ |
| |
| package org.apache.derbyTesting.functionTests.tests.lang; |
| |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import junit.framework.Test; |
| import org.apache.derbyTesting.functionTests.util.Barrier; |
| import org.apache.derbyTesting.junit.BaseJDBCTestCase; |
| import org.apache.derbyTesting.junit.CleanDatabaseTestSetup; |
| import org.apache.derbyTesting.junit.DatabasePropertyTestSetup; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| |
| /** |
| * This test verifies that the deadlock detection algorithm is able to |
| * recognize certain cycles in the wait graph as deadlocks. |
| */ |
| public class DeadlockDetectionTest extends BaseJDBCTestCase { |
| |
| /** SQLState for deadlock exceptions. */ |
| private final static String DEADLOCK = "40001"; |
| |
| public static Test suite() { |
| // Deadlock detection is engine functionality, so only test embedded. |
| Test test = |
| TestConfiguration.embeddedSuite(DeadlockDetectionTest.class); |
| |
| // Reduce the deadlock timeout since this test expects deadlocks, and |
| // we want to detect them quickly in order to reduce the test time. |
| // We don't expect any wait timeouts, so set the wait timeout |
| // sufficiently high to prevent that queries time out before we have |
| // set up the deadlock on slow machines. |
| test = DatabasePropertyTestSetup.setLockTimeouts(test, 1, 30); |
| |
| return new CleanDatabaseTestSetup(test); |
| } |
| |
| public DeadlockDetectionTest(String name) { |
| super(name); |
| } |
| |
| /** |
| * Test case to verify the fix for DERBY-3980. A simple deadlock was not |
| * detected, and was reported as a lock timeout. |
| */ |
| public void testDerby3980_repeatable_read() throws Exception { |
| Statement s = createStatement(); |
| s.executeUpdate("create table derby3980 (i int)"); |
| s.executeUpdate("insert into derby3980 values 1956, 180, 456, 3"); |
| |
| // Set up two threads. |
| Thread[] threads = new Thread[2]; |
| Connection[] conns = new Connection[threads.length]; |
| |
| // This barrier lets the two threads wait for each other so that both |
| // can obtain a read lock before going on trying to obtain the write |
| // lock. If one thread goes ahead and obtains the write lock before the |
| // other thread has obtained the read lock, we won't see a deadlock. |
| final Barrier readLockBarrier = new Barrier(threads.length); |
| |
| // Exceptions seen by the threads. |
| final List<Exception> exceptions = |
| Collections.synchronizedList(new ArrayList<Exception>()); |
| |
| // Start the two threads. Both should first obtain a read lock, and |
| // when both have the read lock, they should try to lock the same row |
| // exclusively. They'll be blocking each other, and we have a deadlock. |
| for (int i = 0; i < threads.length; i++) { |
| final Connection c = openDefaultConnection(); |
| c.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); |
| c.setAutoCommit(false); |
| |
| final PreparedStatement select = c.prepareStatement( |
| "select * from derby3980 where i = 456"); |
| final PreparedStatement update = c.prepareStatement( |
| "update derby3980 set i = 456 where i = 456"); |
| |
| threads[i] = new Thread() { |
| public void run() { |
| try { |
| JDBC.assertSingleValueResultSet( |
| select.executeQuery(), "456"); |
| |
| // Now we've got the read lock. Wait until all threads |
| // have it before attempting to get the write lock. |
| readLockBarrier.await(); |
| |
| // All threads have the read lock. Now all should try |
| // to update the row and thereby create a deadlock. |
| assertUpdateCount(update, 1); |
| |
| // We got the write lock too. End the transaction. |
| c.rollback(); |
| } catch (Exception e) { |
| exceptions.add(e); |
| } |
| } |
| }; |
| |
| conns[i] = c; |
| threads[i].start(); |
| } |
| |
| // Threads have started, wait for them to complete. |
| for (int i = 0; i < threads.length; i++) { |
| threads[i].join(); |
| conns[i].rollback(); |
| conns[i].close(); |
| } |
| |
| // Verify that we only got deadlock exceptions. |
| for (Exception e : exceptions) { |
| if (e instanceof SQLException) { |
| assertSQLState(DEADLOCK, (SQLException) e); |
| } else { |
| // What's this? Report it. |
| throw e; |
| } |
| } |
| |
| // And we should only get one exception. (One transaction should be |
| // picked as victim, the other one should be able to complete.) |
| assertEquals("Number of victims", 1, exceptions.size()); |
| } |
| |
| /** |
| * Test case for DERBY-5073. A deadlock involving three transactions was |
| * not reported when there were other transactions waiting for the same |
| * locks. The deadlock was detected, and a victim chosen. But the victim |
| * would recheck the deadlock and conclude that it wasn't part of it, and |
| * it would pick a new victim that would also recheck and come to the same |
| * conclusion. This would go on until the wait timeout had expired, and |
| * an exception would be throws, although not a deadlock. |
| */ |
| public void testDerby5073_dodgy_victims() throws Exception { |
| Statement s = createStatement(); |
| s.executeUpdate("create table derby5073(x int primary key, y int)"); |
| s.executeUpdate("insert into derby5073(x) values 0, 1, 2"); |
| |
| // We want six connections. Three that are involved in the deadlock, |
| // and three that try to obtain locks on the same rows without |
| // actually being part of the deadlock. |
| Connection[] conns = new Connection[6]; |
| Thread[] threads = new Thread[conns.length]; |
| for (int i = 0; i < conns.length; i++) { |
| conns[i] = openDefaultConnection(); |
| conns[i].setAutoCommit(false); |
| } |
| |
| // Three transactions take an exclusive lock on one row each. |
| for (int i = 3; i < 6; i++) { |
| PreparedStatement ps = conns[i].prepareStatement( |
| "update derby5073 set y = x where x = ?"); |
| ps.setInt(1, i % 3); |
| assertUpdateCount(ps, 1); |
| } |
| |
| // Then try to lock the rows in three other transactions and in the |
| // three transactions that already have locked the rows exclusively. |
| // The transactions that have exclusive locks should attempt to lock |
| // another row than the one they already have locked, otherwise there |
| // will be no deadlock. |
| final List<Exception> exceptions = |
| Collections.synchronizedList(new ArrayList<Exception>()); |
| for (int i = 0; i < threads.length; i++) { |
| final PreparedStatement ps = conns[i].prepareStatement( |
| "select x from derby5073 where x = ?"); |
| |
| // Which row to lock. Add one to the thread number to make sure |
| // that the threads don't attempt to lock the same row that they |
| // already have locked above. |
| final int row = (i + 1) % 3; |
| ps.setInt(1, row); |
| |
| // The query will have to wait, so execute it in a separate thread. |
| threads[i] = new Thread() { |
| public void run() { |
| try { |
| JDBC.assertSingleValueResultSet( |
| ps.executeQuery(), Integer.toString(row)); |
| ps.getConnection().commit(); |
| } catch (Exception e) { |
| exceptions.add(e); |
| } |
| } |
| }; |
| |
| threads[i].start(); |
| |
| // The bug is only seen if the first three threads are already |
| // waiting for the locks when the last three threads (those |
| // involved in the deadlock) start waiting. So take a little nap |
| // here after we've started the third thread (index 2) to allow |
| // the first three threads to enter the waiting state. |
| if (i == 2) Thread.sleep(100L); |
| } |
| |
| // Wait for all threads to finish. |
| for (int i = 0; i < threads.length; i++) { |
| threads[i].join(); |
| conns[i].rollback(); |
| conns[i].close(); |
| } |
| |
| for (Exception e : exceptions) { |
| if (e instanceof SQLException) { |
| assertSQLState(DEADLOCK, (SQLException) e); |
| } else { |
| // What's this? Report it. |
| throw e; |
| } |
| } |
| |
| // And we should only get one exception. (One transaction should be |
| // picked as victim, the other ones should be able to complete.) |
| assertEquals("Number of victims", 1, exceptions.size()); |
| } |
| } |