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

MVStore: various changes (store temporary, store committed and rollback on close)

上级 cfb047bd
......@@ -72,7 +72,7 @@ public class Chunk {
long version;
/**
* When this chunk was created, in seconds after the store was created.
* When this chunk was created, in milliseconds after the store was created.
*/
long time;
......
......@@ -48,8 +48,10 @@ public class MVMap<K, V> extends AbstractMap<K, V>
private boolean closed;
private boolean readOnly;
/**
* This flag is set during a write operation to the tree.
*/
private volatile boolean writing;
private volatile int writeCount;
protected MVMap(DataType keyType, DataType valueType) {
this.keyType = keyType;
......@@ -918,26 +920,21 @@ public class MVMap<K, V> extends AbstractMap<K, V>
}
/**
* This method is called after writing to the map.
* This method is called after writing to the map (whether or not the write
* operation was successful).
*/
protected void afterWrite() {
writeCount++;
writing = false;
}
void waitUntilWritten(long version) {
if (root.getVersion() < version) {
// a write will create a new version
return;
}
// wait until writing is done,
// but only for the current write operation
// a bit like a spin lock
int w = writeCount;
while (writing) {
if (writeCount > w) {
return;
}
/**
* If there is a concurrent update to the given version, wait until it is
* finished.
*
* @param root the root page
*/
protected void waitUntilWritten(Page root) {
while (writing && root == this.root) {
Thread.yield();
}
}
......@@ -967,7 +964,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
/**
* Remove the given page (make the space available).
*
* @param p the page
* @param pos the position of the page to remove
*/
protected void removePage(long pos) {
store.removePage(this, pos);
......
......@@ -52,7 +52,7 @@ public class MVMapConcurrent<K, V> extends MVMap<K, V> {
}
}
void waitUntilWritten(long version) {
protected void waitUntilWritten(Page root) {
// no need to wait
}
......
......@@ -42,6 +42,8 @@ public class TestMVStore extends TestBase {
FileUtils.deleteRecursive(getBaseDir(), true);
FileUtils.createDirectories(getBaseDir());
testWriteBuffer();
testWriteDelay();
testEncryptedFile();
testFileFormatChange();
testRecreateMap();
......@@ -77,6 +79,107 @@ public class TestMVStore extends TestBase {
testSimple();
}
private void testWriteBuffer() throws IOException {
String fileName = getBaseDir() + "/testAutoStoreBuffer.h3";
FileUtils.delete(fileName);
MVStore s;
MVMap<Integer, byte[]> m;
byte[] data = new byte[1000];
long lastSize = 0;
int len = 1000;
for (int bs = 0; bs <= 1; bs++) {
s = new MVStore.Builder().
fileName(fileName).
writeBufferSize(bs).
open();
m = s.openMap("data");
for (int i = 0; i < len; i++) {
m.put(i, data);
}
long size = s.getFile().size();
assertTrue("last:" + lastSize + " now: " + size, size > lastSize);
lastSize = size;
s.close();
}
s = new MVStore.Builder().
fileName(fileName).
open();
m = s.openMap("data");
assertFalse(m.containsKey(1));
m.put(1, data);
s.commit();
m.put(2, data);
s.close();
s = new MVStore.Builder().
fileName(fileName).
open();
m = s.openMap("data");
assertTrue(m.containsKey(1));
assertFalse(m.containsKey(2));
s.close();
FileUtils.delete(fileName);
}
private void testWriteDelay() throws InterruptedException {
String fileName = getBaseDir() + "/testUndoTempStore.h3";
FileUtils.delete(fileName);
MVStore s;
MVMap<Integer, String> m;
s = new MVStore.Builder().
writeDelay(1).
fileName(fileName).
open();
m = s.openMap("data");
m.put(1, "Hello");
s.store();
long v = s.getCurrentVersion();
m.put(2, "World");
Thread.sleep(5);
// must not store, as nothing has been committed yet
assertEquals(v, s.getCurrentVersion());
s.commit();
m.put(3, "!");
for (int i = 100; i > 0; i--) {
if (s.getCurrentVersion() > v) {
break;
}
if (i < 10) {
fail();
}
Thread.sleep(1);
}
s.close();
s = new MVStore.Builder().
fileName(fileName).
open();
m = s.openMap("data");
assertEquals("Hello", m.get(1));
assertEquals("World", m.get(2));
assertFalse(m.containsKey(3));
String data = new String(new char[1000]).replace((char) 0, 'x');
for (int i = 0; i < 1000; i++) {
m.put(i, data);
}
s.close();
s = new MVStore.Builder().
fileName(fileName).
open();
m = s.openMap("data");
assertEquals("Hello", m.get(1));
assertEquals("World", m.get(2));
assertFalse(m.containsKey(3));
s.close();
FileUtils.delete(fileName);
}
private void testEncryptedFile() {
String fileName = getBaseDir() + "/testEncryptedFile.h3";
FileUtils.delete(fileName);
......@@ -175,6 +278,8 @@ public class TestMVStore extends TestBase {
assertEquals("world", map.getName());
s.rollbackTo(old);
assertEquals("hello", map.getName());
s.rollbackTo(0);
assertTrue(map.isClosed());
s.close();
}
......@@ -211,7 +316,7 @@ public class TestMVStore extends TestBase {
for (int cacheSize = 0; cacheSize <= 6; cacheSize += 4) {
s = new MVStore.Builder().
fileName(fileName).
cacheSizeMB(1 + 3 * cacheSize).open();
cacheSize(1 + 3 * cacheSize).open();
map = s.openMap("test");
for (int i = 0; i < 1024; i += 128) {
for (int j = 0; j < i; j++) {
......@@ -253,11 +358,11 @@ public class TestMVStore extends TestBase {
private void testFileHeader() {
String fileName = getBaseDir() + "/testFileHeader.h3";
MVStore s = openStore(fileName);
long time = System.currentTimeMillis() / 1000;
long time = System.currentTimeMillis();
assertEquals("3", s.getFileHeader().get("H"));
long creationTime = Long.parseLong(s.getFileHeader()
.get("creationTime"));
assertTrue(Math.abs(time - creationTime) < 5);
assertTrue(Math.abs(time - creationTime) < 100);
s.getFileHeader().put("test", "123");
MVMap<Integer, Integer> map = s.openMap("test");
map.put(10, 100);
......@@ -274,6 +379,7 @@ public class TestMVStore extends TestBase {
MVMap<Integer, Integer> map = s.openMap("test");
map.put(10, 100);
FilePath f = FilePath.get(s.getFileName());
s.store();
s.close();
int blockSize = 4 * 1024;
// test corrupt file headers
......@@ -299,6 +405,7 @@ public class TestMVStore extends TestBase {
// header should be used
s = openStore(fileName);
map = s.openMap("test");
assertEquals(100, map.get(10).intValue());
s.close();
} else {
// both headers are corrupt
......@@ -710,11 +817,11 @@ public class TestMVStore extends TestBase {
FileUtils.delete(fileName);
MVMap<String, String> meta;
MVStore s = openStore(fileName);
assertEquals(45, s.getRetentionTime());
assertEquals(45000, s.getRetentionTime());
s.setRetentionTime(0);
assertEquals(0, s.getRetentionTime());
s.setRetentionTime(45);
assertEquals(45, s.getRetentionTime());
s.setRetentionTime(45000);
assertEquals(45000, s.getRetentionTime());
assertEquals(0, s.getCurrentVersion());
assertFalse(s.hasUnsavedChanges());
MVMap<String, String> m = s.openMap("data");
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.store;
import org.h2.test.TestBase;
/**
* Test using volatile fields to ensure we don't read from a version that is
* concurrently written to.
*/
public class TestSpinLock extends TestBase {
/**
* The version to use for writing.
*/
volatile int writeVersion;
/**
* The current data object.
*/
volatile Data data = new Data(0, null);
/**
* 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 {
final TestSpinLock obj = new TestSpinLock();
Thread t = new Thread() {
public void run() {
while (!isInterrupted()) {
for (int i = 0; i < 10000; i++) {
Data d = obj.copyOnWrite();
obj.data = d;
d.write(i);
d.writing = false;
}
}
}
};
t.start();
try {
for (int i = 0; i < 100000; i++) {
Data d = obj.getImmutable();
int z = d.x + d.y;
if (z != 0) {
String error = i + " result: " + z + " now: " + d.x + " "
+ d.y;
System.out.println(error);
throw new Exception(error);
}
}
} finally {
t.interrupt();
t.join();
}
}
/**
* Clone the data object if necessary (if the write version is newer than
* the current version).
*
* @return the data object
*/
Data copyOnWrite() {
Data d = data;
d.writing = true;
int w = writeVersion;
if (w <= data.version) {
return d;
}
Data d2 = new Data(w, data);
d2.writing = true;
d.writing = false;
return d2;
}
/**
* Get an immutable copy of the data object.
*
* @return the immutable object
*/
private Data getImmutable() {
Data d = data;
++writeVersion;
// wait until writing is done,
// but only for the current write operation:
// a bit like a spin lock
while (d.writing) {
// Thread.yield() is not required, specially
// if there are multiple cores
// but getImmutable() doesn't
// need to be that fast actually
Thread.yield();
}
return d;
}
/**
* The data class - represents the root page.
*/
static class Data {
/**
* The version.
*/
final int version;
/**
* The values.
*/
int x, y;
/**
* Whether a write operation is in progress.
*/
volatile boolean writing;
/**
* Create a copy of the data.
*
* @param version the new version
* @param old the old data or null
*/
Data(int version, Data old) {
this.version = version;
if (old != null) {
this.x = old.x;
this.y = old.y;
}
}
/**
* Write to the fields in an unsynchronized way.
*
* @param value the new value
*/
void write(int value) {
this.x = value;
this.y = -value;
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论