/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.test.db; import java.io.Reader; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.h2.constant.ErrorCode; import org.h2.test.TestBase; import org.h2.util.Task; /** * Test for deadlocks in the code, and test the deadlock detection mechanism. */ public class TestDeadlock extends TestBase { /** * The first connection. */ Connection c1; /** * The second connection. */ Connection c2; /** * The third connection. */ Connection c3; private volatile SQLException lastException; /** * Run just this test. * * @param a ignored */ public static void main(String... a) throws Exception { TestBase.createCaller().init().test(); } public void test() throws Exception { deleteDb("deadlock"); testDeadlockInFulltextSearch(); testConcurrentLobReadAndTempResultTableDelete(); testDiningPhilosophers(); testLockUpgrade(); testThreePhilosophers(); testNoDeadlock(); testThreeSome(); deleteDb("deadlock"); } private void testDeadlockInFulltextSearch() throws SQLException { deleteDb("deadlock"); String url = "deadlock"; Connection conn, conn2; conn = getConnection(url); conn2 = getConnection(url); final Statement stat = conn.createStatement(); Statement stat2 = conn2.createStatement(); stat.execute("create alias if not exists ft_init for \"org.h2.fulltext.FullText.init\""); stat.execute("call ft_init()"); stat.execute("create table test(id int primary key, name varchar)"); stat.execute("call ft_create_index('PUBLIC', 'TEST', null)"); Task t = new Task() { public void call() throws Exception { while (!stop) { stat.executeQuery("select * from test"); } } }; t.execute(); long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < 1000) { stat2.execute("insert into test values(1, 'Hello')"); stat2.execute("delete from test"); } t.get(); conn2.close(); conn.close(); conn = getConnection(url); conn.createStatement().execute("drop all objects"); conn.close(); } private void testConcurrentLobReadAndTempResultTableDelete() throws Exception { deleteDb("deadlock"); String url = "deadlock;MAX_MEMORY_ROWS_DISTINCT=10"; Connection conn, conn2; Statement stat2; conn = getConnection(url); conn2 = getConnection(url); final Statement stat = conn.createStatement(); stat2 = conn2.createStatement(); stat.execute("create table test(id int primary key, name varchar) as " + "select x, 'Hello' from system_range(1,20)"); stat2.execute("create table test_clob(id int primary key, data clob) as " + "select 1, space(10000)"); ResultSet rs2 = stat2.executeQuery("select * from test_clob"); rs2.next(); Task t = new Task() { public void call() throws Exception { while (!stop) { stat.execute("select * from (select distinct id from test)"); } } }; t.execute(); long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < 1000) { Reader r = rs2.getCharacterStream(2); char[] buff = new char[1024]; while (true) { int x = r.read(buff); if (x < 0) { break; } } } t.get(); stat.execute("drop all objects"); conn.close(); conn2.close(); } private void initTest() throws SQLException { c1 = getConnection("deadlock"); c2 = getConnection("deadlock"); c3 = getConnection("deadlock"); c1.createStatement().execute("SET LOCK_TIMEOUT 1000"); c2.createStatement().execute("SET LOCK_TIMEOUT 1000"); c3.createStatement().execute("SET LOCK_TIMEOUT 1000"); c1.setAutoCommit(false); c2.setAutoCommit(false); c3.setAutoCommit(false); lastException = null; } private void end() throws SQLException { c1.close(); c2.close(); c3.close(); } /** * This class wraps exception handling to simplify creating small threads * that execute a statement. */ abstract class DoIt extends Thread { /** * The operation to execute. */ abstract void execute() throws SQLException; public void run() { try { execute(); } catch (SQLException e) { catchDeadlock(e); } } } /** * Add the exception to the list of exceptions. * * @param e the exception */ void catchDeadlock(SQLException e) { if (lastException != null) { lastException.setNextException(e); } else { lastException = e; } } private void testNoDeadlock() throws Exception { initTest(); c1.createStatement().execute("CREATE TABLE TEST_A(ID INT PRIMARY KEY)"); c1.createStatement().execute("CREATE TABLE TEST_B(ID INT PRIMARY KEY)"); c1.createStatement().execute("CREATE TABLE TEST_C(ID INT PRIMARY KEY)"); c1.commit(); c1.createStatement().execute("INSERT INTO TEST_A VALUES(1)"); c2.createStatement().execute("INSERT INTO TEST_B VALUES(1)"); c3.createStatement().execute("INSERT INTO TEST_C VALUES(1)"); DoIt t2 = new DoIt() { public void execute() throws SQLException { c1.createStatement().execute("DELETE FROM TEST_B"); c1.commit(); } }; t2.start(); DoIt t3 = new DoIt() { public void execute() throws SQLException { c2.createStatement().execute("DELETE FROM TEST_C"); c2.commit(); } }; t3.start(); Thread.sleep(500); try { c3.createStatement().execute("DELETE FROM TEST_C"); c3.commit(); } catch (SQLException e) { catchDeadlock(e); } t2.join(); t3.join(); if (lastException != null) { throw lastException; } c1.commit(); c2.commit(); c3.commit(); c1.createStatement().execute("DROP TABLE TEST_A, TEST_B, TEST_C"); end(); } private void testThreePhilosophers() throws Exception { if (config.mvcc) { return; } initTest(); c1.createStatement().execute("CREATE TABLE TEST_A(ID INT PRIMARY KEY)"); c1.createStatement().execute("CREATE TABLE TEST_B(ID INT PRIMARY KEY)"); c1.createStatement().execute("CREATE TABLE TEST_C(ID INT PRIMARY KEY)"); c1.commit(); c1.createStatement().execute("INSERT INTO TEST_A VALUES(1)"); c2.createStatement().execute("INSERT INTO TEST_B VALUES(1)"); c3.createStatement().execute("INSERT INTO TEST_C VALUES(1)"); DoIt t2 = new DoIt() { public void execute() throws SQLException { c1.createStatement().execute("DELETE FROM TEST_B"); c1.commit(); } }; t2.start(); DoIt t3 = new DoIt() { public void execute() throws SQLException { c2.createStatement().execute("DELETE FROM TEST_C"); c2.commit(); } }; t3.start(); try { c3.createStatement().execute("DELETE FROM TEST_A"); c3.commit(); } catch (SQLException e) { catchDeadlock(e); } t2.join(); t3.join(); checkDeadlock(); c1.commit(); c2.commit(); c3.commit(); c1.createStatement().execute("DROP TABLE TEST_A, TEST_B, TEST_C"); end(); } // test case for issue # 61 // http://code.google.com/p/h2database/issues/detail?id=61) private void testThreeSome() throws Exception { if (config.mvcc) { return; } initTest(); c1.createStatement().execute("CREATE TABLE TEST_A(ID INT PRIMARY KEY)"); c1.createStatement().execute("CREATE TABLE TEST_B(ID INT PRIMARY KEY)"); c1.createStatement().execute("CREATE TABLE TEST_C(ID INT PRIMARY KEY)"); c1.commit(); c1.createStatement().execute("INSERT INTO TEST_A VALUES(1)"); c1.createStatement().execute("INSERT INTO TEST_B VALUES(1)"); c2.createStatement().execute("INSERT INTO TEST_C VALUES(1)"); DoIt t2 = new DoIt() { public void execute() throws SQLException { c3.createStatement().execute("INSERT INTO TEST_B VALUES(2)"); c3.commit(); } }; t2.start(); DoIt t3 = new DoIt() { public void execute() throws SQLException { c2.createStatement().execute("INSERT INTO TEST_A VALUES(2)"); c2.commit(); } }; t3.start(); try { c1.createStatement().execute("INSERT INTO TEST_C VALUES(2)"); c1.commit(); } catch (SQLException e) { catchDeadlock(e); c1.rollback(); } t2.join(); t3.join(); checkDeadlock(); c1.commit(); c2.commit(); c3.commit(); c1.createStatement().execute("DROP TABLE TEST_A, TEST_B, TEST_C"); end(); } private void testLockUpgrade() throws Exception { if (config.mvcc) { return; } initTest(); c1.createStatement().execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); c1.createStatement().execute("INSERT INTO TEST VALUES(1)"); c1.commit(); c1.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); c2.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); c1.createStatement().executeQuery("SELECT * FROM TEST"); c2.createStatement().executeQuery("SELECT * FROM TEST"); Thread t1 = new DoIt() { public void execute() throws SQLException { c1.createStatement().execute("DELETE FROM TEST"); c1.commit(); } }; t1.start(); try { c2.createStatement().execute("DELETE FROM TEST"); c2.commit(); } catch (SQLException e) { catchDeadlock(e); } t1.join(); checkDeadlock(); c1.commit(); c2.commit(); c1.createStatement().execute("DROP TABLE TEST"); end(); } private void testDiningPhilosophers() throws Exception { if (config.mvcc) { return; } initTest(); c1.createStatement().execute("CREATE TABLE T1(ID INT)"); c1.createStatement().execute("CREATE TABLE T2(ID INT)"); c1.createStatement().execute("INSERT INTO T1 VALUES(1)"); c2.createStatement().execute("INSERT INTO T2 VALUES(1)"); DoIt t1 = new DoIt() { public void execute() throws SQLException { c1.createStatement().execute("INSERT INTO T2 VALUES(2)"); c1.commit(); } }; t1.start(); try { c2.createStatement().execute("INSERT INTO T1 VALUES(2)"); } catch (SQLException e) { catchDeadlock(e); } t1.join(); checkDeadlock(); c1.commit(); c2.commit(); c1.createStatement().execute("DROP TABLE T1, T2"); end(); } private void checkDeadlock() throws SQLException { assertTrue(lastException != null); assertKnownException(lastException); assertEquals(ErrorCode.DEADLOCK_1, lastException.getErrorCode()); SQLException e2 = lastException.getNextException(); if (e2 != null) { // we have two exception, but there should only be one SQLException e3 = new SQLException("Expected one exception, got multiple"); e3.initCause(e2); throw e3; } } }