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

Merge pull request #876 from h2database/test_out_of_memory

Test out of memory
......@@ -2450,27 +2450,27 @@ public final class MVStore {
* needed.
*/
void writeInBackground() {
if (closed) {
return;
}
try {
if (closed) {
return;
}
// could also commit when there are many unsaved pages,
// but according to a test it doesn't really help
// could also commit when there are many unsaved pages,
// but according to a test it doesn't really help
long time = getTimeSinceCreation();
if (time <= lastCommitTime + autoCommitDelay) {
return;
}
if (hasUnsavedChanges()) {
try {
commitAndSave();
} catch (Throwable e) {
handleException(e);
long time = getTimeSinceCreation();
if (time <= lastCommitTime + autoCommitDelay) {
return;
}
}
if (autoCompactFillRate > 0) {
try {
if (hasUnsavedChanges()) {
try {
commitAndSave();
} catch (Throwable e) {
handleException(e);
return;
}
}
if (autoCompactFillRate > 0) {
// whether there were file read or write operations since
// the last time
boolean fileOps;
......@@ -2486,9 +2486,9 @@ public final class MVStore {
// in the bookkeeping?
compact(fillRate, autoCommitMemory);
autoCompactLastFileOpCount = fileStore.getWriteCount() + fileStore.getReadCount();
} catch (Throwable e) {
handleException(e);
}
} catch (Throwable e) {
handleException(e);
}
}
......@@ -2497,7 +2497,9 @@ public final class MVStore {
try {
backgroundExceptionHandler.uncaughtException(null, ex);
} catch(Throwable ignore) {
ex.addSuppressed(ignore);
if (ex != ignore) { // OOME may be the same
ex.addSuppressed(ignore);
}
}
}
}
......
......@@ -6,6 +6,7 @@
package org.h2.test;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
......@@ -30,7 +31,9 @@ import java.sql.Types;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.SimpleTimeZone;
import java.util.concurrent.TimeUnit;
......@@ -40,6 +43,7 @@ import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils;
import org.h2.test.utils.ProxyCodeGenerator;
import org.h2.test.utils.ResultVerifier;
import org.h2.test.utils.SelfDestructor;
import org.h2.tools.DeleteDbFiles;
/**
......@@ -1444,6 +1448,16 @@ public abstract class TestBase {
return System.getProperty("java.class.path");
}
/**
* Get the path to a java executable of the current process
*
* @return the path to java
*/
private String getJVM() {
return System.getProperty("java.home") + File.separatorChar + "bin" +
File.separator + "java";
}
/**
* Use up almost all memory.
*
......@@ -1687,4 +1701,46 @@ public abstract class TestBase {
return getClass().getSimpleName();
}
public ProcessBuilder buildChild(String name, Class<? extends TestBase> childClass,
String... jvmArgs) {
List<String> args = new ArrayList<>(16);
args.add(getJVM());
Collections.addAll(args, jvmArgs);
Collections.addAll(args, "-cp", getClassPath(),
SelfDestructor.getPropertyString(1),
childClass.getName(),
"-url", getURL(name, true),
"-user", getUser(),
"-password", getPassword());
ProcessBuilder processBuilder = new ProcessBuilder()
// .redirectError(ProcessBuilder.Redirect.INHERIT)
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.command(args);
return processBuilder;
}
public abstract static class Child extends TestBase
{
private String url;
private String user;
private String password;
public Child(String... args) {
for (int i = 0; i < args.length; i++) {
if ("-url".equals(args[i])) {
url = args[++i];
} else if ("-user".equals(args[i])) {
user = args[++i];
} else if ("-password".equals(args[i])) {
password = args[++i];
}
SelfDestructor.startCountdown(60);
}
}
public Connection getConnection() throws SQLException {
return getConnection(url, user, password);
}
}
}
......@@ -15,7 +15,6 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.mvstore.MVStore;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathMem;
......@@ -28,6 +27,8 @@ import org.h2.test.TestBase;
*/
public class TestOutOfMemory extends TestBase {
private static final String DB_NAME = "outOfMemory";
/**
* Run just this test.
*
......@@ -38,16 +39,18 @@ public class TestOutOfMemory extends TestBase {
}
@Override
public void test() throws SQLException, InterruptedException {
public void test() throws Exception {
if (config.vmlens) {
// running out of memory will cause the vmlens agent to stop working
return;
}
try {
System.gc();
testMVStoreUsingInMemoryFileSystem();
System.gc();
testDatabaseUsingInMemoryFileSystem();
if (!config.travis) {
System.gc();
testMVStoreUsingInMemoryFileSystem();
System.gc();
testDatabaseUsingInMemoryFileSystem();
}
System.gc();
testUpdateWhenNearlyOutOfMemory();
} finally {
......@@ -147,67 +150,92 @@ public class TestOutOfMemory extends TestBase {
}
}
private void testUpdateWhenNearlyOutOfMemory() throws SQLException, InterruptedException {
private void testUpdateWhenNearlyOutOfMemory() throws Exception {
if (config.memory) {
return;
}
recoverAfterOOM();
deleteDb("outOfMemory");
Connection conn = getConnection("outOfMemory;MAX_OPERATION_MEMORY=1000000");
Statement stat = conn.createStatement();
stat.execute("drop all objects");
stat.execute("create table stuff (id int, text varchar as space(100) || id)");
stat.execute("insert into stuff(id) select x from system_range(1, 3000)");
PreparedStatement prep = conn.prepareStatement(
"update stuff set text = text || space(1000) || id");
prep.execute();
stat.execute("checkpoint");
eatMemory(80);
try {
try {
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());
deleteDb(DB_NAME);
ProcessBuilder processBuilder = buildChild(
DB_NAME + ";MAX_OPERATION_MEMORY=1000000",
MyChild.class,
"-XX:+UseParallelGC",
// "-XX:+UseG1GC",
"-Xmx128m");
//*
processBuilder.start().waitFor();
/*/
List<String> args = processBuilder.command();
for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) {
String arg = iter.next();
if(arg.equals(MyChild.class.getName())) {
iter.remove();
break;
}
freeMemory();
conn = null;
conn = getConnection("outOfMemory");
stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select count(*) from stuff");
rs.next();
iter.remove();
}
MyChild.main(args.toArray(new String[0]));
//*/
try (Connection conn = getConnection(DB_NAME)) {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("SELECT count(*) FROM stuff");
assertTrue(rs.next());
assertEquals(3000, rs.getInt(1));
} catch (OutOfMemoryError e) {
freeMemory();
// out of memory not detected
throw new AssertionError("Out of memory not detected", e);
} finally {
freeMemory();
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// out of memory will / may close the database
assertKnownException(e);
}
rs = stat.executeQuery("SELECT * FROM stuff WHERE id = 3000");
assertTrue(rs.next());
String text = rs.getString(2);
assertFalse(rs.wasNull());
assertEquals(1004, text.length());
// TODO: there are intermittent failures here
// where number is about 1000 short of expected value.
// This indicates a real problem - durability failure
// and need to be looked at.
rs = stat.executeQuery("SELECT sum(length(text)) FROM stuff");
assertTrue(rs.next());
int totalSize = rs.getInt(1);
if (3010893 > totalSize) {
TestBase.logErrorMessage("Durability failure - expected: 3010893, actual: " + totalSize);
}
} finally {
deleteDb(DB_NAME);
}
deleteDb("outOfMemory");
}
public static final class MyChild extends TestBase.Child
{
public static void main(String... args) throws Exception {
new MyChild(args).init().test();
}
private MyChild(String... args) {
super(args);
}
@Override
public void test() {
try (Connection conn = getConnection()) {
Statement stat = conn.createStatement();
stat.execute("DROP ALL OBJECTS");
stat.execute("CREATE TABLE stuff (id INT, text VARCHAR)");
stat.execute("INSERT INTO stuff(id) SELECT x FROM system_range(1, 3000)");
PreparedStatement prep = conn.prepareStatement(
"UPDATE stuff SET text = IFNULL(text,'') || space(1000) || id");
prep.execute();
stat.execute("CHECKPOINT");
ResultSet rs = stat.executeQuery("SELECT sum(length(text)) FROM stuff");
assertTrue(rs.next());
assertEquals(3010893, rs.getInt(1));
eatMemory(80);
prep.execute();
fail();
} catch (SQLException ignore) {
} finally {
freeMemory();
}
}
}
}
......@@ -124,14 +124,6 @@ public class TestMVStoreBenchmark extends TestBase {
}
static long getMemory() {
try {
LinkedList<byte[]> list = new LinkedList<>();
while (true) {
list.add(new byte[1024]);
}
} catch (OutOfMemoryError e) {
// ok
}
for (int i = 0; i < 16; i++) {
System.gc();
try {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论