提交 0c340300 authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore bugfixes and test cases

上级 fa2a6925
...@@ -527,9 +527,11 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -527,9 +527,11 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Close the map, making it read only and release the memory. * Close the map, making it read only and release the memory. This method
* may only be called when closing the store or when removing the map, as
* further writes are not possible.
*/ */
public void close() { void close() {
closed = true; closed = true;
readOnly = true; readOnly = true;
removeAllOldVersions(); removeAllOldVersions();
...@@ -1162,10 +1164,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1162,10 +1164,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} }
void setWriteVersion(long writeVersion) { void setWriteVersion(long writeVersion) {
if (readOnly) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Trying to write to a read-only map");
}
this.writeVersion = writeVersion; this.writeVersion = writeVersion;
} }
......
...@@ -15,9 +15,9 @@ import java.util.Comparator; ...@@ -15,9 +15,9 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.h2.compress.CompressLZF; import org.h2.compress.CompressLZF;
import org.h2.compress.Compressor; import org.h2.compress.Compressor;
import org.h2.mvstore.cache.CacheLongKeyLIRS; import org.h2.mvstore.cache.CacheLongKeyLIRS;
...@@ -268,7 +268,7 @@ public class MVStore { ...@@ -268,7 +268,7 @@ public class MVStore {
o = config.get("cacheSize"); o = config.get("cacheSize");
int mb = o == null ? 16 : (Integer) o; int mb = o == null ? 16 : (Integer) o;
int maxMemoryBytes = mb * 1024 * 1024; int maxMemoryBytes = mb * 1024 * 1024;
int averageMemory = pageSplitSize / 2; int averageMemory = Math.max(10, pageSplitSize / 2);
int segmentCount = 16; int segmentCount = 16;
int stackMoveDistance = maxMemoryBytes / averageMemory * 2 / 100; int stackMoveDistance = maxMemoryBytes / averageMemory * 2 / 100;
cache = new CacheLongKeyLIRS<Page>( cache = new CacheLongKeyLIRS<Page>(
...@@ -642,6 +642,8 @@ public class MVStore { ...@@ -642,6 +642,8 @@ public class MVStore {
* Close the file and the store. If there are any committed but unsaved * Close the file and the store. If there are any committed but unsaved
* changes, they are written to disk first. If any temporary data was * changes, they are written to disk first. If any temporary data was
* written but not committed, this is rolled back. All open maps are closed. * written but not committed, this is rolled back. All open maps are closed.
* <p>
* It is not allowed to concurrently call close and store.
*/ */
public void close() { public void close() {
if (closed) { if (closed) {
...@@ -649,26 +651,12 @@ public class MVStore { ...@@ -649,26 +651,12 @@ public class MVStore {
} }
if (fileStore != null && !fileStore.isReadOnly()) { if (fileStore != null && !fileStore.isReadOnly()) {
stopBackgroundThread(); stopBackgroundThread();
; // TODO this is used for testing only
// wait until a pending store operation is finished
if (currentStoreVersion >= 0) { if (currentStoreVersion >= 0) {
System.out.println("Currently storing, waiting..."); // in this case, store is called manually in another thread
Map<Thread, StackTraceElement[]> st = Thread.getAllStackTraces(); throw DataUtils.newIllegalStateException(
for(Entry<Thread, StackTraceElement[]> e : st.entrySet()) { DataUtils.ERROR_WRITING_FAILED,
System.out.println(e.getKey().toString()); "Can not close while storing");
System.out.println(Arrays.toString(e.getValue()));
}
for (int i=0; i<10000 && currentStoreVersion >= 0; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// ignore
}
}
if (currentStoreVersion >= 0) {
System.out.println("Still storing?!");
}
} }
if (hasUnsavedChanges() || lastCommittedVersion != currentVersion) { if (hasUnsavedChanges() || lastCommittedVersion != currentVersion) {
...@@ -827,8 +815,18 @@ public class MVStore { ...@@ -827,8 +815,18 @@ public class MVStore {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED, "This store is read-only"); DataUtils.ERROR_WRITING_FAILED, "This store is read-only");
} }
try {
currentStoreVersion = currentVersion;
return storeNow(temp);
} finally {
// in any case reset the current store version,
// to allow closing the store
currentStoreVersion = -1;
}
}
private long storeNow(boolean temp) {
int currentUnsavedPageCount = unsavedPageCount; int currentUnsavedPageCount = unsavedPageCount;
currentStoreVersion = currentVersion;
long storeVersion = currentStoreVersion; long storeVersion = currentStoreVersion;
long version = ++currentVersion; long version = ++currentVersion;
long time = getTime(); long time = getTime();
...@@ -911,7 +909,8 @@ public class MVStore { ...@@ -911,7 +909,8 @@ public class MVStore {
// this will (again) modify maxLengthLive, but // this will (again) modify maxLengthLive, but
// the correct value is written in the chunk header // the correct value is written in the chunk header
buff = meta.getRoot().writeUnsavedRecursive(c, buff); Page metaRoot = meta.getRoot();
buff = metaRoot.writeUnsavedRecursive(c, buff);
int chunkLength = buff.position(); int chunkLength = buff.position();
...@@ -941,7 +940,7 @@ public class MVStore { ...@@ -941,7 +940,7 @@ public class MVStore {
c.start = filePos; c.start = filePos;
c.length = chunkLength; c.length = chunkLength;
c.metaRootPos = meta.getRoot().getPos(); c.metaRootPos = metaRoot.getPos();
buff.position(0); buff.position(0);
c.writeHeader(buff); c.writeHeader(buff);
rootChunkStart = filePos; rootChunkStart = filePos;
...@@ -970,11 +969,10 @@ public class MVStore { ...@@ -970,11 +969,10 @@ public class MVStore {
p.writeEnd(); p.writeEnd();
} }
} }
meta.getRoot().writeEnd(); metaRoot.writeEnd();
// some pages might have been changed in the meantime (in the newest version) // some pages might have been changed in the meantime (in the newest version)
unsavedPageCount = Math.max(0, unsavedPageCount - currentUnsavedPageCount); unsavedPageCount = Math.max(0, unsavedPageCount - currentUnsavedPageCount);
currentStoreVersion = -1;
if (!temp) { if (!temp) {
metaChanged = false; metaChanged = false;
lastStoredVersion = storeVersion; lastStoredVersion = storeVersion;
...@@ -1428,6 +1426,10 @@ public class MVStore { ...@@ -1428,6 +1426,10 @@ public class MVStore {
* @return the page * @return the page
*/ */
Page readPage(MVMap<?, ?> map, long pos) { Page readPage(MVMap<?, ?> map, long pos) {
if (pos == 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT, "Position 0");
}
Page p = cache.get(pos); Page p = cache.get(pos);
if (p == null) { if (p == null) {
Chunk c = getChunk(pos); Chunk c = getChunk(pos);
......
...@@ -871,6 +871,10 @@ public class Page { ...@@ -871,6 +871,10 @@ public class Page {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
Page p = childrenPages[i]; Page p = childrenPages[i];
if (p != null) { if (p != null) {
if (p.getPos() == 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Page not written");
}
p.writeEnd(); p.writeEnd();
childrenPages[i] = null; childrenPages[i] = null;
} }
......
...@@ -16,6 +16,7 @@ import java.util.Map; ...@@ -16,6 +16,7 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVMapConcurrent; import org.h2.mvstore.MVMapConcurrent;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
...@@ -43,12 +44,45 @@ public class TestConcurrent extends TestMVStore { ...@@ -43,12 +44,45 @@ public class TestConcurrent extends TestMVStore {
FileUtils.deleteRecursive(getBaseDir(), true); FileUtils.deleteRecursive(getBaseDir(), true);
FileUtils.createDirectories(getBaseDir()); FileUtils.createDirectories(getBaseDir());
testConcurrentStoreAndClose();
testConcurrentOnlineBackup(); testConcurrentOnlineBackup();
testConcurrentMap(); testConcurrentMap();
testConcurrentIterate(); testConcurrentIterate();
testConcurrentWrite(); testConcurrentWrite();
testConcurrentRead(); testConcurrentRead();
} }
private void testConcurrentStoreAndClose() throws InterruptedException {
String fileName = getBaseDir() + "/testConcurrentStoreAndClose.h3";
final MVStore s = openStore(fileName);
Task task = new Task() {
@Override
public void call() throws Exception {
int x = 0;
while (!stop) {
s.setStoreVersion(x++);
s.store();
}
}
};
task.execute();
Thread.sleep(1);
try {
s.close();
// sometimes closing works, in which case
// storing fails at some point
Thread.sleep(1000);
Exception e = task.getException();
assertEquals(DataUtils.ERROR_CLOSED,
DataUtils.getErrorCode(e.getMessage()));
} catch (IllegalStateException e) {
// sometimes storing works, in which case
// closing fails
assertEquals(DataUtils.ERROR_WRITING_FAILED,
DataUtils.getErrorCode(e.getMessage()));
}
s.close();
}
/** /**
* Test the concurrent map implementation. * Test the concurrent map implementation.
...@@ -105,8 +139,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -105,8 +139,7 @@ public class TestConcurrent extends TestMVStore {
} }
private void testConcurrentOnlineBackup() throws Exception { private void testConcurrentOnlineBackup() throws Exception {
// need to use NIO because we mix absolute and relative operations String fileName = getBaseDir() + "/onlineBackup.h3";
String fileName = "nio:" + getBaseDir() + "/onlineBackup.h3";
String fileNameRestore = getBaseDir() + "/onlineRestore.h3"; String fileNameRestore = getBaseDir() + "/onlineRestore.h3";
final MVStore s = openStore(fileName); final MVStore s = openStore(fileName);
final MVMap<Integer, byte[]> map = s.openMap("test"); final MVMap<Integer, byte[]> map = s.openMap("test");
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
*/ */
package org.h2.test.store; package org.h2.test.store;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler; import java.lang.Thread.UncaughtExceptionHandler;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
...@@ -17,9 +16,9 @@ import java.util.concurrent.atomic.AtomicReference; ...@@ -17,9 +16,9 @@ import java.util.concurrent.atomic.AtomicReference;
import org.h2.mvstore.Cursor; import org.h2.mvstore.Cursor;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.FileStore;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.mvstore.MVStoreTool;
import org.h2.mvstore.OffHeapStore; import org.h2.mvstore.OffHeapStore;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType; import org.h2.mvstore.type.ObjectDataType;
...@@ -49,6 +48,7 @@ public class TestMVStore extends TestBase { ...@@ -49,6 +48,7 @@ public class TestMVStore extends TestBase {
public void test() throws Exception { public void test() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true); FileUtils.deleteRecursive(getBaseDir(), true);
FileUtils.createDirectories(getBaseDir()); FileUtils.createDirectories(getBaseDir());
testRemoveMap();
testIsEmpty(); testIsEmpty();
testOffHeapStorage(); testOffHeapStorage();
testNewerWriteVersion(); testNewerWriteVersion();
...@@ -66,10 +66,7 @@ public class TestMVStore extends TestBase { ...@@ -66,10 +66,7 @@ public class TestMVStore extends TestBase {
testCacheSize(); testCacheSize();
testConcurrentOpen(); testConcurrentOpen();
testFileHeader(); testFileHeader();
testFileHeaderCorruption();
int todoFixTestCase;
// testFileHeaderCorruption();
testIndexSkip(); testIndexSkip();
testMinMaxNextKey(); testMinMaxNextKey();
testStoreVersion(); testStoreVersion();
...@@ -100,6 +97,30 @@ public class TestMVStore extends TestBase { ...@@ -100,6 +97,30 @@ public class TestMVStore extends TestBase {
testLargerThan2G(); testLargerThan2G();
} }
private void testRemoveMap() throws Exception {
String fileName = getBaseDir() + "/testCloseMap.h3";
FileUtils.delete(fileName);
MVStore s = new MVStore.Builder().
fileName(fileName).
open();
MVMap<Integer, Integer> map;
map = s.openMap("data");
map.put(1, 1);
assertEquals(1, map.get(1).intValue());
s.store();
map.removeMap();
s.store();
map = s.openMap("data");
assertTrue(map.isEmpty());
map.put(2, 2);
s.store();
s.close();
}
private void testIsEmpty() throws Exception { private void testIsEmpty() throws Exception {
MVStore s = new MVStore.Builder(). MVStore s = new MVStore.Builder().
pageSplitSize(50). pageSplitSize(50).
...@@ -600,25 +621,34 @@ public class TestMVStore extends TestBase { ...@@ -600,25 +621,34 @@ public class TestMVStore extends TestBase {
private void testFileHeaderCorruption() throws Exception { private void testFileHeaderCorruption() throws Exception {
String fileName = getBaseDir() + "/testFileHeader.h3"; String fileName = getBaseDir() + "/testFileHeader.h3";
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
s.setRetentionTime(10); s.setRetentionTime(0);
MVMap<Integer, Integer> map = s.openMap("test"); MVMap<Integer, byte[]> map;
for (int i = 0; i < 5; i++) { map = s.openMap("test");
s.setStoreVersion(i); map.put(0, new byte[100]);
for (int i = 0; i < 10; i++) {
map = s.openMap("test" + i);
map.put(0, new byte[1000]);
s.store(); s.store();
} }
// ensure the oldest chunks can be overwritten FileStore fs = s.getFileStore();
Thread.sleep(11); long size = fs.getFile().size();
s.compact(50); for (int i = 0; i < 10; i++) {
map.put(10, 100); map = s.openMap("test" + i);
s.store(); map.removeMap();
FilePath f = FilePath.get(fileName); s.store();
s.commit();
s.compact(100);
if (fs.getFile().size() <= size) {
break;
}
}
s.close(); s.close();
FilePath f = FilePath.get(fileName);
int blockSize = 4 * 1024; int blockSize = 4 * 1024;
// test corrupt file headers // test corrupt file headers
for (int i = 0; i <= blockSize; i += blockSize) { for (int i = 0; i <= blockSize; i += blockSize) {
FileChannel fc = f.open("rw"); FileChannel fc = f.open("rw");
MVStoreTool.dump(fileName, new PrintWriter(System.out));
if (i == 0) { if (i == 0) {
// corrupt the last block (the end header) // corrupt the last block (the end header)
fc.truncate(fc.size() - 4096); fc.truncate(fc.size() - 4096);
...@@ -635,14 +665,12 @@ MVStoreTool.dump(fileName, new PrintWriter(System.out)); ...@@ -635,14 +665,12 @@ MVStoreTool.dump(fileName, new PrintWriter(System.out));
fc.write(buff, i); fc.write(buff, i);
fc.close(); fc.close();
MVStoreTool.dump(fileName, new PrintWriter(System.out));
if (i == 0) { if (i == 0) {
// if the first header is corrupt, the second // if the first header is corrupt, the second
// header should be used // header should be used
s = openStore(fileName); s = openStore(fileName);
map = s.openMap("test"); map = s.openMap("test");
assertEquals(100, map.get(10).intValue()); assertEquals(100, map.get(0).length);
s.close(); s.close();
} else { } else {
// both headers are corrupt // both headers are corrupt
...@@ -906,7 +934,6 @@ MVStoreTool.dump(fileName, new PrintWriter(System.out)); ...@@ -906,7 +934,6 @@ MVStoreTool.dump(fileName, new PrintWriter(System.out));
assertEquals("Hello", oldMap.get(1)); assertEquals("Hello", oldMap.get(1));
// System.out.println(oldMap.get(2)); // System.out.println(oldMap.get(2));
assertEquals("World", oldMap.get(2)); assertEquals("World", oldMap.get(2));
oldMap.close();
// print the newest version ("Hi") // print the newest version ("Hi")
// System.out.println(map.get(1)); // System.out.println(map.get(1));
......
...@@ -170,8 +170,10 @@ public class TestRandomMapOps extends TestBase { ...@@ -170,8 +170,10 @@ public class TestRandomMapOps extends TestBase {
} }
private static MVStore openStore(String fileName) { private static MVStore openStore(String fileName) {
return new MVStore.Builder().fileName(fileName). MVStore s = new MVStore.Builder().fileName(fileName).
pageSplitSize(50).writeDelay(0).open(); pageSplitSize(50).writeDelay(0).open();
s.setRetentionTime(0);
return s;
} }
private void assertEqualsMapValues(byte[] x, byte[] y) { private void assertEqualsMapValues(byte[] x, byte[] y) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论