/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.poweroff;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
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;
import org.h2.test.utils.FilePathReorderWrites;
/**
* Tests that the MVStore recovers from a power failure if the file system or
* disk re-ordered the write operations.
*/
public class TestReorderWrites extends TestBase {
private static final boolean LOG = false;
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testMVStore();
testFileSystem();
}
private void testMVStore() {
FilePathReorderWrites fs = FilePathReorderWrites.register();
String fileName = "reorder:memFS:test.mv";
try {
for (int i = 0; i < 1000; i++) {
log(i + " --------------------------------");
// this test is not interested in power off failures during
// initial creation
fs.setPowerOffCountdown(0, 0);
// release the static data this test generates
FileUtils.delete("memFS:test.mv");
FileUtils.delete("memFS:test.mv.copy");
MVStore store = new MVStore.Builder().
fileName(fileName).
autoCommitDisabled().open();
// store.setRetentionTime(10);
Map<Integer, byte[]> map = store.openMap("data");
map.put(-1, new byte[1]);
store.commit();
store.getFileStore().sync();
Random r = new Random(i);
int stop = 4 + r.nextInt(20);
log("countdown start");
fs.setPowerOffCountdown(stop, i);
try {
for (int j = 1; j < 100; j++) {
Map<Integer, Integer> newMap = store.openMap("d" + j);
newMap.put(j, j * 10);
int key = r.nextInt(10);
int len = 10 * r.nextInt(1000);
if (r.nextBoolean()) {
map.remove(key);
} 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
}
try {
store.close();
} catch (IllegalStateException e) {
// expected
store.closeImmediately();
}
log("verify");
fs.setPowerOffCountdown(100, 0);
if (LOG) {
MVStoreTool.dump(fileName, true);
}
store = new MVStore.Builder().
fileName(fileName).
autoCommitDisabled().open();
map = store.openMap("data");
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();
store.close();
}
} finally {
// release the static data this test generates
FileUtils.delete("memFS:test.mv");
FileUtils.delete("memFS:test.mv.copy");
}
}
private static void log(String message) {
if (LOG) {
System.out.println(message);
}
}
private void testFileSystem() throws IOException {
FilePathReorderWrites fs = FilePathReorderWrites.register();
// disable this for now, still a bug somewhere
FilePathReorderWrites.setPartialWrites(false);
String fileName = "reorder:memFS:test";
ByteBuffer empty = ByteBuffer.allocate(1024);
Random r = new Random(1);
long minSize = Long.MAX_VALUE;
long maxSize = 0;
int minWritten = Integer.MAX_VALUE;
int maxWritten = 0;
for (int i = 0; i < 100; i++) {
fs.setPowerOffCountdown(100, i);
FileUtils.delete(fileName);
FileChannel fc = FilePath.get(fileName).open("rw");
for (int j = 0; j < 20; j++) {
fc.write(empty, j * 1024);
empty.flip();
}
fs.setPowerOffCountdown(4 + r.nextInt(20), i);
int lastWritten = 0;
int lastTruncated = 0;
for (int j = 20; j >= 0; j--) {
try {
byte[] bytes = new byte[1024];
Arrays.fill(bytes, (byte) j);
ByteBuffer data = ByteBuffer.wrap(bytes);
fc.write(data, 0);
lastWritten = j;
} catch (IOException e) {
// expected
break;
}
try {
fc.truncate(j * 1024);
lastTruncated = j * 1024;
} catch (IOException e) {
// expected
break;
}
}
if (lastTruncated <= 0 || lastWritten <= 0) {
fail();
}
fs.setPowerOffCountdown(100, 0);
fc = FilePath.get(fileName).open("rw");
ByteBuffer data = ByteBuffer.allocate(1024);
fc.read(data, 0);
data.flip();
int got = data.get();
long size = fc.size();
minSize = Math.min(minSize, size);
maxSize = Math.max(minSize, size);
minWritten = Math.min(minWritten, got);
maxWritten = Math.max(maxWritten, got);
}
assertTrue(minSize < maxSize);
assertTrue(minWritten < maxWritten);
// release the static data this test generates
FileUtils.delete(fileName);
}
}