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

MVStore: power failure could corrupt the store, if writes were re-ordered.

上级 ba429036
......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>MVStore: power failure could corrupt the store, if writes were re-ordered.
</li>
<li>For compatibility with other databases, support for (double and float)
-0.0 has been removed. 0.0 is used instead.
</li>
......
......@@ -795,6 +795,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestMVTableEngine());
addTest(new TestObjectDataType());
addTest(new TestRandomMapOps());
addTest(new TestReorderWrites());
addTest(new TestSpinLock());
addTest(new TestStreamStore());
addTest(new TestTransactionStore());
......
......@@ -13,6 +13,7 @@ import java.util.Map;
import java.util.Random;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.MVStoreTool;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
......@@ -24,6 +25,8 @@ import org.h2.test.utils.FilePathReorderWrites;
*/
public class TestReorderWrites extends TestBase {
private static final boolean LOG = false;
/**
* Run just this test.
*
......@@ -35,16 +38,16 @@ public class TestReorderWrites extends TestBase {
@Override
public void test() throws Exception {
testMVStore();
testFileSystem();
// testMVStore();
}
private void testMVStore() {
FilePathReorderWrites fs = FilePathReorderWrites.register();
String fileName = "reorder:memFS:test.mv";
Random r = new Random(1);
for (int i = 0; i < 100; i++) {
System.out.println(i + " tst --------------------------------");
for (int i = 0; i < 1000; i++) {
log(i + " --------------------------------");
Random r = new Random(i);
fs.setPowerOffCountdown(100, i);
FileUtils.delete(fileName);
MVStore store = new MVStore.Builder().
......@@ -52,12 +55,12 @@ public class TestReorderWrites extends TestBase {
autoCommitDisabled().open();
// store.setRetentionTime(10);
Map<Integer, byte[]> map = store.openMap("data");
map.put(0, new byte[1]);
map.put(-1, new byte[1]);
store.commit();
// if (r.nextBoolean()) {
store.getFileStore().sync();
//}
fs.setPowerOffCountdown(4 + r.nextInt(20), i);
store.getFileStore().sync();
int stop = 4 + r.nextInt(20);
log("synched start");
fs.setPowerOffCountdown(stop, i);
try {
for (int j = 1; j < 100; j++) {
Map<Integer, Integer> newMap = store.openMap("d" + j);
......@@ -69,31 +72,61 @@ public class TestReorderWrites extends TestBase {
} else {
map.put(key, new byte[len]);
}
log("op " + j + ": ");
store.commit();
switch (r.nextInt(10)) {
case 0:
log("op compact");
store.compact(100, 10 * 1024);
break;
case 1:
log("op compactMoveChunks");
store.compactMoveChunks();
log("op compactMoveChunks done");
break;
}
}
// write has to fail at some point
fail();
} catch (IllegalStateException e) {
log("stop " + e);
// expected
}
store.close();
System.out.println("-------------------------------- test");
try {
store.close();
} catch (IllegalStateException e) {
// expected
store.closeImmediately();
}
log("verify");
fs.setPowerOffCountdown(100, 0);
System.out.println("file size: " + FileUtils.size(fileName));
if (LOG) {
MVStoreTool.dump(fileName, true);
}
store = new MVStore.Builder().
fileName(fileName).
autoCommitDisabled().open();
map = store.openMap("data");
assertEquals(1, map.get(0).length);
if (!map.containsKey(-1)) {
fail("key not found, size=" + map.size() + " i=" + i);
} else {
assertEquals("i=" + i, 1, map.get(-1).length);
}
for (int j = 0; j < 100; j++) {
Map<Integer, Integer> newMap = store.openMap("d" + j);
newMap.get(j);
}
// map.keySet();
map.keySet();
store.close();
}
}
private static void log(String message) {
if (LOG) {
System.out.println(message);
}
}
private void testFileSystem() throws IOException {
FilePathReorderWrites fs = FilePathReorderWrites.register();
String fileName = "reorder:memFS:test";
......
......@@ -948,6 +948,10 @@ public class TestMVStore extends TestBase {
break;
}
}
// the last chunk is at the end
s.setReuseSpace(false);
map = s.openMap("test2");
map.put(1, new byte[1000]);
s.close();
FilePath f = FilePath.get(fileName);
int blockSize = 4 * 1024;
......@@ -976,6 +980,8 @@ public class TestMVStore extends TestBase {
s = openStore(fileName);
map = s.openMap("test");
assertEquals(100, map.get(0).length);
map = s.openMap("test2");
assertFalse(map.containsKey(1));
s.close();
} else {
// both headers are corrupt
......@@ -1414,7 +1420,7 @@ public class TestMVStore extends TestBase {
assertEquals(0, m.size());
s.commit();
// ensure only nodes are read, but not leaves
assertEquals(41, s.getFileStore().getReadCount());
assertEquals(45, s.getFileStore().getReadCount());
assertTrue(s.getFileStore().getWriteCount() < 5);
s.close();
}
......@@ -1579,7 +1585,6 @@ public class TestMVStore extends TestBase {
data.put("2", "World");
s.commit();
assertEquals(1, s.getCurrentVersion());
assertFalse(m.containsKey("chunk.2"));
assertEquals("[data]", s.getMapNames().toString());
assertEquals("data", s.getMapName(data.getId()));
......@@ -1599,8 +1604,6 @@ public class TestMVStore extends TestBase {
s.rollbackTo(1);
assertEquals("Hello", data.get("1"));
assertEquals("World", data.get("2"));
assertFalse(m.containsKey("chunk.1"));
assertFalse(m.containsKey("chunk.2"));
s.close();
}
......
......@@ -105,10 +105,11 @@ public class FilePathReorderWrites extends FilePathWrapper {
if (powerFailureCountdown == 0) {
return;
}
if (--powerFailureCountdown > 0) {
return;
if (powerFailureCountdown < 0) {
throw POWER_FAILURE;
}
if (powerFailureCountdown >= -1) {
powerFailureCountdown--;
if (powerFailureCountdown == 0) {
powerFailureCountdown--;
throw POWER_FAILURE;
}
......@@ -122,7 +123,7 @@ public class FilePathReorderWrites extends FilePathWrapper {
IOUtils.copy(in, out);
FileChannel base = getBase().open(mode);
FileChannel readBase = copy.open(mode);
return new FilePowerFailure(this, base, readBase);
return new FileReorderWrites(this, base, readBase);
}
@Override
......@@ -140,7 +141,7 @@ public class FilePathReorderWrites extends FilePathWrapper {
/**
* An file that checks for errors before each write operation.
*/
class FilePowerFailure extends FileBase {
class FileReorderWrites extends FileBase {
private final FilePathReorderWrites file;
/**
......@@ -162,7 +163,7 @@ class FilePowerFailure extends FileBase {
private int id;
FilePowerFailure(FilePathReorderWrites file, FileChannel base, FileChannel readBase) {
FileReorderWrites(FilePathReorderWrites file, FileChannel base, FileChannel readBase) {
this.file = file;
this.base = base;
this.readBase = readBase;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论