blob: 80a1136ba36b2d3c6b699927eb95e25eb8d9ba3d [file]
/*
* 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());
}
}