ignite-db - fixes
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/bf594734 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/bf594734 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/bf594734 Branch: refs/heads/ignite-db-x-10884 Commit: bf59473483ca9d93cbe7dc7e1dab91c1cd857c5c Parents: 4c2e9a2 Author: S.Vladykin <[email protected]> Authored: Tue Apr 5 08:01:34 2016 +0300 Committer: S.Vladykin <[email protected]> Committed: Tue Apr 5 08:01:34 2016 +0300 ---------------------------------------------------------------------- .../ignite/internal/pagemem/impl/PageImpl.java | 6 +- .../processors/cache/GridCacheMapEntry.java | 5 +- .../query/h2/database/BPlusTreeRefIndex.java | 484 ++++++++++++------- .../IgniteDbSingleNodePutGetSelfTest.java | 86 ++++ 4 files changed, 407 insertions(+), 174 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/bf594734/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageImpl.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageImpl.java index a57119d..77f096d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageImpl.java @@ -21,11 +21,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.locks.AbstractQueuedSynchronizer; - import org.apache.ignite.internal.pagemem.FullPageId; import org.apache.ignite.internal.pagemem.Page; import org.apache.ignite.internal.util.typedef.internal.SB; -import org.apache.ignite.internal.util.typedef.internal.U; /** * @@ -185,9 +183,6 @@ class PageImpl extends AbstractQueuedSynchronizer implements Page { * Mark dirty. */ private void markDirty() { - if (fullId.pageId() == 0x0001000000010001L) - U.dumpStack(); - pageMem.setDirty(fullId, ptr, true); } @@ -196,6 +191,7 @@ class PageImpl extends AbstractQueuedSynchronizer implements Page { if (markDirty) markDirty(); + assert getState() == -1; assert getExclusiveOwnerThread() == Thread.currentThread() : "illegal monitor state"; setExclusiveOwnerThread(null); http://git-wip-us.apache.org/repos/asf/ignite/blob/bf594734/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java index 0afa0fc..82b65e0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMapEntry.java @@ -3931,8 +3931,9 @@ public abstract class GridCacheMapEntry extends GridMetadataAwareAdapter impleme } } } - else - clearIndex(prev, ver); + // TODO fix +// else +// clearIndex(prev, ver); // Nullify value after swap. value(null); http://git-wip-us.apache.org/repos/asf/ignite/blob/bf594734/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/BPlusTreeRefIndex.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/BPlusTreeRefIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/BPlusTreeRefIndex.java index 0dae675..8dea5ae 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/BPlusTreeRefIndex.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/BPlusTreeRefIndex.java @@ -150,7 +150,7 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { if (pageId == null) return ">NPE<"; - if (pageId.equals(0L)) + if (pageId == 0L) return "<Zero>"; try (Page page = page(pageId)) { @@ -312,7 +312,7 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { /** */ private final PageHandler<Remove> removeFromLeaf = new GetPageHandler<Remove>() { - @Override public int run0(Page page, ByteBuffer buf, IndexPageIO io, Remove r, int lvl) + @Override public int run0(Page leaf, ByteBuffer buf, IndexPageIO io, Remove r, int lvl) throws IgniteCheckedException { assert lvl == 0: lvl; assert r.removed == null; @@ -326,13 +326,45 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { r.removed = getRow(io.getLink(buf, idx)); - doRemove(io, page, buf, cnt, idx, r.meta, lvl, false); + doRemove(io, leaf, buf, cnt, idx, r.meta, lvl, false); + + // We may need to replace inner key or want to merge this leaf with sibling after the remove -> keep lock. + if (r.needReplaceInner == TRUE || + // We need to make sure that we have back or forward to be able to merge. + ((r.fwdId != 0 || r.backId != 0) && mayMerge(--cnt, io.getMaxCount(buf)))) { + r.addTail(leaf, buf, io, 0, false, Integer.MIN_VALUE); + + if (r.needReplaceInner == FALSE) + r.needMerge = TRUE; + } return Remove.FOUND; } }; /** */ + private final PageHandler<Remove> lockBackAndRemoveFromLeaf = new GetPageHandler<Remove>() { + @Override protected int run0(Page back, ByteBuffer buf, IndexPageIO io, Remove r, int lvl) + throws IgniteCheckedException { + // Check that we have consistent view of the world. + if (io.getForward(buf) != r.pageId) + return Remove.RETRY; + + // Correct locking order: from back to forward. + int res = doRemoveFromLeaf(r); + + // If we need to do more tricks, then we have to keep locks on back and leaf pages. + if (res == Remove.FOUND && (r.needMerge == TRUE || r.needReplaceInner == TRUE)) { + assert r.needMerge == TRUE ^ r.needReplaceInner == TRUE: "we can do only one thing at once"; + + r.addTail(back, buf, io, lvl, true, Integer.MIN_VALUE); + } + + return res; + } + }; + + /** */ private final PageHandler<Remove> lockBackAndTail = new GetPageHandler<Remove>() { @Override public int run0(Page back, ByteBuffer buf, IndexPageIO io, Remove r, int lvl) throws IgniteCheckedException { @@ -343,12 +375,10 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { // Correct locking order: from back to forward. int res = doLockTail(r, lvl); - if (res != Remove.FOUND) - return res; - - r.addTail(back, buf, io, lvl, true, Integer.MIN_VALUE); + if (res == Remove.FOUND) + r.addTail(back, buf, io, lvl, true, Integer.MIN_VALUE); - return Remove.FOUND; + return res; } }; @@ -388,6 +418,13 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { r.addTail(page, buf, io, lvl, false, idx); + if (r.needMerge == TRUE) { + assert r.needReplaceInner == FALSE; + assert r.tail.down != null; + + r.needMerge = READY; + } + return Remove.FOUND; } }; @@ -491,9 +528,9 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { ) throws IgniteCheckedException { super(keyCol, valCol); - // TODO make configurable - minFill = 0.1f; - maxFill = 0.8f; + // TODO make configurable: 0 <= minFill <= maxFill <= 1 + minFill = 0f; // Testing worst case when merge happens only on empty page. + maxFill = 0f; // Avoiding random effects on testing. assert cctx.cacheId() == metaPageId.cacheId(); @@ -853,7 +890,12 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { if (i != 0) b.append(','); - b.append(getRow(io.getLink(buf, i)).key.value(coctx, true)); + long link = io.getLink(buf, i); + + if (pageId(link) == 0) + b.append("<!X!>"); + else + b.append(getRow(link).key.value(coctx, true)); } b.append(']'); @@ -895,7 +937,7 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { default: if (!r.isFinished()) - r.finishTail(); + r.finishTail(true); assert r.isFinished(); @@ -910,7 +952,7 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { r.releaseTail(); r.releaseMeta(); -// if ("_key_PK".equals(getName())) { // && row.getValue(0).getInt() == 963 +// if ("_key_PK".equals(getName()) && row.getValue(0).getInt() < 900) { // X.println("row= " + row); // X.println("rmv= " + r.removed); // X.println("idx= " + getName()); @@ -933,8 +975,14 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { private void doRemove(IndexPageIO io, Page page, ByteBuffer buf, int cnt, int idx, Page meta, int lvl, boolean kickLeftChild) throws IgniteCheckedException { assert cnt > 0; - assert idx < cnt; assert idx >= 0; + assert idx <= cnt; + + if (idx == cnt) { + idx--; // This may happen in case of right turn, we need to remove the rightmost ref and link. + + assert !kickLeftChild: "right child must be dropped here"; + } cnt--; @@ -985,32 +1033,21 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { return res; } - if (!r.isFinished()) { - if (r.needReplaceInner == TRUE) - return lockTail(r, page, backId, fwdId, lvl); - - r.finishTail(); - } + if (!r.isFinished() && !r.finishTail(false)) + return lockTail(r, page, backId, fwdId, lvl); return res; case Remove.FOUND: // We must be at the bottom here, just need to remove row from the current page. assert lvl == 0 : lvl; + assert r.removed == null; - if (r.needReplaceInner == TRUE && backId != 0) { - // TODO We know, that can lock back page before the remove. - } - - // If r.removed is set then we are retrying to lock tail pages and don't need to do remove here. - res = r.removed != null ? Remove.FOUND : writePage(page, removeFromLeaf, r, lvl, Remove.RETRY); - - if (res == Remove.FOUND) { - if (r.needReplaceInner == TRUE) - return lockTail(r, page, backId, fwdId, lvl); + res = removeFromLeaf(r, page, backId, fwdId); + // Finish if we don't need to do any merges. + if (res == Remove.FOUND && r.needReplaceInner == FALSE && r.needMerge == FALSE) r.finish(); - } return res; @@ -1036,6 +1073,46 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { } /** + * @param r Remove operation. + * @param leaf Leaf page. + * @param backId Back page ID. + * @param fwdId Forward ID. + * @return Result code. + * @throws IgniteCheckedException If failed. + */ + private int removeFromLeaf(Remove r, Page leaf, long backId, long fwdId) throws IgniteCheckedException { + r.pageId = leaf.id(); + r.page = leaf; + r.backId = backId; + r.fwdId = fwdId; + + if (backId == 0) + return doRemoveFromLeaf(r); // Fast path. + + // Lock back page before the remove, we'll need it for merges. + Page back = page(backId); + + try { + return writePage(back, lockBackAndRemoveFromLeaf, r, 0, Remove.RETRY); + } + finally { + if (r.canRelease(back, 0)) + back.close(); + } + } + + /** + * @param r Remove operation. + * @return Result code. + * @throws IgniteCheckedException If failed. + */ + private int doRemoveFromLeaf(Remove r) throws IgniteCheckedException { + assert r.page != null; + + return writePage(r.page, removeFromLeaf, r, 0, Remove.RETRY); + } + + /** * @param cnt Count. * @param cap Capacity. * @return {@code true} If may merge. @@ -1043,14 +1120,21 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { private boolean mayMerge(int cnt, int cap) { int minCnt = (int)(minFill * cap); - if (cnt <= minCnt) + if (cnt <= minCnt) { + assert cnt == 0; // TODO remove + return true; + } + + assert cnt > 0; int maxCnt = (int)(maxFill * cap); if (cnt > maxCnt) return false; + assert false; // TODO remove + // Randomization is for smoothing worst case scenarios. Probability of merge attempt // is proportional to free space in our page (discounted on fill factor). return ThreadLocalRandom.current().nextInt(maxCnt - minCnt) >= cnt - minCnt; @@ -1093,21 +1177,26 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { if (newCnt == -1) // Not enough space. return false; + cur.io.setCount(cur.buf, newCnt); + + int prntCnt = prnt.io.getCount(prnt.buf); + // Move down split key in inner pages. if (cur.lvl != 0) { - cur.io.setLink(cur.buf, cnt, prnt.io.getLink(prnt.buf, prnt.idx)); + int prntIdx = prnt.idx; + + if (prntIdx == prntCnt) // It was a right turn. + prntIdx--; + + cur.io.setLink(cur.buf, cnt, prnt.io.getLink(prnt.buf, prntIdx)); cnt++; } - // Update current page. cur.io.copyItems(fwdBuf, cur.buf, 0, cnt, fwdCnt, true); - cur.io.setCount(cur.buf, newCnt); cur.io.setForward(cur.buf, cur.io.getForward(fwdBuf)); // Update parent. - int prntCnt = prnt.io.getCount(prnt.buf); - assert prntCnt > 0: prntCnt; doRemove(prnt.io, prnt.page, prnt.buf, prntCnt, prnt.idx, meta, prnt.lvl, false); @@ -1146,107 +1235,6 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { } /** - * @param r Remove operation. - * @throws IgniteCheckedException If failed. - */ - private void doReplaceInner(Remove r) throws IgniteCheckedException { - assert r.needReplaceInner == READY: r.needReplaceInner; - assert r.tail.lvl > 0; - assert r.innerIdx >= 0; - - Tail leaf = r.getTail(0, false); - Tail inner = r.getTail(r.tail.lvl, false); - - int cnt = leaf.io.getCount(leaf.buf); - - if (cnt == 0) { // Merge empty page. - if (!merge(r, 0)) { - // For leaf pages this can happen only when parent is empty -> drop the whole branch. - dropEmptyBranch(r.meta, inner); - - return; - } - - // Need to handle possible tail restructuring after merge. - leaf = r.getTail(0, false); - - cnt = leaf.io.getCount(leaf.buf); - - // If any leaf becomes empty we have to either merge it or drop the whole empty branch. - assert cnt > 0: "leaf can't be empty after successful merge"; - } - - long maxLink = leaf.io.getLink(leaf.buf, cnt - 1); - - inner.io.setLink(inner.buf, r.innerIdx, maxLink); - leaf.io.setRemoveId(leaf.buf, globalRmvId.get()); - - // Try to merge the whole branch if possible. - for (int i = 1, end = r.tail.lvl - 1; i < end; i++) - merge(r, i); - } - - /** - * @param r Remove operation. - * @param lvl Level. - * @return {@code true} If merged, {@code false} if not (for example because of insufficient space). - * @throws IgniteCheckedException - */ - private boolean merge(Remove r, int lvl) throws IgniteCheckedException { - assert r.tail.lvl > lvl; - - Tail prnt = r.getTail(lvl + 1, false); - - if (prnt.io.getCount(prnt.buf) == 0) - return false; // Parent is an empty routing page, forward page will have another parent. - - Tail cur = r.getTail(lvl, false); - Tail back = r.getTail(lvl, true); - - if (back == null) { - // We don't have a back page -> last move was to the left. - long fwdId = cur.io.getForward(cur.buf); - - if (fwdId == 0) // We can get 0 only in the last rightmost page with empty parent -> keep both. - return false; - - int cnt = cur.io.getCount(cur.buf); - int maxCnt = cur.io.getMaxCount(cur.buf); - - if (!mayMerge(cnt, maxCnt)) - return false; - - try (Page fwd = page(fwdId)) { - assert fwd != null; // We've locked cur page which is back for fwd. - - // Check count in read lock first. - int fwdCnt = getLinksCount(fwd); - - if (countAfterMerge(cur, fwdCnt) == -1) - return false; - - return writePage(fwd, mergePages, r, 0, FALSE) == TRUE; - } - } - else { - assert cur.io == back.io: "must always be the same"; // Otherwise may be not compatible. - - if (mergePages(r.meta, prnt, back, cur.page, cur.buf)) { - assert prnt.down == back; - assert back.fwd == cur; - - // Back becomes current. - back.down = cur.down; - back.fwd = null; - - return true; - } - - return false; - } - } - - /** * @param page Page. * @param buf Buffer. * @param io IO. @@ -1278,6 +1266,8 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { * @throws IgniteCheckedException If failed. */ private int lockTail(Remove r, Page page, long backId, long fwdId, int lvl) throws IgniteCheckedException { + assert r.needMerge == TRUE ^ r.needReplaceInner == TRUE: "we can do only one thing at once"; + // Init parameters for the handlers. r.pageId = page.id(); r.page = page; @@ -1394,12 +1384,11 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { // Move right all the greater elements to make a free slot for a new row link. io.copyItems(buf, buf, idx, idx + 1, cnt - idx, false); + io.setCount(buf, cnt + 1); io.setLink(buf, idx, row.link); if (!io.isLeaf()) // Setup reference to the right page on split. inner(io).setRight(buf, idx, rightId); - - io.setCount(buf, cnt + 1); } /** @@ -1503,6 +1492,20 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { } /** + * @param page Page. + */ + private static void writeUnlockAndClose(Page page) { + assert page != null; + + try { + page.releaseWrite(true); + } + finally { + page.close(); + } + } + + /** * @param pageId Inner page ID. * @return Leftmost child page ID. */ @@ -1852,14 +1855,9 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { * @param tail Tail lock. */ private void tail(Page tail) { - if (this.tail != null) { - try { - this.tail.releaseWrite(true); - } - finally { - this.tail.close(); - } - } + if (this.tail != null) + writeUnlockAndClose(this.tail); + this.tail = tail; } @@ -1895,6 +1893,9 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { /** */ byte needReplaceInner = FALSE; + /** */ + byte needMerge = FALSE; + /** Removed row. */ GridH2Row removed; @@ -1942,26 +1943,43 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { /** * Process tail and finish. * - * @return {@code false} If failed to finish and need to lock more rows up. + * @param skipMergeMore Ignore the attempt to merge more pages up. + * @return {@code false} If failed to finish and we need to lock more pages up. * @throws IgniteCheckedException If failed. */ - boolean finishTail() throws IgniteCheckedException { + boolean finishTail(boolean skipMergeMore) throws IgniteCheckedException { + assert !isFinished(); + assert needMerge != FALSE || needReplaceInner != FALSE; assert tail != null; - // We increment remove ID in write lock on leaf page, thus it is guaranteed that - // any successor will get greater value than he had read at the beginning of the operation. - // Thus it will be guaranteed to do a retry from root. - globalRmvId.incrementAndGet(); - if (needReplaceInner == READY) { + assert getTail(0, false) != null: "we must keep lock on the leaf page"; + + // We increment remove ID in write lock on leaf page, thus it is guaranteed that + // any successor will get greater value than he had read at the beginning of the operation. + // Thus it will be guaranteed to do a retry from root. + globalRmvId.incrementAndGet(); + // Need to replace inner key with new max key for the left subtree. - doReplaceInner(this); + doReplaceInner(); needReplaceInner = DONE; } - else { - // TODO merge if possible + else if (needMerge == READY) { + assert tail.down != null || tail.fwd.down != null; + + boolean needMergeMore = merge(tail.lvl - 1, true, true); + + if (needMergeMore && !skipMergeMore) { + needMerge = TRUE; + + return false; + } + + needMerge = DONE; } + else + return false; releaseTail(); finish(); @@ -1970,6 +1988,137 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { } /** + * @throws IgniteCheckedException If failed. + */ + private void doReplaceInner() throws IgniteCheckedException { + assert needReplaceInner == READY: needReplaceInner; + assert tail.lvl > 0; + assert innerIdx >= 0; + + Tail leaf = getTail(0, false); + Tail inner = getTail(tail.lvl, false); + + assert innerIdx < inner.io.getCount(inner.buf); + + int cnt = leaf.io.getCount(leaf.buf); + + if (cnt == 0) { // Merge empty leaf page. + if (!merge(0, true, false)) { + // For leaf pages this can happen only when parent is empty -> drop the whole branch. + dropEmptyBranch(meta, inner); + + return; + } + + // Need to handle possible tail restructuring after merge. + leaf = getTail(0, false); + + cnt = leaf.io.getCount(leaf.buf); + + // If any leaf becomes empty we have to either merge it or drop the whole empty branch. + assert cnt > 0: "leaf can't be empty after successful merge"; + } + + // If after leaf merge parent have lost inner key, we don't need to update it anymore. + if (innerIdx < inner.io.getCount(inner.buf)) { + long maxLink = leaf.io.getLink(leaf.buf, cnt - 1); + + inner.io.setLink(inner.buf, innerIdx, maxLink); + leaf.io.setRemoveId(leaf.buf, globalRmvId.get()); + } + else { + assert innerIdx == inner.io.getCount(inner.buf); + assert inner(inner.io).getLeft(inner.buf, innerIdx) == leaf.page.id(); + } + + // Try to merge the whole branch up if possible. + for (int i = 1, end = tail.lvl - 1; i < end; i++) { + if (!merge(i, false, true)) + break; + } + } + + /** + * @param lvl Level. + * @param skipMayMerge Skip checking for {@link #mayMerge(int, int)}. + * @return {@code true} If merged, {@code false} if not (for example because of insufficient space). + * @throws IgniteCheckedException If failed. + */ + private boolean merge(int lvl, boolean skipMayMerge, boolean releaseMerged) throws IgniteCheckedException { + assert tail.lvl > lvl; + + Tail prnt = getTail(lvl + 1, false); + + if (prnt.io.getCount(prnt.buf) == 0) + return false; // Parent is an empty routing page, forward page will have another parent. + + Tail cur = getTail(lvl, false); + Tail back = getTail(lvl, true); + + if (back == null) { + // We don't have a back page -> last move was to the left. + long fwdId = cur.io.getForward(cur.buf); + + if (fwdId == 0) // We can get 0 only in the last rightmost page with empty parent -> drop both. + return false; + + int cnt = cur.io.getCount(cur.buf); + + if (!skipMayMerge) { + int maxCnt = cur.io.getMaxCount(cur.buf); + + if (!mayMerge(cnt, maxCnt)) + return false; + } + + try (Page fwd = page(fwdId)) { + assert fwd != null; // We've locked cur page which is back for fwd. + + // Check count in read lock first. + int fwdCnt = getLinksCount(fwd); + + if (countAfterMerge(cur, fwdCnt) == -1) + return false; + + if (writePage(fwd, mergePages, this, 0, FALSE) == TRUE) { + if (releaseMerged) { + prnt.down = null; + + writeUnlockAndClose(cur.page); + } + + return true; + } + + return false; + } + } + else { + assert cur.io == back.io: "must always be the same"; // Otherwise may be not compatible. + + if (mergePages(meta, prnt, back, cur.page, cur.buf)) { + assert prnt.down == back; + assert back.fwd == cur; + + // Back becomes current. + back.down = cur.down; + back.fwd = null; + + if (releaseMerged) { + prnt.down = null; + + writeUnlockAndClose(back.page); + writeUnlockAndClose(cur.page); + } + + return true; + } + + return false; + } + } + + /** * @return {@code true} If finished. */ boolean isFinished() { @@ -1985,12 +2134,7 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { tail = null; while (t != null) { - try { - t.page.releaseWrite(true); - } - finally { - t.page.close(); - } + writeUnlockAndClose(t.page); if (t.down != null) t = t.down; @@ -2037,8 +2181,6 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { * @param idx Insertion index. */ void addTail(Page page, ByteBuffer buf, IndexPageIO io, int lvl, boolean back, int idx) { - assert back ^ idx >= 0; - Tail t = new Tail(page, buf, io, lvl, idx); if (back) { @@ -2072,6 +2214,8 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { * @return Tail. */ public Tail getTail(int lvl, boolean back) { + assert lvl <= tail.lvl: "level is too high"; + Tail t = tail; while (t.lvl != lvl) { @@ -2079,6 +2223,8 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { t = t.fwd; else t = t.down; + + assert t != null : "level is too low"; } if (back) @@ -2678,6 +2824,8 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { /** {@inheritDoc} */ @Override public long getLink(ByteBuffer buf, int idx) { + assert idx < getCount(buf): idx; + return buf.getLong(offset(idx, SHIFT_LINK)); } @@ -2813,6 +2961,8 @@ public class BPlusTreeRefIndex extends PageMemoryIndex { /** {@inheritDoc} */ @Override public long getLink(ByteBuffer buf, int idx) { + assert idx < getCount(buf): idx; + return buf.getLong(offset(idx)); } http://git-wip-us.apache.org/repos/asf/ignite/blob/bf594734/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java index c00fe6c..58f1549 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java @@ -420,6 +420,92 @@ public class IgniteDbSingleNodePutGetSelfTest extends GridCommonAbstractTest { } } + public void testPutGetRemoveMultipleForward() throws Exception { + IgniteEx ig = grid(0); + + final IgniteCache<Integer, DbValue> cache = ig.cache(null); + + GridCacheAdapter<Object, Object> internalCache = ig.context().cache().internalCache(); + + int cnt = 100_000; + + X.println("Put."); + + for (int i = 0; i < cnt; i++) { + DbValue v0 = new DbValue(i, "test-value", i); + +// if (i % 100 == 0) +// X.println(" --> " + i); + + cache.put(i, v0); + + checkEmpty(internalCache, i); + + assertEquals(v0, cache.get(i)); + } + + X.println("Start removing."); + + for (int i = 0; i < cnt; i++) { + if (i % 100 == 0) { + X.println("-> " + i); + +// assertEquals((long)(cnt - i), +// cache.query(new SqlFieldsQuery("select count(*) from dbvalue")).getAll().get(0).get(0)); + } + + cache.remove(i); + + assertNull(cache.get(i)); + + if (i + 1 < cnt) + assertEquals(new DbValue(i + 1, "test-value", i + 1), cache.get(i + 1)); + } + } + + public void testPutGetRemoveMultipleBackward() throws Exception { + IgniteEx ig = grid(0); + + final IgniteCache<Integer, DbValue> cache = ig.cache(null); + + GridCacheAdapter<Object, Object> internalCache = ig.context().cache().internalCache(); + + int cnt = 100_000; + + X.println("Put."); + + for (int i = 0; i < cnt; i++) { + DbValue v0 = new DbValue(i, "test-value", i); + +// if (i % 100 == 0) +// X.println(" --> " + i); + + cache.put(i, v0); + + checkEmpty(internalCache, i); + + assertEquals(v0, cache.get(i)); + } + + X.println("Start removing in backward direction."); + + for (int i = cnt - 1; i >= 0; i--) { + if (i % 100 == 0) { + X.println("-> " + i); + +// assertEquals((long)(cnt - i), +// cache.query(new SqlFieldsQuery("select count(*) from dbvalue")).getAll().get(0).get(0)); + } + + cache.remove(i); + + assertNull(cache.get(i)); + + if (i - 1 >= 0) + assertEquals(new DbValue(i - 1, "test-value", i - 1), cache.get(i - 1)); + } + } + private void checkEmpty(final GridCacheAdapter internalCache, final int key) throws Exception { GridTestUtils.waitForCondition(new PA() { @Override public boolean apply() {
