Unverified 提交 043f22a8 authored 作者: Andrei Tokar's avatar Andrei Tokar 提交者: GitHub

Merge pull request #1673 from h2database/issue_1672

Deadlock on MVStore closure due to OOM
...@@ -185,7 +185,7 @@ public class MVStore implements AutoCloseable { ...@@ -185,7 +185,7 @@ public class MVStore implements AutoCloseable {
private volatile boolean reuseSpace = true; private volatile boolean reuseSpace = true;
private final AtomicInteger state = new AtomicInteger(); private volatile int state;
private final FileStore fileStore; private final FileStore fileStore;
...@@ -960,10 +960,11 @@ public class MVStore implements AutoCloseable { ...@@ -960,10 +960,11 @@ public class MVStore implements AutoCloseable {
// isClosed() would wait until closure is done and then we jump out of the loop. // isClosed() would wait until closure is done and then we jump out of the loop.
// This is a subtle difference between !isClosed() and isOpen(). // This is a subtle difference between !isClosed() and isOpen().
while (!isClosed()) { while (!isClosed()) {
if (state.compareAndSet(STATE_OPEN, STATE_STOPPING)) { stopBackgroundThread(normalShutdown);
try { storeLock.lock();
stopBackgroundThread(normalShutdown); try {
storeLock.lock(); if (state == STATE_OPEN) {
state = STATE_STOPPING;
try { try {
try { try {
if (normalShutdown && fileStore != null && !fileStore.isReadOnly()) { if (normalShutdown && fileStore != null && !fileStore.isReadOnly()) {
...@@ -979,7 +980,7 @@ public class MVStore implements AutoCloseable { ...@@ -979,7 +980,7 @@ public class MVStore implements AutoCloseable {
shrinkFileIfPossible(0); shrinkFileIfPossible(0);
} }
state.set(STATE_CLOSING); state = STATE_CLOSING;
// release memory early - this is important when called // release memory early - this is important when called
// because of out of memory // because of out of memory
...@@ -1000,11 +1001,11 @@ public class MVStore implements AutoCloseable { ...@@ -1000,11 +1001,11 @@ public class MVStore implements AutoCloseable {
} }
} }
} finally { } finally {
storeLock.unlock(); state = STATE_CLOSED;
} }
} finally {
state.set(STATE_CLOSED);
} }
} finally {
storeLock.unlock();
} }
} }
} }
...@@ -2806,7 +2807,7 @@ public class MVStore implements AutoCloseable { ...@@ -2806,7 +2807,7 @@ public class MVStore implements AutoCloseable {
} }
private boolean isOpen() { private boolean isOpen() {
return state.get() == STATE_OPEN; return state == STATE_OPEN;
} }
/** /**
...@@ -2817,27 +2818,17 @@ public class MVStore implements AutoCloseable { ...@@ -2817,27 +2818,17 @@ public class MVStore implements AutoCloseable {
if (isOpen()) { if (isOpen()) {
return false; return false;
} }
int millis = 1; storeLock.lock();
while (state.get() != STATE_CLOSED) { try {
/* assert state == STATE_CLOSED;
* We need to wait for completion of close procedure. This is return true;
* required because otherwise database may be closed too early while } finally {
* underlying storage still has unreleased resources. The quickly storeLock.unlock();
* following connection attempts fail with The file is locked
* exception.
*/
try {
Thread.sleep(millis++);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
} }
return true;
} }
private boolean isOpenOrStopping() { private boolean isOpenOrStopping() {
return state.get() <= STATE_STOPPING; return state <= STATE_STOPPING;
} }
private void stopBackgroundThread(boolean waitForIt) { private void stopBackgroundThread(boolean waitForIt) {
......
...@@ -25,31 +25,35 @@ public class TestAnalyzeTableTx extends TestDb { ...@@ -25,31 +25,35 @@ public class TestAnalyzeTableTx extends TestDb {
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
if (config.networked || config.big) { return !config.networked && !config.big;
return false;
}
return true;
} }
@Override @Override
public void test() throws Exception { public void test() throws Exception {
deleteDb(getTestName()); deleteDb(getTestName());
Connection shared = getConnection(getTestName());
Statement statement = shared.createStatement();
statement.executeUpdate("DROP TABLE IF EXISTS TEST");
statement.executeUpdate("CREATE TABLE TEST(ID INT PRIMARY KEY)");
Connection[] connections = new Connection[C]; Connection[] connections = new Connection[C];
for (int i = 0; i < C; i++) { try (Connection shared = getConnection(getTestName())) {
Connection c = getConnection(getTestName()); Statement statement = shared.createStatement();
c.createStatement().executeUpdate("INSERT INTO TEST VALUES (" + i + ')'); statement.executeUpdate("DROP TABLE IF EXISTS TEST");
connections[i] = c; statement.executeUpdate("CREATE TABLE TEST(ID INT PRIMARY KEY)");
}
try (ResultSet rs = statement.executeQuery("SELECT * FROM TEST")) {
for (int i = 0; i < C; i++) { for (int i = 0; i < C; i++) {
if (!rs.next()) Connection c = getConnection(getTestName());
throw new Exception("next"); c.createStatement().executeUpdate("INSERT INTO TEST VALUES (" + i + ')');
if (rs.getInt(1) != i) connections[i] = c;
throw new Exception(Integer.toString(i)); }
try (ResultSet rs = statement.executeQuery("SELECT * FROM TEST")) {
for (int i = 0; i < C; i++) {
if (!rs.next())
throw new Exception("next");
if (rs.getInt(1) != i)
throw new Exception(Integer.toString(i));
}
}
} finally {
for (Connection connection : connections) {
if (connection != null) {
try { connection.close(); } catch (Throwable ignore) {/**/}
}
} }
} }
} }
......
...@@ -506,8 +506,7 @@ public class TestMultiThread extends TestDb implements Runnable { ...@@ -506,8 +506,7 @@ public class TestMultiThread extends TestDb implements Runnable {
@Override @Override
public void run() { public void run() {
try { try (Connection c = getConnection("concurrentUpdate2;LOCK_TIMEOUT=10000")) {
Connection c = getConnection("concurrentUpdate2;LOCK_TIMEOUT=10000");
PreparedStatement ps = c.prepareStatement("UPDATE TEST SET V = ? WHERE " + column + " = ?"); PreparedStatement ps = c.prepareStatement("UPDATE TEST SET V = ? WHERE " + column + " = ?");
for (int test = 0; test < 1000; test++) { for (int test = 0; test < 1000; test++) {
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
...@@ -524,34 +523,36 @@ public class TestMultiThread extends TestDb implements Runnable { ...@@ -524,34 +523,36 @@ public class TestMultiThread extends TestDb implements Runnable {
private void testConcurrentUpdate2() throws Exception { private void testConcurrentUpdate2() throws Exception {
deleteDb("concurrentUpdate2"); deleteDb("concurrentUpdate2");
Connection c = getConnection("concurrentUpdate2"); try (Connection c = getConnection("concurrentUpdate2")) {
Statement s = c.createStatement(); Statement s = c.createStatement();
s.execute("CREATE TABLE TEST(A INT, B INT, V INT, PRIMARY KEY(A, B))"); s.execute("CREATE TABLE TEST(A INT, B INT, V INT, PRIMARY KEY(A, B))");
PreparedStatement ps = c.prepareStatement("INSERT INTO TEST VALUES (?, ?, ?)"); PreparedStatement ps = c.prepareStatement("INSERT INTO TEST VALUES (?, ?, ?)");
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) { for (int j = 0; j < 16; j++) {
ps.setInt(1, i); ps.setInt(1, i);
ps.setInt(2, j); ps.setInt(2, j);
ps.setInt(3, 0); ps.setInt(3, 0);
ps.executeUpdate(); ps.executeUpdate();
}
} }
} ConcurrentUpdate2 a = new ConcurrentUpdate2("A");
ConcurrentUpdate2 a = new ConcurrentUpdate2("A"); ConcurrentUpdate2 b = new ConcurrentUpdate2("B");
ConcurrentUpdate2 b = new ConcurrentUpdate2("B"); a.start();
a.start(); b.start();
b.start(); a.join();
a.join(); b.join();
b.join(); Throwable e = a.exception;
deleteDb("concurrentUpdate2"); if (e == null) {
Throwable e = a.exception; e = b.exception;
if (e == null) {
e = b.exception;
}
if (e != null) {
if (e instanceof Exception) {
throw (Exception) e;
} }
throw (Error) e; if (e != null) {
if (e instanceof Exception) {
throw (Exception) e;
}
throw (Error) e;
}
} finally {
deleteDb("concurrentUpdate2");
} }
} }
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论