提交 53fd89a8 authored 作者: Thomas Mueller's avatar Thomas Mueller

MVTableEngine

上级 f26792c2
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
package org.h2.engine; package org.h2.engine;
import java.beans.ExceptionListener;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -273,6 +274,15 @@ public class Database implements DataHandler { ...@@ -273,6 +274,15 @@ public class Database implements DataHandler {
public void setMvStore(MVTableEngine.Store mvStore) { public void setMvStore(MVTableEngine.Store mvStore) {
this.mvStore = mvStore; this.mvStore = mvStore;
mvStore.getStore().setBackgroundExceptionListener(new ExceptionListener() {
@Override
public void exceptionThrown(Exception e) {
setBackgroundException(DbException.convert(e));
}
});
} }
/** /**
...@@ -1086,6 +1096,7 @@ public class Database implements DataHandler { ...@@ -1086,6 +1096,7 @@ public class Database implements DataHandler {
if (closing) { if (closing) {
return; return;
} }
throwLastBackgroundException();
if (fileLockMethod == FileLock.LOCK_SERIALIZED && !reconnectChangePending) { if (fileLockMethod == FileLock.LOCK_SERIALIZED && !reconnectChangePending) {
// another connection may have written something - don't write // another connection may have written something - don't write
try { try {
...@@ -1799,6 +1810,17 @@ public class Database implements DataHandler { ...@@ -1799,6 +1810,17 @@ public class Database implements DataHandler {
* @param session the session * @param session the session
*/ */
synchronized void commit(Session session) { synchronized void commit(Session session) {
throwLastBackgroundException();
if (readOnly) {
return;
}
if (pageStore != null) {
pageStore.commit(session);
}
session.setAllCommitted();
}
private void throwLastBackgroundException() {
if (backgroundException != null) { if (backgroundException != null) {
// we don't care too much about concurrency here, // we don't care too much about concurrency here,
// we just want to make sure the exception is _normally_ // we just want to make sure the exception is _normally_
...@@ -1809,13 +1831,16 @@ public class Database implements DataHandler { ...@@ -1809,13 +1831,16 @@ public class Database implements DataHandler {
throw b; throw b;
} }
} }
if (readOnly) {
return;
} }
if (pageStore != null) {
pageStore.commit(session); public void setBackgroundException(DbException e) {
if (backgroundException == null) {
backgroundException = e;
TraceSystem t = getTraceSystem();
if (t != null) {
t.getTrace(Trace.DATABASE).error(e, "flush");
}
} }
session.setAllCommitted();
} }
/** /**
......
...@@ -116,7 +116,7 @@ public class Chunk { ...@@ -116,7 +116,7 @@ public class Chunk {
} }
/** /**
* Write the header. * Write the chunk header.
* *
* @param buff the target buffer * @param buff the target buffer
*/ */
......
/*
* Copyright 2004-2013 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.mvstore;
import java.util.BitSet;
import org.h2.util.MathUtils;
/**
* A free space bit set.
*/
public class FreeSpaceBitSet {
/**
* The first usable block.
*/
private final int firstFreeBlock;
/**
* The block size in bytes.
*/
private final int blockSize;
/**
* The bit set.
*/
private final BitSet set = new BitSet();
/**
* Create a new free space map.
*
* @param firstFreeBlock the first free block
* @param blockSize the block size
*/
public FreeSpaceBitSet(int firstFreeBlock, int blockSize) {
this.firstFreeBlock = firstFreeBlock;
this.blockSize = blockSize;
clear();
}
/**
* Reset the list.
*/
public synchronized void clear() {
set.clear();
set.set(0, firstFreeBlock);
}
/**
* Check whether one of the blocks is in use.
*
* @param pos the position in bytes
* @param length the number of bytes
* @return true if a block is in use
*/
public synchronized boolean isUsed(long pos, int length) {
int start = getBlock(pos);
int blocks = getBlockCount(length);
for (int i = start; i < start + blocks; i++) {
if (!set.get(i)) {
return false;
}
}
return true;
}
/**
* Check whether one of the blocks is free.
*
* @param pos the position in bytes
* @param length the number of bytes
* @return true if a block is free
*/
public synchronized boolean isFree(long pos, int length) {
int start = getBlock(pos);
int blocks = getBlockCount(length);
for (int i = start; i < start + blocks; i++) {
if (set.get(i)) {
return false;
}
}
return true;
}
/**
* Allocate a number of blocks and mark them as used.
*
* @param length the number of bytes to allocate
* @return the start position in bytes
*/
public synchronized long allocate(int length) {
int blocks = getBlockCount(length);
for (int i = 0;;) {
int start = set.nextClearBit(i);
int end = set.nextSetBit(start + 1);
if (end < 0 || end - start >= blocks) {
set.set(start, start + blocks);
return getPos(start);
}
i = end;
}
}
/**
* Mark the space as in use.
*
* @param pos the position in bytes
* @param length the number of bytes
*/
public synchronized void markUsed(long pos, int length) {
int start = getBlock(pos);
int blocks = getBlockCount(length);
set.set(start, start + blocks);
}
/**
* Mark the space as free.
*
* @param pos the position in bytes
* @param length the number of bytes
*/
public synchronized void free(long pos, int length) {
int start = getBlock(pos);
int blocks = getBlockCount(length);
set.clear(start, start + blocks);
}
private long getPos(int block) {
return (long) block * (long) blockSize;
}
private int getBlock(long pos) {
return (int) (pos / blockSize);
}
private int getBlockCount(int length) {
return MathUtils.roundUpInt(length, blockSize) / blockSize;
}
@Override
public String toString() {
StringBuilder buff = new StringBuilder("[");
for (int i = 0;;) {
if (i > 0) {
buff.append(", ");
}
int start = set.nextClearBit(i);
buff.append(start).append('-');
int end = set.nextSetBit(start + 1);
if (end < 0) {
break;
}
buff.append(end - 1);
i = end + 1;
}
return buff.append(']').toString();
}
}
\ No newline at end of file
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
package org.h2.mvstore; package org.h2.mvstore;
import java.beans.ExceptionListener;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
...@@ -104,7 +105,6 @@ MVStore: ...@@ -104,7 +105,6 @@ MVStore:
- maybe rename 'rollback' to 'revert' to distinguish from transactions - maybe rename 'rollback' to 'revert' to distinguish from transactions
- support other compression algorithms (deflate, LZ4,...) - support other compression algorithms (deflate, LZ4,...)
- only retain the last version, unless explicitly set (setRetainVersion) - only retain the last version, unless explicitly set (setRetainVersion)
- unit test for the FreeSpaceList; maybe find a simpler implementation
- support opening (existing) maps by id - support opening (existing) maps by id
- more consistent null handling (keys/values sometimes may be null) - more consistent null handling (keys/values sometimes may be null)
- logging mechanism, specially for operations in a background thread - logging mechanism, specially for operations in a background thread
...@@ -161,11 +161,12 @@ public class MVStore { ...@@ -161,11 +161,12 @@ public class MVStore {
*/ */
private final ConcurrentHashMap<Integer, Chunk> chunks = private final ConcurrentHashMap<Integer, Chunk> chunks =
new ConcurrentHashMap<Integer, Chunk>(); new ConcurrentHashMap<Integer, Chunk>();
/** /**
* The list of free spaces between the chunks. * The free spaces between the chunks. The first block to use is block 2
* (the first two blocks are the file header).
*/ */
private FreeSpaceList freeSpaceList = new FreeSpaceList(); private FreeSpaceBitSet freeSpace = new FreeSpaceBitSet(2, BLOCK_SIZE);
/** /**
* The map of temporarily removed pages. The key is the unsaved version, the * The map of temporarily removed pages. The key is the unsaved version, the
...@@ -235,6 +236,8 @@ public class MVStore { ...@@ -235,6 +236,8 @@ public class MVStore {
*/ */
private int writeDelay = 1000; private int writeDelay = 1000;
private ExceptionListener backgroundExceptionListener;
MVStore(HashMap<String, Object> config) { MVStore(HashMap<String, Object> config) {
String f = (String) config.get("fileName"); String f = (String) config.get("fileName");
if (f != null && f.indexOf(':') < 0) { if (f != null && f.indexOf(':') < 0) {
...@@ -598,12 +601,13 @@ public class MVStore { ...@@ -598,12 +601,13 @@ public class MVStore {
} }
} }
// rebuild the free space list // rebuild the free space list
freeSpaceList.clear(); freeSpace.clear();
for (Chunk c : chunks.values()) { for (Chunk c : chunks.values()) {
if (c.start == Long.MAX_VALUE) { if (c.start == Long.MAX_VALUE) {
continue; continue;
} }
freeSpaceList.markUsed(c); int len = MathUtils.roundUpInt(c.length, BLOCK_SIZE) + BLOCK_SIZE;
freeSpace.markUsed(c.start, len);
} }
} }
...@@ -710,6 +714,7 @@ public class MVStore { ...@@ -710,6 +714,7 @@ public class MVStore {
/** /**
* Close the file and the store, without writing anything. * Close the file and the store, without writing anything.
* This will stop the background thread.
*/ */
public void closeImmediately() { public void closeImmediately() {
closeFile(false); closeFile(false);
...@@ -743,7 +748,7 @@ public class MVStore { ...@@ -743,7 +748,7 @@ public class MVStore {
} }
meta = null; meta = null;
chunks.clear(); chunks.clear();
freeSpaceList.clear(); freeSpace.clear();
cache.clear(); cache.clear();
maps.clear(); maps.clear();
} catch (Exception e) { } catch (Exception e) {
...@@ -927,6 +932,8 @@ public class MVStore { ...@@ -927,6 +932,8 @@ public class MVStore {
int chunkLength = buff.position(); int chunkLength = buff.position();
// round to the next block,
// and one additional block for the file header
int length = MathUtils.roundUpInt(chunkLength, BLOCK_SIZE) + BLOCK_SIZE; int length = MathUtils.roundUpInt(chunkLength, BLOCK_SIZE) + BLOCK_SIZE;
if (length > buff.capacity()) { if (length > buff.capacity()) {
buff = DataUtils.ensureCapacity(buff, length - buff.capacity()); buff = DataUtils.ensureCapacity(buff, length - buff.capacity());
...@@ -934,17 +941,22 @@ public class MVStore { ...@@ -934,17 +941,22 @@ public class MVStore {
buff.limit(length); buff.limit(length);
long fileSizeUsed = getFileSizeUsed(); long fileSizeUsed = getFileSizeUsed();
long filePos = reuseSpace ? allocateChunk(length) : fileSizeUsed; long filePos;
if (reuseSpace) {
filePos = freeSpace.allocate(length);
} else {
filePos = fileSizeUsed;
freeSpace.markUsed(fileSizeUsed, length);
}
boolean storeAtEndOfFile = filePos + length >= fileSizeUsed; boolean storeAtEndOfFile = filePos + length >= fileSizeUsed;
// free up the space of unused chunks now // free up the space of unused chunks now
for (Chunk x : removedChunks) { for (Chunk x : removedChunks) {
freeSpaceList.markFree(x); freeSpace.free(x.start, x.length);
} }
c.start = filePos; c.start = filePos;
c.length = chunkLength; c.length = chunkLength;
freeSpaceList.markUsed(c);
c.metaRootPos = meta.getRoot().getPos(); c.metaRootPos = meta.getRoot().getPos();
buff.position(0); buff.position(0);
c.writeHeader(buff); c.writeHeader(buff);
...@@ -1107,10 +1119,6 @@ public class MVStore { ...@@ -1107,10 +1119,6 @@ public class MVStore {
return size; return size;
} }
private long allocateChunk(long length) {
return ((long) freeSpaceList.allocatePages(length)) * BLOCK_SIZE;
}
/** /**
* Check whether there are any unsaved changes. * Check whether there are any unsaved changes.
* *
...@@ -1450,6 +1458,20 @@ public class MVStore { ...@@ -1450,6 +1458,20 @@ public class MVStore {
return v; return v;
} }
/**
* Set the listener to be used for exceptions that occur in the background thread.
*
* @param backgroundExceptionListener the listener
*/
public void setBackgroundExceptionListener(
ExceptionListener backgroundExceptionListener) {
this.backgroundExceptionListener = backgroundExceptionListener;
}
public ExceptionListener getBackgroundExceptionListener() {
return backgroundExceptionListener;
}
/** /**
* Check whether all data can be read from this version. This requires that * Check whether all data can be read from this version. This requires that
* all chunks referenced by this version are still available (not * all chunks referenced by this version are still available (not
...@@ -1561,7 +1583,7 @@ public class MVStore { ...@@ -1561,7 +1583,7 @@ public class MVStore {
} }
meta.clear(); meta.clear();
chunks.clear(); chunks.clear();
freeSpaceList.clear(); freeSpace.clear();
maps.clear(); maps.clear();
synchronized (freedPages) { synchronized (freedPages) {
freedPages.clear(); freedPages.clear();
...@@ -1594,7 +1616,7 @@ public class MVStore { ...@@ -1594,7 +1616,7 @@ public class MVStore {
loadFromFile = true; loadFromFile = true;
do { do {
last = chunks.remove(lastChunkId); last = chunks.remove(lastChunkId);
freeSpaceList.markFree(last); freeSpace.free(last.start, last.length);
lastChunkId--; lastChunkId--;
} while (last.version > version && chunks.size() > 0); } while (last.version > version && chunks.size() > 0);
rootChunkStart = last.start; rootChunkStart = last.start;
...@@ -1771,7 +1793,13 @@ public class MVStore { ...@@ -1771,7 +1793,13 @@ public class MVStore {
if (time <= lastStoreTime + writeDelay) { if (time <= lastStoreTime + writeDelay) {
return; return;
} }
try {
store(true); store(true);
} catch (Exception e) {
if (backgroundExceptionListener != null) {
backgroundExceptionListener.exceptionThrown(e);
}
}
} }
/** /**
...@@ -1780,8 +1808,10 @@ public class MVStore { ...@@ -1780,8 +1808,10 @@ public class MVStore {
* @param mb the cache size in MB. * @param mb the cache size in MB.
*/ */
public void setCacheSize(long mb) { public void setCacheSize(long mb) {
if (cache != null) {
cache.setMaxMemory(mb * 1024 * 1024); cache.setMaxMemory(mb * 1024 * 1024);
} }
}
public boolean isReadOnly() { public boolean isReadOnly() {
return readOnly; return readOnly;
...@@ -1848,13 +1878,7 @@ public class MVStore { ...@@ -1848,13 +1878,7 @@ public class MVStore {
// ignore // ignore
} }
} }
try {
store.storeInBackground(); store.storeInBackground();
} catch (Exception e) {
int todo;
// TODO throw the exception in the main thread
// at some point, or log the problem
}
} }
} }
......
...@@ -110,6 +110,7 @@ import org.h2.test.store.TestCacheLIRS; ...@@ -110,6 +110,7 @@ import org.h2.test.store.TestCacheLIRS;
import org.h2.test.store.TestCacheLongKeyLIRS; import org.h2.test.store.TestCacheLongKeyLIRS;
import org.h2.test.store.TestConcurrent; import org.h2.test.store.TestConcurrent;
import org.h2.test.store.TestDataUtils; import org.h2.test.store.TestDataUtils;
import org.h2.test.store.TestFreeSpace;
import org.h2.test.store.TestMVRTree; import org.h2.test.store.TestMVRTree;
import org.h2.test.store.TestMVStore; import org.h2.test.store.TestMVStore;
import org.h2.test.store.TestMVTableEngine; import org.h2.test.store.TestMVTableEngine;
...@@ -448,10 +449,17 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -448,10 +449,17 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
} else { } else {
test.runTests(); test.runTests();
Profiler prof = new Profiler(); Profiler prof = new Profiler();
prof.depth = 4; prof.depth = 8;
prof.interval = 1; prof.interval = 1;
prof.startCollecting(); prof.startCollecting();
if (test.mvStore) {
TestPerformance.main("-init", "-db", "9", "-size", "1000");
TestPerformance.main("-init", "-db", "1", "-size", "1000");
TestPerformance.main("-init", "-db", "9", "-size", "1000");
TestPerformance.main("-init", "-db", "1", "-size", "1000");
} else {
TestPerformance.main("-init", "-db", "1"); TestPerformance.main("-init", "-db", "1");
}
prof.stopCollecting(); prof.stopCollecting();
System.out.println(prof.getTop(3)); System.out.println(prof.getTop(3));
// Recover.execute("data", null); // Recover.execute("data", null);
...@@ -694,6 +702,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -694,6 +702,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestCacheLongKeyLIRS().runTest(this); new TestCacheLongKeyLIRS().runTest(this);
new TestConcurrent().runTest(this); new TestConcurrent().runTest(this);
new TestDataUtils().runTest(this); new TestDataUtils().runTest(this);
new TestFreeSpace().runTest(this);
new TestMVRTree().runTest(this); new TestMVRTree().runTest(this);
new TestMVStore().runTest(this); new TestMVStore().runTest(this);
new TestMVTableEngine().runTest(this); new TestMVTableEngine().runTest(this);
......
db1 = H2, org.h2.Driver, jdbc:h2:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa db1 = H2, org.h2.Driver, jdbc:h2:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa
db9 = H2 (MVStore), org.h2.Driver, jdbc:h2:data/test;MV_STORE=TRUE;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa
#xdb1 = H2, org.h2.Driver, jdbc:h2:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine, sa, sa #xdb1 = H2, org.h2.Driver, jdbc:h2:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine, sa, sa
......
...@@ -4,30 +4,37 @@ ...@@ -4,30 +4,37 @@
* (http://h2database.com/html/license.html). * (http://h2database.com/html/license.html).
* Initial Developer: H2 Group * Initial Developer: H2 Group
*/ */
package org.h2.mvstore; package org.h2.test.store;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.h2.mvstore.DataUtils;
import org.h2.util.MathUtils;
/** /**
* A list that maintains ranges of free space (in pages) in a file. * A list that maintains ranges of free space (in blocks).
*/ */
public class FreeSpaceList { public class FreeSpaceList {
/** /**
* The first 2 pages are occupied by the file header. * The first usable block.
*/ */
private static final int FIRST_FREE_PAGE = 2; private final int firstFreeBlock;
/** /**
* The maximum number of pages. Smaller than than MAX_VALUE to avoid * The block size in bytes.
* overflow errors during arithmetic operations.
*/ */
private static final int MAX_PAGE_COUNT = Integer.MAX_VALUE / 2; private final int blockSize;
private List<PageRange> freeSpaceList = new ArrayList<PageRange>(); private List<BlockRange> freeSpaceList = new ArrayList<BlockRange>();
public FreeSpaceList() { public FreeSpaceList(int firstFreeBlock, int blockSize) {
this.firstFreeBlock = firstFreeBlock;
if (Integer.bitCount(blockSize) != 1) {
throw DataUtils.newIllegalArgumentException("Block size is not a power of 2");
}
this.blockSize = blockSize;
clear(); clear();
} }
...@@ -36,20 +43,23 @@ public class FreeSpaceList { ...@@ -36,20 +43,23 @@ public class FreeSpaceList {
*/ */
public synchronized void clear() { public synchronized void clear() {
freeSpaceList.clear(); freeSpaceList.clear();
freeSpaceList.add(new PageRange(FIRST_FREE_PAGE, MAX_PAGE_COUNT)); freeSpaceList.add(new BlockRange(firstFreeBlock,
Integer.MAX_VALUE - firstFreeBlock));
} }
/** /**
* Allocate a number of pages. * Allocate a number of blocks and mark them as used.
* *
* @param length the number of bytes to allocate * @param length the number of bytes to allocate
* @return the position in pages * @return the start position in bytes
*/ */
public synchronized int allocatePages(long length) { public synchronized long allocate(int length) {
int required = (int) (length / MVStore.BLOCK_SIZE) + 1; int required = getBlockCount(length);
for (PageRange pr : freeSpaceList) { for (BlockRange pr : freeSpaceList) {
if (pr.length >= required) { if (pr.length >= required) {
return pr.start; int result = pr.start;
this.markUsed(pr.start * blockSize, length);
return result * blockSize;
} }
} }
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
...@@ -57,18 +67,13 @@ public class FreeSpaceList { ...@@ -57,18 +67,13 @@ public class FreeSpaceList {
"Could not find a free page to allocate"); "Could not find a free page to allocate");
} }
/** public synchronized void markUsed(long pos, int length) {
* Mark a chunk as used. int start = (int) (pos / blockSize);
* int required = getBlockCount(length);
* @param c the chunk BlockRange found = null;
*/
public synchronized void markUsed(Chunk c) {
int chunkStart = (int) (c.start / MVStore.BLOCK_SIZE);
int required = (int) ((c.start + c.length) / MVStore.BLOCK_SIZE) + 2 - chunkStart;
PageRange found = null;
int i = 0; int i = 0;
for (PageRange pr : freeSpaceList) { for (BlockRange pr : freeSpaceList) {
if (chunkStart >= pr.start && chunkStart < (pr.start + pr.length)) { if (start >= pr.start && start < (pr.start + pr.length)) {
found = pr; found = pr;
break; break;
} }
...@@ -77,52 +82,43 @@ public class FreeSpaceList { ...@@ -77,52 +82,43 @@ public class FreeSpaceList {
if (found == null) { if (found == null) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, DataUtils.ERROR_INTERNAL,
"Cannot find spot to mark chunk as used in free list: {0}", c); "Cannot find spot to mark as used in free list");
} }
if (chunkStart + required > found.start + found.length) { if (start + required > found.start + found.length) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, DataUtils.ERROR_INTERNAL,
"Chunk runs over edge of free space: {0}", c); "Runs over edge of free space");
} }
if (found.start == chunkStart) { if (found.start == start) {
// if the used-chunk is at the beginning of a free-space-range // if the used space is at the beginning of a free-space-range
found.start += required; found.start += required;
found.length -= required; found.length -= required;
if (found.length == 0) { if (found.length == 0) {
// if the free-space-range is now empty, remove it // if the free-space-range is now empty, remove it
freeSpaceList.remove(i); freeSpaceList.remove(i);
} }
} else if (found.start + found.length == chunkStart + required) { } else if (found.start + found.length == start + required) {
// if the used-chunk is at the end of a free-space-range // if the used space is at the end of a free-space-range
found.length -= required; found.length -= required;
if (found.length == 0) {
// if the free-space-range is now empty, remove it
freeSpaceList.remove(i);
}
} else { } else {
// it's in the middle, so split the existing entry // it's in the middle, so split the existing entry
int length1 = chunkStart - found.start; int length1 = start - found.start;
int start2 = chunkStart + required; int start2 = start + required;
int length2 = found.start + found.length - chunkStart - required; int length2 = found.start + found.length - start - required;
found.length = length1; found.length = length1;
PageRange newRange = new PageRange(start2, length2); BlockRange newRange = new BlockRange(start2, length2);
freeSpaceList.add(i + 1, newRange); freeSpaceList.add(i + 1, newRange);
} }
} }
/** public synchronized void free(long pos, int length) {
* Mark the chunk as free. int start = (int) (pos / blockSize);
* int required = getBlockCount(length);
* @param c the chunk BlockRange found = null;
*/
public synchronized void markFree(Chunk c) {
int chunkStart = (int) (c.start / MVStore.BLOCK_SIZE);
int required = (c.length / MVStore.BLOCK_SIZE) + 1;
PageRange found = null;
int i = 0; int i = 0;
for (PageRange pr : freeSpaceList) { for (BlockRange pr : freeSpaceList) {
if (pr.start > chunkStart) { if (pr.start > start) {
found = pr; found = pr;
break; break;
} }
...@@ -131,18 +127,18 @@ public class FreeSpaceList { ...@@ -131,18 +127,18 @@ public class FreeSpaceList {
if (found == null) { if (found == null) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, DataUtils.ERROR_INTERNAL,
"Cannot find spot to mark chunk as unused in free list: {0}", c); "Cannot find spot to mark as unused in free list");
} }
if (chunkStart + required + 1 == found.start) { if (start + required == found.start) {
// if the used-chunk is adjacent to the beginning of a // if the used space is adjacent to the beginning of a
// free-space-range // free-space-range
found.start = chunkStart; found.start = start;
found.length += required; found.length += required;
// compact: merge the previous entry into this one if // compact: merge the previous entry into this one if
// they are now adjacent // they are now adjacent
if (i > 0) { if (i > 0) {
PageRange previous = freeSpaceList.get(i - 1); BlockRange previous = freeSpaceList.get(i - 1);
if (previous.start + previous.length + 1 == found.start) { if (previous.start + previous.length == found.start) {
previous.length += found.length; previous.length += found.length;
freeSpaceList.remove(i); freeSpaceList.remove(i);
} }
...@@ -150,64 +146,60 @@ public class FreeSpaceList { ...@@ -150,64 +146,60 @@ public class FreeSpaceList {
return; return;
} }
if (i > 0) { if (i > 0) {
// if the used-chunk is adjacent to the end of a free-space-range // if the used space is adjacent to the end of a free-space-range
PageRange previous = freeSpaceList.get(i - 1); BlockRange previous = freeSpaceList.get(i - 1);
if (previous.start + previous.length + 1 == chunkStart) { if (previous.start + previous.length == start) {
previous.length += required; previous.length += required;
// compact: merge the next entry into this one if
// they are now adjacent
if (previous.start + previous.length + 1 == found.start) {
previous.length += found.length;
freeSpaceList.remove(i);
}
return; return;
} }
} }
// it is between 2 entries, so add a new one // it is between 2 entries, so add a new one
PageRange newRange = new PageRange(chunkStart, required); BlockRange newRange = new BlockRange(start, required);
freeSpaceList.add(i, newRange); freeSpaceList.add(i, newRange);
} }
@Override private int getBlockCount(int length) {
public String toString() { if (length <= 0) {
StringBuilder buff = new StringBuilder(); throw DataUtils.newIllegalStateException(
boolean first = true; DataUtils.ERROR_INTERNAL, "Free space invalid length");
for (PageRange r : freeSpaceList) {
if (first) {
first = false;
} else {
buff.append(", ");
} }
buff.append(r.start + "-" + (r.start + r.length - 1)); return MathUtils.roundUpInt(length, blockSize) / blockSize;
} }
return buff.toString();
@Override
public String toString() {
return freeSpaceList.toString();
} }
/** /**
* A range of free pages. * A range of free blocks.
*/ */
private static final class PageRange { private static final class BlockRange {
/** /**
* The starting point, in pages. * The starting point, in blocks.
*/ */
public int start; int start;
/** /**
* The length, in pages. * The length, in blocks.
*/ */
public int length; int length;
public PageRange(int start, int length) { public BlockRange(int start, int length) {
this.start = start; this.start = start;
this.length = length; this.length = length;
} }
@Override @Override
public String toString() { public String toString() {
return "start:" + start + " length:" + length; if (start + length == Integer.MAX_VALUE) {
return start + "-";
}
return start + "-" + (start + length - 1);
} }
} }
} }
/*
* Copyright 2004-2013 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 java.util.TreeSet;
import org.h2.mvstore.DataUtils;
import org.h2.util.MathUtils;
/**
* A list that maintains ranges of free space (in blocks) in a file.
*/
public class FreeSpaceTree {
/**
* The first usable block.
*/
private final int firstFreeBlock;
/**
* The block size in bytes.
*/
private final int blockSize;
/**
* The list of free space.
*/
private TreeSet<BlockRange> freeSpace = new TreeSet<BlockRange>();
public FreeSpaceTree(int firstFreeBlock, int blockSize) {
this.firstFreeBlock = firstFreeBlock;
if (Integer.bitCount(blockSize) != 1) {
throw DataUtils.newIllegalArgumentException("Block size is not a power of 2");
}
this.blockSize = blockSize;
clear();
}
/**
* Reset the list.
*/
public synchronized void clear() {
freeSpace.clear();
freeSpace.add(new BlockRange(firstFreeBlock,
Integer.MAX_VALUE - firstFreeBlock));
}
/**
* Allocate a number of blocks and mark them as used.
*
* @param length the number of bytes to allocate
* @return the start position in bytes
*/
public synchronized long allocate(int length) {
int blocks = getBlockCount(length);
BlockRange x = null;
for (BlockRange b : freeSpace) {
if (b.blocks >= blocks) {
x = b;
break;
}
}
long pos = getPos(x.start);
if (x.blocks == blocks) {
freeSpace.remove(x);
} else {
x.start += blocks;
x.blocks -= blocks;
}
return pos;
}
public synchronized void markUsed(long pos, int length) {
int start = getBlock(pos);
int blocks = getBlockCount(length);
BlockRange x = new BlockRange(start, blocks);
BlockRange prev = freeSpace.floor(x);
if (prev == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Free space already marked");
}
if (prev.start == start) {
if (prev.blocks == blocks) {
// match
freeSpace.remove(prev);
} else {
// cut the front
prev.start += blocks;
prev.blocks -= blocks;
}
} else if (prev.start + prev.blocks == start + blocks) {
// cut the end
prev.blocks -= blocks;
} else {
// insert an entry
x.start = start + blocks;
x.blocks = prev.start + prev.blocks - x.start;
freeSpace.add(x);
prev.blocks = start - prev.start;
}
}
public synchronized void free(long pos, int length) {
int start = getBlock(pos);
int blocks = getBlockCount(length);
BlockRange x = new BlockRange(start, blocks);
BlockRange next = freeSpace.ceiling(x);
if (next == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Free space sentinel is missing");
}
BlockRange prev = freeSpace.lower(x);
if (prev != null) {
if (prev.start + prev.blocks == start) {
// extend the previous entry
prev.blocks += blocks;
if (prev.start + prev.blocks == next.start) {
// merge with the next entry
prev.blocks += next.blocks;
freeSpace.remove(next);
}
return;
}
}
if (start + blocks == next.start) {
// extend the next entry
next.start -= blocks;
next.blocks += blocks;
return;
}
freeSpace.add(x);
}
private long getPos(int block) {
return (long) block * (long) blockSize;
}
private int getBlock(long pos) {
return (int) (pos / blockSize);
}
private int getBlockCount(int length) {
if (length <= 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Free space invalid length");
}
return MathUtils.roundUpInt(length, blockSize) / blockSize;
}
@Override
public String toString() {
return freeSpace.toString();
}
/**
* A range of free blocks.
*/
private static final class BlockRange implements Comparable<BlockRange> {
/**
* The starting point (the block number).
*/
public int start;
/**
* The length, in blocks.
*/
public int blocks;
public BlockRange(int start, int blocks) {
this.start = start;
this.blocks = blocks;
}
@Override
public int compareTo(BlockRange o) {
return start < o.start ? -1 : start > o.start ? 1 : 0;
}
@Override
public String toString() {
if (blocks + start == Integer.MAX_VALUE) {
return start + "-";
}
return start + "-" + (start + blocks - 1);
}
}
}
/*
* Copyright 2004-2013 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 java.util.Random;
import org.h2.mvstore.FreeSpaceBitSet;
import org.h2.test.TestBase;
import org.h2.util.Utils;
/**
* Tests the free space list.
*/
public class TestFreeSpace extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
testMemoryUsage();
testPerformance();
}
@Override
public void test() throws Exception {
testSimple();
testRandomized();
}
private static void testPerformance() {
for (int i = 0; i < 10; i++) {
long t = System.currentTimeMillis();
FreeSpaceBitSet f = new FreeSpaceBitSet(0, 4096);
// 75 ms
// FreeSpaceList f = new FreeSpaceList(0, 4096);
// 13868 ms
// FreeSpaceTree f = new FreeSpaceTree(0, 4096);
// 56 ms
for (int j = 0; j < 100000; j++) {
f.markUsed(j * 2 * 4096, 4096);
}
for (int j = 0; j < 100000; j++) {
f.free(j * 2 * 4096, 4096);
}
for (int j = 0; j < 100000; j++) {
f.allocate(4096 * 2);
}
System.out.println(System.currentTimeMillis() - t);
}
}
private static void testMemoryUsage() {
// 16 GB file size
long size = 16L * 1024 * 1024 * 1024;
System.gc();
System.gc();
long first = Utils.getMemoryUsed();
FreeSpaceBitSet f = new FreeSpaceBitSet(0, 4096);
// 512 KB
// FreeSpaceTree f = new FreeSpaceTree(0, 4096);
// 64 MB
// FreeSpaceList f = new FreeSpaceList(0, 4096);
// too slow
for (long j = size; j > 0; j -= 4 * 4096) {
f.markUsed(j, 4096);
}
System.gc();
System.gc();
long mem = Utils.getMemoryUsed() - first;
System.out.println("Memory used: " + mem);
System.out.println("f: " + f.toString().length());
}
private void testSimple() {
FreeSpaceBitSet f1 = new FreeSpaceBitSet(2, 1024);
FreeSpaceList f2 = new FreeSpaceList(2, 1024);
FreeSpaceTree f3 = new FreeSpaceTree(2, 1024);
assertEquals(f1.toString(), f2.toString());
assertEquals(f1.toString(), f3.toString());
assertEquals(2 * 1024, f1.allocate(10240));
assertEquals(2 * 1024, f2.allocate(10240));
assertEquals(2 * 1024, f3.allocate(10240));
assertEquals(f1.toString(), f2.toString());
assertEquals(f1.toString(), f3.toString());
f1.markUsed(20480, 1024);
f2.markUsed(20480, 1024);
f3.markUsed(20480, 1024);
assertEquals(f1.toString(), f2.toString());
assertEquals(f1.toString(), f3.toString());
}
private void testRandomized() {
FreeSpaceBitSet f1 = new FreeSpaceBitSet(2, 8);
FreeSpaceList f2 = new FreeSpaceList(2, 8);
Random r = new Random(1);
StringBuilder log = new StringBuilder();
for (int i = 0; i < 100000; i++) {
long pos = r.nextInt(1024);
int length = 1 + r.nextInt(8 * 128);
switch (r.nextInt(3)) {
case 0: {
log.append("allocate(" + length + ");\n");
long a = f1.allocate(length);
long b = f2.allocate(length);
assertEquals(a, b);
break;
}
case 1:
if (f1.isUsed(pos, length)) {
log.append("free(" + pos + ", " + length + ");\n");
f1.free(pos, length);
f2.free(pos, length);
}
break;
case 2:
if (f1.isFree(pos, length)) {
log.append("markUsed(" + pos + ", " + length + ");\n");
f1.markUsed(pos, length);
f2.markUsed(pos, length);
}
break;
}
assertEquals(f1.toString(), f2.toString());
}
}
}
...@@ -46,6 +46,7 @@ public class TestMVTableEngine extends TestBase { ...@@ -46,6 +46,7 @@ public class TestMVTableEngine extends TestBase {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
// testShrinkDatabaseFile(); // testShrinkDatabaseFile();
testTwoPhaseCommit();
testRecover(); testRecover();
testSeparateKey(); testSeparateKey();
testRollback(); testRollback();
...@@ -88,6 +89,33 @@ public class TestMVTableEngine extends TestBase { ...@@ -88,6 +89,33 @@ public class TestMVTableEngine extends TestBase {
} }
} }
private void testTwoPhaseCommit() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
Connection conn;
Statement stat;
String url = "mvstore;MV_STORE=TRUE";
url = getURL(url, true);
conn = getConnection(url);
stat = conn.createStatement();
stat.execute("create table test(id int primary key, name varchar)");
stat.execute("set write_delay 0");
conn.setAutoCommit(false);
stat.execute("insert into test values(1, 'Hello')");
stat.execute("prepare commit test_tx");
stat.execute("shutdown immediately");
JdbcUtils.closeSilently(conn);
conn = getConnection(url);
stat = conn.createStatement();
ResultSet rs;
rs = stat.executeQuery("select * from information_schema.in_doubt");
assertTrue(rs.next());
stat.execute("commit transaction test_tx");
rs = stat.executeQuery("select * from test");
assertTrue(rs.next());
conn.close();
}
private void testRecover() throws Exception { private void testRecover() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true); FileUtils.deleteRecursive(getBaseDir(), true);
Connection conn; Connection conn;
......
...@@ -45,7 +45,7 @@ public class TestTransactionStore extends TestBase { ...@@ -45,7 +45,7 @@ public class TestTransactionStore extends TestBase {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
FileUtils.createDirectories(getBaseDir()); FileUtils.createDirectories(getBaseDir());
testStopWhileCommitting(); // testStopWhileCommitting();
testGetModifiedMaps(); testGetModifiedMaps();
testKeyIterator(); testKeyIterator();
testMultiStatement(); testMultiStatement();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论