提交 b3b45da6 authored 作者: Thomas Mueller's avatar Thomas Mueller

Sometimes a StackOverflow occured when checking for deadlock.

上级 8cff8c47
...@@ -18,7 +18,9 @@ Change Log ...@@ -18,7 +18,9 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>The Shell tool does no longer truncate results with only one column, and displays <ul><li>Sometimes a StackOverflow occured when checking for deadlock. See also
http://code.google.com/p/h2database/issues/detail?id=61
</li><li>The Shell tool does no longer truncate results with only one column, and displays
a message if data was truncated. a message if data was truncated.
</li></ul> </li></ul>
......
...@@ -9,6 +9,7 @@ package org.h2.table; ...@@ -9,6 +9,7 @@ package org.h2.table;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set;
import org.h2.command.Prepared; import org.h2.command.Prepared;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
...@@ -884,16 +885,16 @@ public abstract class Table extends SchemaObjectBase { ...@@ -884,16 +885,16 @@ public abstract class Table extends SchemaObjectBase {
/** /**
* Check if a deadlock occurred. This method is called recursively. There is * Check if a deadlock occurred. This method is called recursively. There is
* a circle if the session to be tested for is the same as the originating * a circle if the session to be tested has already being visited (i.e., it
* session (the 'clash session'). In this case the method must return an * is one of the 'clash sessions'). In this case the method must return an
* empty object array. Once a deadlock has been detected, the methods must * empty object array. Once a deadlock has been detected, the methods must
* add the session to the list. * add the session to the list.
* *
* @param session the session to be tested for * @param session the session to be tested for
* @param clash the originating session, and null when starting verification * @param clash set with sessions already visited, and null when starting verification
* @return an object array with the sessions involved in the deadlock * @return an object array with the sessions involved in the deadlock
*/ */
public ObjectArray checkDeadlock(Session session, Session clash) { public ObjectArray checkDeadlock(Session session, Set clash) {
return null; return null;
} }
......
...@@ -10,6 +10,7 @@ import java.sql.SQLException; ...@@ -10,6 +10,7 @@ import java.sql.SQLException;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set;
import org.h2.api.DatabaseEventListener; import org.h2.api.DatabaseEventListener;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
...@@ -379,6 +380,7 @@ public class TableData extends Table implements RecordReader { ...@@ -379,6 +380,7 @@ public class TableData extends Table implements RecordReader {
} }
private void doLock(Session session, int lockMode, boolean exclusive) throws SQLException { private void doLock(Session session, int lockMode, boolean exclusive) throws SQLException {
traceLock(session, exclusive, "requesting for");
long max = System.currentTimeMillis() + session.getLockTimeout(); long max = System.currentTimeMillis() + session.getLockTimeout();
boolean checkDeadlock = false; boolean checkDeadlock = false;
while (true) { while (true) {
...@@ -486,16 +488,17 @@ public class TableData extends Table implements RecordReader { ...@@ -486,16 +488,17 @@ public class TableData extends Table implements RecordReader {
return buff.toString(); return buff.toString();
} }
public ObjectArray checkDeadlock(Session session, Session clash) { public ObjectArray checkDeadlock(Session session, Set clash) {
// only one deadlock check at any given time // only one deadlock check at any given time
synchronized (TableData.class) { synchronized (TableData.class) {
if (clash == null) { if (clash == null) {
// verification is started // verification is started
clash = session; clash = new HashSet();
} else if (clash == session) { } else if (clash.contains(session)) {
// we found a circle // we found a circle
return new ObjectArray(); return new ObjectArray();
} }
clash.add(session);
ObjectArray error = null; ObjectArray error = null;
for (Iterator it = lockShared.iterator(); it.hasNext();) { for (Iterator it = lockShared.iterator(); it.hasNext();) {
Session s = (Session) it.next(); Session s = (Session) it.next();
......
...@@ -48,6 +48,7 @@ public class TestDeadlock extends TestBase { ...@@ -48,6 +48,7 @@ public class TestDeadlock extends TestBase {
testLockUpgrade(); testLockUpgrade();
testThreePhilosophers(); testThreePhilosophers();
testNoDeadlock(); testNoDeadlock();
testThreeSome();
deleteDb("deadlock"); deleteDb("deadlock");
} }
...@@ -180,7 +181,7 @@ public class TestDeadlock extends TestBase { ...@@ -180,7 +181,7 @@ public class TestDeadlock extends TestBase {
} }
t2.join(); t2.join();
t3.join(); t3.join();
checkDeadlock(); checkDeadlock(1);
c1.commit(); c1.commit();
c2.commit(); c2.commit();
c3.commit(); c3.commit();
...@@ -188,6 +189,51 @@ public class TestDeadlock extends TestBase { ...@@ -188,6 +189,51 @@ public class TestDeadlock extends TestBase {
end(); 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(2);
c1.commit();
c2.commit();
c3.commit();
c1.createStatement().execute("DROP TABLE TEST_A, TEST_B, TEST_C");
end();
}
private void testLockUpgrade() throws Exception { private void testLockUpgrade() throws Exception {
if (config.mvcc) { if (config.mvcc) {
return; return;
...@@ -214,7 +260,7 @@ public class TestDeadlock extends TestBase { ...@@ -214,7 +260,7 @@ public class TestDeadlock extends TestBase {
catchDeadlock(e); catchDeadlock(e);
} }
t1.join(); t1.join();
checkDeadlock(); checkDeadlock(1);
c1.commit(); c1.commit();
c2.commit(); c2.commit();
c1.createStatement().execute("DROP TABLE TEST"); c1.createStatement().execute("DROP TABLE TEST");
...@@ -243,21 +289,23 @@ public class TestDeadlock extends TestBase { ...@@ -243,21 +289,23 @@ public class TestDeadlock extends TestBase {
catchDeadlock(e); catchDeadlock(e);
} }
t1.join(); t1.join();
checkDeadlock(); checkDeadlock(1);
c1.commit(); c1.commit();
c2.commit(); c2.commit();
c1.createStatement().execute("DROP TABLE T1, T2"); c1.createStatement().execute("DROP TABLE T1, T2");
end(); end();
} }
private void checkDeadlock() throws SQLException { private void checkDeadlock(int max) throws SQLException {
assertTrue(lastException != null); assertTrue(lastException != null);
assertKnownException(lastException); assertKnownException(lastException);
assertEquals(ErrorCode.DEADLOCK_1, lastException.getErrorCode()); assertEquals(ErrorCode.DEADLOCK_1, lastException.getErrorCode());
SQLException e2 = lastException.getNextException(); SQLException e2 = lastException.getNextException();
if (e2 != null) { if (e2 != null && max == 1) {
// we have two exception, but there should only be one // we have two exception, but there should only be one
throw e2; SQLException e3 = new SQLException("Expected one exception, got multiple");
e3.initCause(e2);
throw e3;
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论