I fixed a couple of issues with my previous patch, but it appears I
still don't understand the rules around allocating/de-allocating chunks,
especially in the presence of rollback, which means that the freelist
still gets out of sync.
--
You received this message because you are subscribed to the Google Groups "H2
Database" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/h2-database?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
Index: src/main/org/h2/mvstore/Chunk.java
===================================================================
--- src/main/org/h2/mvstore/Chunk.java (revision 4708)
+++ src/main/org/h2/mvstore/Chunk.java (working copy)
@@ -42,7 +42,7 @@
int length;
/**
- * The number of pages.
+ * The number of times the page has been written.
*/
int pageCount;
Index: src/main/org/h2/mvstore/FreeSpaceList.java
===================================================================
--- src/main/org/h2/mvstore/FreeSpaceList.java (revision 0)
+++ src/main/org/h2/mvstore/FreeSpaceList.java (working copy)
@@ -0,0 +1,139 @@
+package org.h2.mvstore;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FreeSpaceList {
+
+ // the first 2 pages are occupied by the file header
+ private static final int FIRST_FREE_PAGE = 3;
+
+ private List<PageRange> freeSpaceList = new ArrayList<PageRange>();
+
+ public FreeSpaceList() {
+ freeSpaceList.add(new PageRange(FIRST_FREE_PAGE, Integer.MAX_VALUE));
+ }
+
+ private static final class PageRange {
+ /** the start point, in pages */
+ public long start;
+ /** the length, in pages */
+ public int length;
+
+ public PageRange(long start, int length) {
+ this.start = start;
+ this.length = length;
+ }
+
+ @Override
+ public String toString() {
+ return "start:" + start + " length:" + length;
+ }
+ }
+
+ /**
+ * @return position in pages
+ */
+ public synchronized long allocatePages(long length) {
+ long required = (length / MVStore.BLOCK_SIZE) + 1;
+ System.out.println("allocatePages : required " + required);
+ for (PageRange pr : freeSpaceList) {
+ if (pr.length >= required) {
+ return pr.start;
+ }
+ }
+ throw new IllegalStateException("could not find a free page to
allocate");
+ }
+
+ public synchronized void markUsed(Chunk c) {
+ long chunkStart = c.start / MVStore.BLOCK_SIZE;
+ int required = (c.length / MVStore.BLOCK_SIZE) + 1;
+ System.out.println("markUsed : chunk ID: " + c.id + " chunkStart:" +
chunkStart + " required:" + required);
+ PageRange found = null;
+ int i = 0;
+ for (PageRange pr : freeSpaceList) {
+ if (pr.start <= chunkStart) {
+ found = pr;
+ break;
+ }
+ i++;
+ }
+ if (found == null) {
+ throw new IllegalStateException("cannot find spot to mark chunk as
used in free list, " + c.toString());
+ }
+ if (found.start == chunkStart) {
+ // if the used-chunk is at the beginning of a free-space-range
+ found.start += required;
+ } else if (found.start + found.length == chunkStart + required) {
+ // if the used-chunk is at the end of a free-space-range
+ found.length -= required;
+ } else {
+ // it's in the middle, so split the existing entry
+ int length1 = (int) (chunkStart - found.start);
+ long start2 = chunkStart + required;
+ int length2 = (int) (found.start + found.length - chunkStart -
required);
+
+ found.length = length1;
+ PageRange newRange = new PageRange(start2, length2);
+ freeSpaceList.add(i, newRange);
+ }
+ }
+
+ public synchronized void markFree(Chunk c) {
+ long chunkStart = c.start / MVStore.BLOCK_SIZE;
+ int required = (c.length / MVStore.BLOCK_SIZE) + 1;
+ System.out.println("markFree : chunk ID: " + c.id + " chunkStart:" +
chunkStart + " required:" + required);
+ PageRange found = null;
+ int i = 0;
+ for (PageRange pr : freeSpaceList) {
+ if (pr.start > chunkStart) {
+ found = pr;
+ break;
+ }
+ i++;
+ }
+ if (found == null) {
+ throw new IllegalStateException("cannot find spot to mark chunk as
unused in free list, " + c.toString());
+ }
+ if (chunkStart + required + 1 == found.start) {
+ // if the used-chunk is adjacent to the beginning of a
+ // free-space-range
+ found.start = chunkStart;
+ found.length += required;
+ // compactify-free-list: merge the previous entry into this one if
+ // they are now adjacent
+ if (i > 0) {
+ PageRange previous = freeSpaceList.get(i - 1);
+ if (previous.start + previous.length + 1 == found.start) {
+ previous.length += found.length;
+ freeSpaceList.remove(i);
+ }
+ }
+ return;
+ }
+ if (i > 0) {
+ // if the used-chunk is adjacent to the end of a free-space-range
+ PageRange previous = freeSpaceList.get(i - 1);
+ if (previous.start + previous.length + 1 == chunkStart) {
+ previous.length += required;
+ // compactify-free-list: 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;
+ }
+ }
+
+ // it is between 2 entries, so add a new one
+ PageRange newRange = new PageRange(chunkStart, required);
+ freeSpaceList.add(i, newRange);
+ }
+
+ public synchronized void clear() {
+ System.out.println("clear");
+ freeSpaceList.clear();
+ freeSpaceList.add(new PageRange(FIRST_FREE_PAGE, Integer.MAX_VALUE));
+ }
+}
Index: src/main/org/h2/mvstore/MVStore.java
===================================================================
--- src/main/org/h2/mvstore/MVStore.java (revision 4708)
+++ src/main/org/h2/mvstore/MVStore.java (working copy)
@@ -12,12 +12,12 @@
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.h2.compress.CompressLZF;
import org.h2.compress.Compressor;
@@ -148,7 +148,12 @@
*/
private final ConcurrentHashMap<Integer, Chunk> chunks =
new ConcurrentHashMap<Integer, Chunk>();
+ /**
+ * The list of free spaces between the chunks.
+ */
+ private FreeSpaceList freeSpaceList = new FreeSpaceList();
+
/**
* The map of temporarily freed entries in the chunks. The key is the
* unsaved version, the value is the map of chunks. The maps of chunks
@@ -564,6 +569,11 @@
lastChunkId = Math.max(c.id, lastChunkId);
chunks.put(c.id, c);
}
+ // rebuild the free space list
+ freeSpaceList.clear();
+ for (Chunk c : chunks.values()) {
+ freeSpaceList.markUsed(c);
+ }
}
private void readFileHeader() {
@@ -694,6 +704,7 @@
}
meta = null;
chunks.clear();
+ freeSpaceList.clear();
cache.clear();
maps.clear();
} catch (Exception e) {
@@ -845,7 +856,7 @@
}
applyFreedChunks(storeVersion);
- ArrayList<Integer> removedChunks = New.arrayList();
+ Set<Integer> removedChunks = New.hashSet();
// do it twice, because changing the meta table
// could cause a chunk to become empty
for (int i = 0; i < 2; i++) {
@@ -900,7 +911,7 @@
buff.limit(length);
long fileLength = getFileLengthUsed();
- long filePos = reuseSpace ? allocateChunk(length) : fileLength;
+ long filePos = reuseSpace ? allocateChunk(chunkLength) : fileLength;
boolean storeAtEndOfFile = filePos + length >= fileLength;
// need to keep old chunks
@@ -908,11 +919,13 @@
// by an old version
// so empty space is not reused too early
for (int x : removedChunks) {
+ freeSpaceList.markFree(chunks.get(x));
chunks.remove(x);
}
c.start = filePos;
c.length = chunkLength;
+ freeSpaceList.markUsed(c);
c.metaRootPos = meta.getRoot().getPos();
buff.position(0);
c.writeHeader(buff);
@@ -1024,33 +1037,7 @@
}
private long allocateChunk(long length) {
- BitSet set = new BitSet();
- set.set(0);
- set.set(1);
- for (Chunk c : chunks.values()) {
- if (c.start == Long.MAX_VALUE) {
- continue;
- }
- int first = (int) (c.start / BLOCK_SIZE);
- int last = (int) ((c.start + c.length) / BLOCK_SIZE);
- set.set(first, last + 2);
- }
- int required = (int) (length / BLOCK_SIZE) + 1;
- for (int i = 0; i < set.size(); i++) {
- if (!set.get(i)) {
- boolean ok = true;
- for (int j = 0; j < required; j++) {
- if (set.get(i + j)) {
- ok = false;
- break;
- }
- }
- if (ok) {
- return i * BLOCK_SIZE;
- }
- }
- }
- return set.size() * BLOCK_SIZE;
+ return freeSpaceList.allocatePages(length) * BLOCK_SIZE;
}
/**
@@ -1488,6 +1475,7 @@
}
meta.clear();
chunks.clear();
+ freeSpaceList.clear();
maps.clear();
synchronized (freedChunks) {
freedChunks.clear();
@@ -1517,6 +1505,7 @@
revertTemp(version);
loadFromFile = true;
do {
+ freeSpaceList.markFree(chunks.get(lastChunkId));
last = chunks.remove(lastChunkId);
lastChunkId--;
} while (last.version > version && chunks.size() > 0);
Index: src/test/org/h2/test/store/TestMVStore.java
===================================================================
--- src/test/org/h2/test/store/TestMVStore.java (revision 4708)
+++ src/test/org/h2/test/store/TestMVStore.java (working copy)
@@ -79,6 +79,10 @@
testIterate();
testCloseTwice();
testSimple();
+ // this test will run out of memory on a 32-bit VM
+ if ("64".equals(System.getProperty("sun.arch.data.model"))) {
+ testLargerThan2G();
+ }
}
private void testAtomicOperations() {
@@ -1392,7 +1396,33 @@
}
s.close();
}
+
+ private void testLargerThan2G() {
+ String fileName = getBaseDir() + "/testLargerThan2G.h3";
+ FileUtils.delete(fileName);
+
+ MVStore store = new
MVStore.Builder().cacheSize(800).fileName(fileName).writeBufferSize(0).writeDelay(-1)
+ .open();
+ MVMap<String, String> map = store.openMap("test");
+ int totalWrite = 15000000;
+ int lineStored = 0;
+ while (lineStored <= totalWrite) {
+ lineStored++;
+ String actualKey = lineStored + " just for length
hhhhhhhhhhhhhhhhh" + lineStored;
+ String value = "Just a a string that is kinda longgggggggggggg but
not too much AAAAAAAAAAAAAAAAAAAAA hhhhhhhhhhhhhhhhhh"
+ + lineStored;
+
+ map.put(actualKey, value);
+
+ if (lineStored % 1000000 == 0) {
+ store.store();
+ }
+ }
+ store.store();
+ store.close();
+ }
+
/**
* Open a store for the given file name, using a small page size.
*