use of org.neo4j.io.pagecache.PageCursor in project neo4j by neo4j.
the class InternalTreeLogic method updateRightmostChildInLeftSibling.
private void updateRightmostChildInLeftSibling(PageCursor cursor, long childPointer, long stableGeneration, long unstableGeneration) throws IOException {
long leftSibling = bTreeNode.leftSibling(cursor, stableGeneration, unstableGeneration);
// Left sibling is not allowed to be NO_NODE here because that means there is a child node with no parent
PointerChecking.checkPointer(leftSibling, false);
try (PageCursor leftSiblingCursor = cursor.openLinkedCursor(leftSibling)) {
bTreeNode.goTo(leftSiblingCursor, "left sibling", leftSibling);
int keyCount = bTreeNode.keyCount(leftSiblingCursor);
bTreeNode.setChildAt(leftSiblingCursor, childPointer, keyCount, stableGeneration, unstableGeneration);
}
}
use of org.neo4j.io.pagecache.PageCursor in project neo4j by neo4j.
the class InternalTreeLogic method splitLeaf.
/**
* Leaves cursor at same page as when called. No guarantees on offset.
* Cursor is expected to be pointing to full leaf.
*
* @param cursor cursor pointing into full (left) leaf that should be split in two.
* @param structurePropagation {@link StructurePropagation} used to report structure changes between tree levels.
* @param newKey key to be inserted
* @param newValue value to be inserted (in association with key)
* @param keyCount number of keys in this leaf (it was already read anyway)
* @throws IOException on cursor failure
*/
private void splitLeaf(PageCursor cursor, StructurePropagation<KEY> structurePropagation, KEY newKey, VALUE newValue, int keyCount, long stableGeneration, long unstableGeneration) throws IOException {
// To avoid moving cursor between pages we do all operations on left node first.
// Save data that needs transferring and then add it to right node.
// UPDATE SIBLINGS
//
// Before split
// newRight is leaf node to be inserted between left and oldRight
// [left] -> [oldRight]
//
// [newRight]
//
// After split
// [left] -> [newRight] -> [oldRight]
//
long current = cursor.getCurrentPageId();
long oldRight = bTreeNode.rightSibling(cursor, stableGeneration, unstableGeneration);
PointerChecking.checkPointer(oldRight, true);
long newRight = idProvider.acquireNewId(stableGeneration, unstableGeneration);
// BALANCE KEYS AND VALUES
// Two different scenarios
// Before split
// [key1]<=[key2]<=[key3]<=[key4]<=[key5] (<= greater than or equal to)
// ^
// |
// pos |
// [newKey] -----------------
//
// After split
// Left
// [key1]<=[key2]<=[key3]
//
// Right
// [newKey][key4][key5]
//
// Before split
// [key1]<=[key2]<=[key3]<=[key4]<=[key5] (<= greater than or equal to)
// ^
// | pos
// |
// [newKey]
//
// After split
// Left
// [newKey]<=[key1]<=[key2]
//
// Right
// [key3][key4][key5]
//
// CONCURRENCY
// To have readers see correct state at all times, the order of updates must be:
// 1. Acquire new page id R
// 2. Copy "right-hand" keys/values to R and set key count
// 3. Set L's right sibling to R
// 4. Set key count of L to new "left-hand" key count
// 5. Write new key/values into L
// Position where newKey / newValue is to be inserted
int pos = positionOf(search(cursor, newKey, readKey, keyCount));
int keyCountAfterInsert = keyCount + 1;
int middlePos = middle(keyCountAfterInsert);
// allKeysIncludingNewKey should now contain all keys in sorted order and
// allValuesIncludingNewValue should now contain all values in same order as corresponding keys
// and are ready to be split between left and newRight.
// We now have everything we need to start working on newRight
// and everything that needs to be updated in left has been so.
structurePropagation.hasRightKeyInsert = true;
structurePropagation.midChild = current;
structurePropagation.rightChild = newRight;
if (middlePos == pos) {
layout.copyKey(newKey, structurePropagation.rightKey);
} else {
bTreeNode.keyAt(cursor, structurePropagation.rightKey, pos < middlePos ? middlePos - 1 : middlePos);
}
// Update new right
try (PageCursor rightCursor = cursor.openLinkedCursor(newRight)) {
bTreeNode.goTo(rightCursor, "new right sibling in split", newRight);
bTreeNode.initializeLeaf(rightCursor, stableGeneration, unstableGeneration);
bTreeNode.setRightSibling(rightCursor, oldRight, stableGeneration, unstableGeneration);
bTreeNode.setLeftSibling(rightCursor, current, stableGeneration, unstableGeneration);
int rightKeyCount = keyCountAfterInsert - middlePos;
if (pos < middlePos) {
// v-------v copy
// before _,_,_,_,_,_,_,_,_,_
// insert _,_,_,X,_,_,_,_,_,_,_
// middle ^
copyKeysAndValues(cursor, middlePos - 1, rightCursor, 0, rightKeyCount);
} else {
// v---v first copy
// v-v second copy
// before _,_,_,_,_,_,_,_,_,_
// insert _,_,_,_,_,_,_,_,X,_,_
// middle ^
int countBeforePos = pos - middlePos;
if (countBeforePos > 0) {
// first copy
copyKeysAndValues(cursor, middlePos, rightCursor, 0, countBeforePos);
}
bTreeNode.insertKeyAt(rightCursor, newKey, countBeforePos, countBeforePos);
bTreeNode.insertValueAt(rightCursor, newValue, countBeforePos, countBeforePos);
int countAfterPos = keyCount - pos;
if (countAfterPos > 0) {
// second copy
copyKeysAndValues(cursor, pos, rightCursor, countBeforePos + 1, countAfterPos);
}
}
bTreeNode.setKeyCount(rightCursor, rightKeyCount);
}
// Update old right with new left sibling (newRight)
if (TreeNode.isNode(oldRight)) {
try (PageCursor oldRightCursor = cursor.openLinkedCursor(oldRight)) {
bTreeNode.goTo(oldRightCursor, "old right sibling", oldRight);
bTreeNode.setLeftSibling(oldRightCursor, newRight, stableGeneration, unstableGeneration);
}
}
// If pos < middle. Write shifted values to left node. Else, don't write anything.
if (pos < middlePos) {
bTreeNode.insertKeyAt(cursor, newKey, pos, middlePos - 1);
bTreeNode.insertValueAt(cursor, newValue, pos, middlePos - 1);
}
bTreeNode.setKeyCount(cursor, middlePos);
bTreeNode.setRightSibling(cursor, newRight, stableGeneration, unstableGeneration);
}
use of org.neo4j.io.pagecache.PageCursor in project neo4j by neo4j.
the class SeekCursor method scoutNextSibling.
/**
* Reads first key on next sibling, without moving the main {@link PageCursor} to that sibling.
* This to be able to guard for, and retry read if, concurrent writes moving keys in the "wrong" direction.
* The first key read here will be matched after actually moving the main {@link PageCursor} to
* the next sibling.
* <p>
* May only be called if {@link #pointerId} points to next sibling.
*
* @return {@code true} if first key in next sibling was read successfully, otherwise {@code false},
* which means that caller should retry most recent read.
* @throws IOException on {@link PageCursor} error.
*/
private boolean scoutNextSibling() throws IOException {
// Read header but to local variables and not global once
byte nodeType;
int keyCount = -1;
try (PageCursor scout = this.cursor.openLinkedCursor(GenerationSafePointerPair.pointer(pointerId))) {
scout.next();
nodeType = TreeNode.nodeType(scout);
if (nodeType == TreeNode.NODE_TYPE_TREE_NODE) {
keyCount = bTreeNode.keyCount(scout);
if (keyCountIsSane(keyCount)) {
int firstPos = seekForward ? 0 : keyCount - 1;
bTreeNode.keyAt(scout, expectedFirstAfterGoToNext, firstPos);
}
}
if (this.cursor.shouldRetry()) {
// on shouldRetry here.
return false;
}
checkOutOfBounds(this.cursor);
}
if (nodeType != TreeNode.NODE_TYPE_TREE_NODE || !keyCountIsSane(keyCount)) {
return false;
}
return true;
}
use of org.neo4j.io.pagecache.PageCursor in project neo4j by neo4j.
the class ConsistencyCheckerTest method shouldThrowDescriptiveExceptionOnBrokenGSPP.
@Test
public void shouldThrowDescriptiveExceptionOnBrokenGSPP() throws Exception {
// GIVEN
int pageSize = 256;
PageCursor cursor = new PageAwareByteArrayCursor(pageSize);
Layout<MutableLong, MutableLong> layout = new SimpleLongLayout();
TreeNode<MutableLong, MutableLong> treeNode = new TreeNode<>(pageSize, layout);
long stableGeneration = MIN_GENERATION;
long crashGeneration = stableGeneration + 1;
long unstableGeneration = stableGeneration + 2;
String pointerFieldName = "abc";
long pointer = 123;
cursor.next(0);
treeNode.initializeInternal(cursor, stableGeneration, crashGeneration);
treeNode.setSuccessor(cursor, pointer, stableGeneration, crashGeneration);
// WHEN
try {
assertNoCrashOrBrokenPointerInGSPP(cursor, stableGeneration, unstableGeneration, pointerFieldName, TreeNode.BYTE_POS_SUCCESSOR, treeNode);
cursor.checkAndClearCursorException();
fail("Should have failed");
} catch (CursorException e) {
// THEN
assertThat(e.getMessage(), containsString(pointerFieldName));
assertThat(e.getMessage(), containsString(pointerFieldName));
assertThat(e.getMessage(), containsString("state=CRASH"));
assertThat(e.getMessage(), containsString("state=EMPTY"));
assertThat(e.getMessage(), containsString(String.valueOf(pointer)));
}
}
use of org.neo4j.io.pagecache.PageCursor in project neo4j by neo4j.
the class ConsistencyCheckerTest method shouldDetectUnusedPages.
@Test
public void shouldDetectUnusedPages() throws Exception {
// GIVEN
int pageSize = 256;
Layout<MutableLong, MutableLong> layout = new SimpleLongLayout();
TreeNode<MutableLong, MutableLong> node = new TreeNode<>(pageSize, layout);
long stableGeneration = GenerationSafePointer.MIN_GENERATION;
long unstableGeneration = stableGeneration + 1;
SimpleIdProvider idProvider = new SimpleIdProvider();
InternalTreeLogic<MutableLong, MutableLong> logic = new InternalTreeLogic<>(idProvider, node, layout);
PageCursor cursor = new PageAwareByteArrayCursor(pageSize);
cursor.next(idProvider.acquireNewId(stableGeneration, unstableGeneration));
node.initializeLeaf(cursor, stableGeneration, unstableGeneration);
logic.initialize(cursor);
StructurePropagation<MutableLong> structure = new StructurePropagation<>(layout.newKey(), layout.newKey(), layout.newKey());
MutableLong key = layout.newKey();
for (int g = 0, k = 0; g < 3; g++) {
for (int i = 0; i < 100; i++, k++) {
key.setValue(k);
logic.insert(cursor, structure, key, key, ValueMergers.overwrite(), stableGeneration, unstableGeneration);
if (structure.hasRightKeyInsert) {
goTo(cursor, "new root", idProvider.acquireNewId(stableGeneration, unstableGeneration));
node.initializeInternal(cursor, stableGeneration, unstableGeneration);
node.insertKeyAt(cursor, structure.rightKey, 0, 0);
node.setKeyCount(cursor, 1);
node.setChildAt(cursor, structure.midChild, 0, stableGeneration, unstableGeneration);
node.setChildAt(cursor, structure.rightChild, 1, stableGeneration, unstableGeneration);
logic.initialize(cursor);
}
if (structure.hasMidChildUpdate) {
logic.initialize(cursor);
}
structure.clear();
}
stableGeneration = unstableGeneration;
unstableGeneration++;
}
// WHEN
ConsistencyChecker<MutableLong> cc = new ConsistencyChecker<>(node, layout, stableGeneration, unstableGeneration);
try {
cc.checkSpace(cursor, idProvider.lastId(), PrimitiveLongCollections.emptyIterator());
fail("Should have failed");
} catch (RuntimeException e) {
// THEN good
assertThat(e.getMessage(), containsString("unused pages"));
}
}
Aggregations