diff --git a/core/java/src/net/metanotion/io/block/index/BSkipList.java b/core/java/src/net/metanotion/io/block/index/BSkipList.java index c510107f7aeeeac4251192402013645a1932a275..c55b850d950953a3f43456ee62a206a856cf9ca5 100644 --- a/core/java/src/net/metanotion/io/block/index/BSkipList.java +++ b/core/java/src/net/metanotion/io/block/index/BSkipList.java @@ -82,8 +82,8 @@ public class BSkipList extends SkipList { firstSpanPage = bf.file.readUnsignedInt(); firstLevelPage = bf.file.readUnsignedInt(); size = bf.file.readUnsignedInt(); - spans = bf.file.readUnsignedInt(); - levelCount = bf.file.readUnsignedInt(); + int spans = bf.file.readInt(); + int levelCount = bf.file.readInt(); //System.out.println(size + " " + spans); this.fileOnly = fileOnly; @@ -92,8 +92,18 @@ public class BSkipList extends SkipList { else first = new BSkipSpan(bf, this, firstSpanPage, key, val); stack = new BSkipLevels(bf, firstLevelPage, this); + int total = 0; + for (BSkipSpan ss : spanHash.values()) { + total += ss.nKeys; + } if (BlockFile.log.shouldLog(Log.DEBUG)) - BlockFile.log.debug("Loaded " + this + " cached " + levelHash.size() + " levels and " + spanHash.size() + " spans"); + BlockFile.log.debug("Loaded " + this + " cached " + levelHash.size() + " levels and " + spanHash.size() + " spans with " + total + " entries"); + if (levelCount != levelHash.size() || spans != spanHash.size() || size != total) { + if (BlockFile.log.shouldLog(Log.WARN)) + BlockFile.log.warn("On-disk counts were " + levelCount + " / " + spans + " / " + size + ", correcting"); + size = total; + flush(); + } //rng = new Random(System.currentTimeMillis()); } @@ -117,8 +127,8 @@ public class BSkipList extends SkipList { bf.file.writeInt(firstSpanPage); bf.file.writeInt(firstLevelPage); bf.file.writeInt(Math.max(0, size)); - bf.file.writeInt(Math.max(0, spans)); - bf.file.writeInt(Math.max(0, levelCount)); + bf.file.writeInt(spanHash.size()); + bf.file.writeInt(levelHash.size()); } catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); } } @@ -164,11 +174,21 @@ public class BSkipList extends SkipList { BSkipLevels.init(bf, firstLevel, firstSpan, 4); } + /** + * @return log2(span count), minimum 4 + */ @Override public int maxLevels() { - int max = super.maxLevels(); - int cells = (BlockFile.PAGESIZE - BSkipLevels.HEADER_LEN) / 4; - return Math.min(cells, max); + int hob = 0; + int s = spanHash.size(); + while(s > 0) { + hob++; + s /= P; + } + int max = Math.max(hob, super.maxLevels()); + // 252 + //int cells = (BlockFile.PAGESIZE - BSkipLevels.HEADER_LEN) / 4; + return Math.min(BSkipLevels.MAX_SIZE, max); } @Override @@ -203,8 +223,8 @@ public class BSkipList extends SkipList { public void bslck(boolean isMeta, boolean fix) { BlockFile.log.warn(" size " + this.size); - BlockFile.log.warn(" spans " + this.spans); - BlockFile.log.warn(" levels " + this.levelCount); + BlockFile.log.warn(" spans " + this.spanHash.size()); + BlockFile.log.warn(" levels " + this.levelHash.size()); BlockFile.log.warn(" skipPage " + this.skipPage); BlockFile.log.warn(" firstSpanPage " + this.firstSpanPage); BlockFile.log.warn(" firstLevelPage " + this.firstLevelPage); diff --git a/core/java/src/net/metanotion/io/block/index/BSkipSpan.java b/core/java/src/net/metanotion/io/block/index/BSkipSpan.java index e63edc10958661a332cc90f5eaa8664f714cb052..992ed9d2e447ea9d6663d1cb433aab7342535b09 100644 --- a/core/java/src/net/metanotion/io/block/index/BSkipSpan.java +++ b/core/java/src/net/metanotion/io/block/index/BSkipSpan.java @@ -161,6 +161,8 @@ public class BSkipSpan extends SkipSpan { return; bf.file.writeShort((short) keys.length); bf.file.writeShort((short) nKeys); + if (nKeys <= 0 && prev != null) + BlockFile.log.error("Flushing with no entries?" + this, new Exception()); int ksz, vsz; int curPage = this.page; @@ -264,8 +266,11 @@ public class BSkipSpan extends SkipSpan { bss.nextPage = bf.file.readUnsignedInt(); bss.spanSize = bf.file.readUnsignedShort(); bss.nKeys = bf.file.readUnsignedShort(); - if(bss.spanSize < 1 || bss.spanSize > SkipSpan.MAX_SIZE || bss.nKeys > bss.spanSize) - throw new IOException("Invalid span size " + bss.nKeys + " / "+ bss.spanSize); + if(bss.spanSize < 1 || bss.spanSize > SkipSpan.MAX_SIZE || bss.nKeys > bss.spanSize) { + BlockFile.log.error("Invalid span size " + bss.nKeys + " / "+ bss.spanSize); + bss.nKeys = 0; + bss.spanSize = bf.spanSize; + } } /** @@ -298,10 +303,13 @@ public class BSkipSpan extends SkipSpan { for(int i=0;i<this.nKeys;i++) { if((pageCounter[0] + 4) > BlockFile.PAGESIZE) { BlockFile.pageSeek(this.bf.file, curNextPage[0]); - curPage = curNextPage[0]; int magic = bf.file.readInt(); - if (magic != BlockFile.MAGIC_CONT) - throw new IOException("Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + curPage); + if (magic != BlockFile.MAGIC_CONT) { + BlockFile.log.error("Lost " + (this.nKeys - i) + " entries - Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + curNextPage[0]); + lostEntries(i, curPage); + break; + } + curPage = curNextPage[0]; curNextPage[0] = this.bf.file.readUnsignedInt(); pageCounter[0] = CONT_HEADER_LEN; } @@ -310,8 +318,15 @@ public class BSkipSpan extends SkipSpan { pageCounter[0] +=4; byte[] k = new byte[ksz]; byte[] v = new byte[vsz]; - curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage); - curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage); + int lastGood = curPage; + try { + curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage); + curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage); + } catch (IOException ioe) { + BlockFile.log.error("Lost " + (this.nKeys - i) + " entries - Error loading " + this + " on page " + curPage, ioe); + lostEntries(i, lastGood); + break; + } // System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz); this.keys[i] = (Comparable) this.keySer.construct(k); this.vals[i] = this.valSer.construct(v); @@ -336,6 +351,33 @@ public class BSkipSpan extends SkipSpan { } } + /** + * Attempt to recover from corrupt data in this span. + * All entries starting with firstBadEntry are lost. + * Zero out the overflow page on lastGoodPage, + * and corect the number of entries in the first page. + * We don't attempt to free the lost continuation pages. + */ + protected void lostEntries(int firstBadEntry, int lastGoodPage) { + try { + this.nKeys = firstBadEntry; + // zero overflow page pointer + BlockFile.pageSeek(this.bf.file, lastGoodPage); + bf.file.skipBytes(4); // skip magic + bf.file.writeInt(0); + // write new number of keys + if (lastGoodPage != this.page) { + BlockFile.pageSeek(this.bf.file, this.page); + bf.file.skipBytes(18); + } else { + bf.file.skipBytes(10); + } + bf.file.writeShort(this.nKeys); + } catch (IOException ioe) { + BlockFile.log.error("Error while recovering from corruption of " + this, ioe); + } + } + protected BSkipSpan(BlockFile bf, BSkipList bsl) { this.bf = bf; this.bsl = bsl; diff --git a/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java b/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java index fa74cbcd2f8318c9c70ab4627dd70597190a0821..be3a44772ef8aec9f5ab3d74a55635e0528eacac 100644 --- a/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java +++ b/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java @@ -172,10 +172,13 @@ public class IBSkipSpan extends BSkipSpan { for(int i=0;i<this.nKeys;i++) { if((pageCounter[0] + 4) > BlockFile.PAGESIZE) { BlockFile.pageSeek(this.bf.file, curNextPage[0]); - curPage = curNextPage[0]; int magic = bf.file.readInt(); - if (magic != BlockFile.MAGIC_CONT) - throw new IOException("Bad SkipSpan continuation magic number 0x" + Integer.toHexString(magic) + " on page " + curPage); + if (magic != BlockFile.MAGIC_CONT) { + BlockFile.log.error("Lost " + (this.nKeys - i) + " entries - Bad SkipSpan magic number 0x" + Integer.toHexString(magic) + " on page " + curNextPage[0]); + lostEntries(i, curPage); + break; + } + curPage = curNextPage[0]; curNextPage[0] = this.bf.file.readUnsignedInt(); pageCounter[0] = CONT_HEADER_LEN; } @@ -183,7 +186,13 @@ public class IBSkipSpan extends BSkipSpan { int vsz = this.bf.file.readUnsignedShort(); pageCounter[0] +=4; byte[] k = new byte[ksz]; - curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage); + try { + curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage); + } catch (IOException ioe) { + BlockFile.log.error("Lost " + (this.nKeys - i) + " entries - Error loading " + this + " on page " + curPage, ioe); + lostEntries(i, curPage); + break; + } //System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz); Comparable ckey = (Comparable) this.keySer.construct(k); if (ckey == null) { @@ -197,7 +206,13 @@ public class IBSkipSpan extends BSkipSpan { if (diff == 0) { //System.err.println("Found " + key + " at " + i + " (first: " + this.firstKey + ')'); byte[] v = new byte[vsz]; - curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage); + try { + curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage); + } catch (IOException ioe) { + BlockFile.log.error("Lost " + (this.nKeys - i) + " entries - Error loading " + this + " on page " + curPage, ioe); + lostEntries(i, curPage); + break; + } Object rv = this.valSer.construct(v); if (rv == null) { BlockFile.log.error("Null deserialized value in entry " + i + " page " + curPage + @@ -224,6 +239,7 @@ public class IBSkipSpan extends BSkipSpan { } private void repair(int fail) { + /***** needs work try { loadData(false); if (this.nKeys > 0) @@ -233,6 +249,7 @@ public class IBSkipSpan extends BSkipSpan { } catch (IOException ioe) { BlockFile.log.error("Failed to repair corruption of " + fail + " entries", ioe); } + *****/ } private IBSkipSpan(BlockFile bf, BSkipList bsl) { diff --git a/core/java/src/net/metanotion/util/skiplist/SkipLevels.java b/core/java/src/net/metanotion/util/skiplist/SkipLevels.java index f2af9a4eb462aea8e4b9b33cdd9da227f4ac409e..8e090c48962b0a61ce56f5f41a2289ee4c8f0ee2 100644 --- a/core/java/src/net/metanotion/util/skiplist/SkipLevels.java +++ b/core/java/src/net/metanotion/util/skiplist/SkipLevels.java @@ -187,7 +187,6 @@ public class SkipLevels { } res[1] = null; } - sl.delSpan(res[1] != null); } if((bottom.nKeys == 0) && (sl.first != bottom)) { this.killInstance(); } return res; @@ -244,7 +243,6 @@ public class SkipLevels { if(levels.length < height) return slvls; } - sl.addSpan(height != 0); } return null; } diff --git a/core/java/src/net/metanotion/util/skiplist/SkipList.java b/core/java/src/net/metanotion/util/skiplist/SkipList.java index 132e4e8f7f2bf3a768b0b2774724bda5c170fb0c..d2d2a26ca340a64fe9c6ead8e42ee69b95e9cfdb 100644 --- a/core/java/src/net/metanotion/util/skiplist/SkipList.java +++ b/core/java/src/net/metanotion/util/skiplist/SkipList.java @@ -36,7 +36,7 @@ import net.i2p.util.RandomSource; public class SkipList { /** the probability of each next higher level */ - private static final int P = 2; + protected static final int P = 2; private static final int MIN_SLOTS = 4; // these two are really final protected SkipSpan first; @@ -45,8 +45,6 @@ public class SkipList { public static final Random rng = RandomSource.getInstance(); protected int size; - protected int spans; - protected int levelCount; public void flush() { } protected SkipList() { } @@ -60,8 +58,6 @@ public class SkipList { throw new IllegalArgumentException("Invalid span size"); first = new SkipSpan(span); stack = new SkipLevels(1, first); - spans = 1; - levelCount = 1; //rng = new Random(System.currentTimeMillis()); } @@ -76,29 +72,12 @@ public class SkipList { size--; } - public void addSpan(boolean addLevel) { - spans++; - if (addLevel) - levelCount++; - } - - public void delSpan(boolean delLevel) { - if (spans > 0) - spans--; - if (delLevel && levelCount > 0) - levelCount--; - } - /** - * @return log2(spans), minimum 4 + * @return 4 since we don't track span count here any more - see override + * Fix if for some reason you want a huge in-memory skiplist. */ public int maxLevels() { - int hob = 0, s = spans; - while(s > 0) { - hob++; - s /= P; - } - return Math.max(hob, MIN_SLOTS); + return MIN_SLOTS; } /** @@ -157,13 +136,13 @@ public class SkipList { /** dumps all the skip levels */ public void printSL() { - System.out.println("List size " + size + " spans " + spans); + System.out.println("List size " + size); System.out.println(stack.printAll()); } /** dumps all the data */ public void print() { - System.out.println("List size " + size + " spans " + spans); + System.out.println("List size " + size); System.out.println(first.print()); }