Unverified 提交 f1ecb149 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #867 from h2database/test_out_of_memory

TestOutOfMemory stability
...@@ -209,6 +209,8 @@ public class Database implements DataHandler { ...@@ -209,6 +209,8 @@ public class Database implements DataHandler {
private RowFactory rowFactory = RowFactory.DEFAULT; private RowFactory rowFactory = RowFactory.DEFAULT;
public Database(ConnectionInfo ci, String cipher) { public Database(ConnectionInfo ci, String cipher) {
META_LOCK_DEBUGGING.set(null);
META_LOCK_DEBUGGING_STACK.set(null);
String name = ci.getName(); String name = ci.getName();
this.dbSettings = ci.getDbSettings(); this.dbSettings = ci.getDbSettings();
this.reconnectCheckDelayNs = TimeUnit.MILLISECONDS.toNanos(dbSettings.reconnectCheckDelay); this.reconnectCheckDelayNs = TimeUnit.MILLISECONDS.toNanos(dbSettings.reconnectCheckDelay);
...@@ -1256,6 +1258,7 @@ public class Database implements DataHandler { ...@@ -1256,6 +1258,7 @@ public class Database implements DataHandler {
* hook * hook
*/ */
void close(boolean fromShutdownHook) { void close(boolean fromShutdownHook) {
try {
synchronized (this) { synchronized (this) {
if (closing) { if (closing) {
return; return;
...@@ -1270,7 +1273,6 @@ public class Database implements DataHandler { ...@@ -1270,7 +1273,6 @@ public class Database implements DataHandler {
// ignore // ignore
} }
traceSystem.close(); traceSystem.close();
Engine.getInstance().close(databaseName);
return; return;
} }
closing = true; closing = true;
...@@ -1350,7 +1352,6 @@ public class Database implements DataHandler { ...@@ -1350,7 +1352,6 @@ public class Database implements DataHandler {
} }
closeOnExit = null; closeOnExit = null;
} }
Engine.getInstance().close(databaseName);
if (deleteFilesOnDisconnect && persistent) { if (deleteFilesOnDisconnect && persistent) {
deleteFilesOnDisconnect = false; deleteFilesOnDisconnect = false;
try { try {
...@@ -1361,6 +1362,9 @@ public class Database implements DataHandler { ...@@ -1361,6 +1362,9 @@ public class Database implements DataHandler {
// ignore (the trace is closed already) // ignore (the trace is closed already)
} }
} }
} finally {
Engine.getInstance().close(databaseName);
}
} }
private void removeOrphanedLobs() { private void removeOrphanedLobs() {
......
...@@ -69,7 +69,8 @@ class DatabaseCloser extends Thread { ...@@ -69,7 +69,8 @@ class DatabaseCloser extends Thread {
trace.error(e, "could not close the database"); trace.error(e, "could not close the database");
// if this was successful, we ignore the exception // if this was successful, we ignore the exception
// otherwise not // otherwise not
} catch (RuntimeException e2) { } catch (Throwable e2) {
e.addSuppressed(e2);
throw e; throw e;
} }
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
package org.h2.engine; package org.h2.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
...@@ -27,7 +28,7 @@ import org.h2.util.Utils; ...@@ -27,7 +28,7 @@ import org.h2.util.Utils;
public class Engine implements SessionFactory { public class Engine implements SessionFactory {
private static final Engine INSTANCE = new Engine(); private static final Engine INSTANCE = new Engine();
private static final HashMap<String, Database> DATABASES = new HashMap<>(); private static final Map<String, Database> DATABASES = new HashMap<>();
private volatile long wrongPasswordDelay = private volatile long wrongPasswordDelay =
SysProperties.DELAY_WRONG_PASSWORD_MIN; SysProperties.DELAY_WRONG_PASSWORD_MIN;
...@@ -50,13 +51,14 @@ public class Engine implements SessionFactory { ...@@ -50,13 +51,14 @@ public class Engine implements SessionFactory {
Database database; Database database;
ci.removeProperty("NO_UPGRADE", false); ci.removeProperty("NO_UPGRADE", false);
boolean openNew = ci.getProperty("OPEN_NEW", false); boolean openNew = ci.getProperty("OPEN_NEW", false);
boolean opened = false;
User user = null;
synchronized (DATABASES) {
if (openNew || ci.isUnnamedInMemory()) { if (openNew || ci.isUnnamedInMemory()) {
database = null; database = null;
} else { } else {
database = DATABASES.get(name); database = DATABASES.get(name);
} }
User user = null;
boolean opened = false;
if (database == null) { if (database == null) {
if (ifExists && !Database.exists(name)) { if (ifExists && !Database.exists(name)) {
throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, name); throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, name);
...@@ -76,6 +78,7 @@ public class Engine implements SessionFactory { ...@@ -76,6 +78,7 @@ public class Engine implements SessionFactory {
DATABASES.put(name, database); DATABASES.put(name, database);
} }
} }
}
if (opened) { if (opened) {
// start the thread when already synchronizing on the database // start the thread when already synchronizing on the database
// otherwise a deadlock can occur when the writer thread // otherwise a deadlock can occur when the writer thread
...@@ -272,8 +275,10 @@ public class Engine implements SessionFactory { ...@@ -272,8 +275,10 @@ public class Engine implements SessionFactory {
throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, e, "JMX"); throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, e, "JMX");
} }
} }
synchronized (DATABASES) {
DATABASES.remove(name); DATABASES.remove(name);
} }
}
/** /**
* This method is called after validating user name and password. If user * This method is called after validating user name and password. If user
......
...@@ -411,7 +411,7 @@ public class JdbcConnection extends TraceObject ...@@ -411,7 +411,7 @@ public class JdbcConnection extends TraceObject
session = null; session = null;
} }
} }
} catch (Exception e) { } catch (Throwable e) {
throw logAndConvert(e); throw logAndConvert(e);
} }
} }
......
...@@ -248,7 +248,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements ...@@ -248,7 +248,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements
} finally { } finally {
afterWriting(); afterWriting();
} }
} catch (Exception e) { } catch (Throwable e) {
throw logAndConvert(e); throw logAndConvert(e);
} }
} }
......
...@@ -273,7 +273,7 @@ public class DbException extends RuntimeException { ...@@ -273,7 +273,7 @@ public class DbException extends RuntimeException {
* @param e the root cause * @param e the root cause
* @return the SQL exception object * @return the SQL exception object
*/ */
public static SQLException toSQLException(Exception e) { public static SQLException toSQLException(Throwable e) {
if (e instanceof SQLException) { if (e instanceof SQLException) {
return (SQLException) e; return (SQLException) e;
} }
......
...@@ -351,8 +351,10 @@ public class TraceObject { ...@@ -351,8 +351,10 @@ public class TraceObject {
* @param ex the exception * @param ex the exception
* @return the SQL exception object * @return the SQL exception object
*/ */
protected SQLException logAndConvert(Exception ex) { protected SQLException logAndConvert(Throwable ex) {
SQLException e = DbException.toSQLException(ex); SQLException e = null;
try {
e = DbException.toSQLException(ex);
if (trace == null) { if (trace == null) {
DbException.traceThrowable(e); DbException.traceThrowable(e);
} else { } else {
...@@ -363,6 +365,12 @@ public class TraceObject { ...@@ -363,6 +365,12 @@ public class TraceObject {
trace.error(e, "exception"); trace.error(e, "exception");
} }
} }
} catch(Throwable ignore) {
if (e == null) {
e = new SQLException("", "HY000", ex);
}
e.addSuppressed(ignore);
}
return e; return e;
} }
......
...@@ -380,9 +380,7 @@ public final class MVStore { ...@@ -380,9 +380,7 @@ public final class MVStore {
} }
private void panic(IllegalStateException e) { private void panic(IllegalStateException e) {
if (backgroundExceptionHandler != null) { handleException(e);
backgroundExceptionHandler.uncaughtException(null, e);
}
panicException = e; panicException = e;
closeImmediately(); closeImmediately();
throw e; throw e;
...@@ -862,10 +860,8 @@ public final class MVStore { ...@@ -862,10 +860,8 @@ public final class MVStore {
public void closeImmediately() { public void closeImmediately() {
try { try {
closeStore(false); closeStore(false);
} catch (Exception e) { } catch (Throwable e) {
if (backgroundExceptionHandler != null) { handleException(e);
backgroundExceptionHandler.uncaughtException(null, e);
}
} }
} }
...@@ -2468,13 +2464,11 @@ public final class MVStore { ...@@ -2468,13 +2464,11 @@ public final class MVStore {
if (hasUnsavedChanges()) { if (hasUnsavedChanges()) {
try { try {
commitAndSave(); commitAndSave();
} catch (Exception e) { } catch (Throwable e) {
if (backgroundExceptionHandler != null) { handleException(e);
backgroundExceptionHandler.uncaughtException(null, e);
return; return;
} }
} }
}
if (autoCompactFillRate > 0) { if (autoCompactFillRate > 0) {
try { try {
// whether there were file read or write operations since // whether there were file read or write operations since
...@@ -2492,10 +2486,18 @@ public final class MVStore { ...@@ -2492,10 +2486,18 @@ public final class MVStore {
// in the bookkeeping? // in the bookkeeping?
compact(fillRate, autoCommitMemory); compact(fillRate, autoCommitMemory);
autoCompactLastFileOpCount = fileStore.getWriteCount() + fileStore.getReadCount(); autoCompactLastFileOpCount = fileStore.getWriteCount() + fileStore.getReadCount();
} catch (Exception e) { } catch (Throwable e) {
if (backgroundExceptionHandler != null) { handleException(e);
backgroundExceptionHandler.uncaughtException(null, e); }
}
} }
private void handleException(Throwable ex) {
if (backgroundExceptionHandler != null) {
try {
backgroundExceptionHandler.uncaughtException(null, ex);
} catch(Throwable ignore) {
ex.addSuppressed(ignore);
} }
} }
} }
......
...@@ -79,6 +79,8 @@ public abstract class TestBase { ...@@ -79,6 +79,8 @@ public abstract class TestBase {
private final LinkedList<byte[]> memory = new LinkedList<>(); private final LinkedList<byte[]> memory = new LinkedList<>();
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
/** /**
* Get the test directory for this test. * Get the test directory for this test.
* *
...@@ -545,7 +547,6 @@ public abstract class TestBase { ...@@ -545,7 +547,6 @@ public abstract class TestBase {
* @param s the message * @param s the message
*/ */
static synchronized void printlnWithTime(long millis, String s) { static synchronized void printlnWithTime(long millis, String s) {
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
s = dateFormat.format(new java.util.Date()) + " " + s = dateFormat.format(new java.util.Date()) + " " +
formatTime(millis) + " " + s; formatTime(millis) + " " + s;
System.out.println(s); System.out.println(s);
...@@ -1470,6 +1471,12 @@ public abstract class TestBase { ...@@ -1470,6 +1471,12 @@ public abstract class TestBase {
*/ */
protected void freeMemory() { protected void freeMemory() {
memory.clear(); memory.clear();
for (int i = 0; i < 5; i++) {
System.gc();
try {
Thread.sleep(20);
} catch (InterruptedException ignore) {/**/}
}
} }
/** /**
......
...@@ -13,7 +13,9 @@ import java.sql.SQLException; ...@@ -13,7 +13,9 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathMem; import org.h2.store.fs.FilePathMem;
...@@ -37,10 +39,6 @@ public class TestOutOfMemory extends TestBase { ...@@ -37,10 +39,6 @@ public class TestOutOfMemory extends TestBase {
@Override @Override
public void test() throws SQLException, InterruptedException { public void test() throws SQLException, InterruptedException {
if (config.travis) {
// fails regularly under Travis, not sure why
return;
}
if (config.vmlens) { if (config.vmlens) {
// running out of memory will cause the vmlens agent to stop working // running out of memory will cause the vmlens agent to stop working
return; return;
...@@ -60,7 +58,16 @@ public class TestOutOfMemory extends TestBase { ...@@ -60,7 +58,16 @@ public class TestOutOfMemory extends TestBase {
private void testMVStoreUsingInMemoryFileSystem() { private void testMVStoreUsingInMemoryFileSystem() {
FilePath.register(new FilePathMem()); FilePath.register(new FilePathMem());
String fileName = "memFS:" + getTestName(); String fileName = "memFS:" + getTestName();
MVStore store = MVStore.open(fileName); final AtomicReference<Throwable> exRef = new AtomicReference<>();
MVStore store = new MVStore.Builder()
.fileName(fileName)
.backgroundExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
exRef.compareAndSet(null, e);
}
})
.open();
try { try {
Map<Integer, byte[]> map = store.openMap("test"); Map<Integer, byte[]> map = store.openMap("test");
Random r = new Random(1); Random r = new Random(1);
...@@ -70,10 +77,11 @@ public class TestOutOfMemory extends TestBase { ...@@ -70,10 +77,11 @@ public class TestOutOfMemory extends TestBase {
r.nextBytes(data); r.nextBytes(data);
map.put(i, data); map.put(i, data);
} }
Throwable throwable = exRef.get();
if(throwable instanceof OutOfMemoryError) throw (OutOfMemoryError)throwable;
if(throwable instanceof IllegalStateException) throw (IllegalStateException)throwable;
fail(); fail();
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError | IllegalStateException e) {
// expected
} catch (IllegalStateException e) {
// expected // expected
} }
try { try {
...@@ -83,7 +91,7 @@ public class TestOutOfMemory extends TestBase { ...@@ -83,7 +91,7 @@ public class TestOutOfMemory extends TestBase {
} }
store.closeImmediately(); store.closeImmediately();
store = MVStore.open(fileName); store = MVStore.open(fileName);
map = store.openMap("test"); store.openMap("test");
store.close(); store.close();
} finally { } finally {
// just in case, otherwise if this test suffers a spurious failure, // just in case, otherwise if this test suffers a spurious failure,
...@@ -95,47 +103,55 @@ public class TestOutOfMemory extends TestBase { ...@@ -95,47 +103,55 @@ public class TestOutOfMemory extends TestBase {
private void testDatabaseUsingInMemoryFileSystem() throws SQLException, InterruptedException { private void testDatabaseUsingInMemoryFileSystem() throws SQLException, InterruptedException {
String filename = "memFS:" + getTestName(); String filename = "memFS:" + getTestName();
String url = "jdbc:h2:" + filename; String url = "jdbc:h2:" + filename + "/test";
try {
Connection conn = DriverManager.getConnection(url); Connection conn = DriverManager.getConnection(url);
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
try { try {
stat.execute("create table test(id int, name varchar) as " + stat.execute("create table test(id int, name varchar) as " +
"select x, space(10000000) from system_range(1, 1000)"); "select x, space(10000000+x) from system_range(1, 1000)");
fail(); fail();
} catch (SQLException e) { } catch (SQLException e) {
int err = e.getErrorCode(); assertTrue("Unexpected error code: " + e.getErrorCode(),
assertTrue(e.getMessage(), err == ErrorCode.GENERAL_ERROR_1 ErrorCode.OUT_OF_MEMORY == e.getErrorCode() ||
|| err == ErrorCode.OUT_OF_MEMORY); ErrorCode.FILE_CORRUPTED_1 == e.getErrorCode() ||
ErrorCode.DATABASE_IS_CLOSED == e.getErrorCode() ||
ErrorCode.GENERAL_ERROR_1 == e.getErrorCode());
} }
recoverAfterOOM();
try { try {
conn.close(); conn.close();
fail(); fail();
} catch (SQLException e) { } catch (SQLException e) {
int err = e.getErrorCode(); assertTrue("Unexpected error code: " + e.getErrorCode(),
assertTrue(e.getMessage(), err == ErrorCode.GENERAL_ERROR_1 ErrorCode.OUT_OF_MEMORY == e.getErrorCode() ||
|| err == ErrorCode.OUT_OF_MEMORY ErrorCode.FILE_CORRUPTED_1 == e.getErrorCode() ||
|| err == ErrorCode.DATABASE_IS_CLOSED); ErrorCode.DATABASE_IS_CLOSED == e.getErrorCode() ||
} ErrorCode.GENERAL_ERROR_1 == e.getErrorCode());
for (int i = 0; i < 5; i++) {
System.gc();
Thread.sleep(20);
} }
recoverAfterOOM();
conn = DriverManager.getConnection(url); conn = DriverManager.getConnection(url);
stat = conn.createStatement(); stat = conn.createStatement();
stat.execute("select 1"); stat.execute("SELECT 1");
conn.close(); conn.close();
} finally {
// release the static data this test generates // release the static data this test generates
FileUtils.delete(filename); FileUtils.deleteRecursive(filename, true);
} }
private void testUpdateWhenNearlyOutOfMemory() throws SQLException, InterruptedException {
if (config.memory || config.mvcc || config.mvStore) {
return;
} }
private static void recoverAfterOOM() throws InterruptedException {
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
System.gc(); System.gc();
Thread.sleep(20); Thread.sleep(20);
} }
}
private void testUpdateWhenNearlyOutOfMemory() throws SQLException, InterruptedException {
if (config.memory) {
return;
}
recoverAfterOOM();
deleteDb("outOfMemory"); deleteDb("outOfMemory");
Connection conn = getConnection("outOfMemory;MAX_OPERATION_MEMORY=1000000"); Connection conn = getConnection("outOfMemory;MAX_OPERATION_MEMORY=1000000");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
...@@ -148,8 +164,27 @@ public class TestOutOfMemory extends TestBase { ...@@ -148,8 +164,27 @@ public class TestOutOfMemory extends TestBase {
stat.execute("checkpoint"); stat.execute("checkpoint");
eatMemory(80); eatMemory(80);
try { try {
assertThrows(ErrorCode.OUT_OF_MEMORY, prep).execute(); try {
assertThrows(ErrorCode.DATABASE_IS_CLOSED, conn).close(); prep.execute();
fail();
} catch(DbException ex) {
freeMemory();
assertTrue(ErrorCode.OUT_OF_MEMORY == ex.getErrorCode() || ErrorCode.GENERAL_ERROR_1 == ex.getErrorCode());
} catch (SQLException ex) {
freeMemory();
assertTrue(ErrorCode.OUT_OF_MEMORY == ex.getErrorCode() || ErrorCode.GENERAL_ERROR_1 == ex.getErrorCode());
}
recoverAfterOOM();
try {
conn.close();
fail();
} catch(DbException ex) {
freeMemory();
assertEquals(ErrorCode.DATABASE_IS_CLOSED, ex.getErrorCode());
} catch (SQLException ex) {
freeMemory();
assertEquals(ErrorCode.DATABASE_IS_CLOSED, ex.getErrorCode());
}
freeMemory(); freeMemory();
conn = null; conn = null;
conn = getConnection("outOfMemory"); conn = getConnection("outOfMemory");
...@@ -160,7 +195,7 @@ public class TestOutOfMemory extends TestBase { ...@@ -160,7 +195,7 @@ public class TestOutOfMemory extends TestBase {
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
freeMemory(); freeMemory();
// out of memory not detected // out of memory not detected
throw (Error) new AssertionError("Out of memory not detected").initCause(e); throw new AssertionError("Out of memory not detected", e);
} finally { } finally {
freeMemory(); freeMemory();
if (conn != null) { if (conn != null) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论