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