diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/GenSafePointerPair.java b/community/index/src/main/java/org/neo4j/index/gbptree/GenSafePointerPair.java index 322c3cab0a8c7..111657d5fdee5 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/GenSafePointerPair.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/GenSafePointerPair.java @@ -262,71 +262,8 @@ public static long write( PageCursor cursor, long pointer, long stableGeneration GenSafePointer.write( cursor, unstableGeneration, pointer ); } return writeResult; - -// TODO ANOTHER APPROACH, keep here for reference and pursuit later -// // Select correct slot -// boolean writeToSlotA; -// -// boolean aIsValid = correctChecksumA && (generationA <= stableGeneration || generationA == unstableGeneration); -// boolean bIsValid = correctChecksumB && (generationB <= stableGeneration || generationB == unstableGeneration); -// -// // Failure cases -// // - both invalid -// if ( !aIsValid && !bIsValid ) -// { -// return false; -// } -// // - both valid and same generation, but not empty -// if ( aIsValid && bIsValid && generationA == generationB && generationA >= MIN_GENERATION ) -// { -// return false; -// } -// -// // Prioritized selection -// // - one with unstable generation -// if ( (aIsValid && generationA == unstableGeneration) || (bIsValid && generationB == unstableGeneration) ) -// { -// writeToSlotA = aIsValid && generationA == unstableGeneration; -// } -// // - exactly one invalid -// else if ( !aIsValid || !bIsValid ) -// { -// writeToSlotA = !aIsValid; -// } -// // - empty -// else if ( isEmpty( generationA, pointerA ) || isEmpty( generationB, pointerB ) ) -// { -// writeToSlotA = isEmpty( generationA, pointerA ); -// } -// // - lowest generation -// else -// { -// writeToSlotA = generationA < generationB; -// } -// -// // And write -// int writeOffset = writeToSlotA ? offset : offset + SIZE; -// cursor.setOffset( writeOffset ); -// GenSafePointer.write( cursor, unstableGeneration, pointer ); -// return true; } - // All different ordered combinations of pointer states and if they are considered to be an ok state to see - // STABLE,STABLE - OK if different generation - // STABLE,UNSTABLE - OK - // STABLE,CRASH - OK - // STABLE,BROKEN - OK - // STABLE,EMPTY - OK - // UNSTABLE,UNSTABLE - NOT OK - // UNSTABLE,CRASH - NOT OK - // UNSTABLE,BROKEN - NOT OK - // UNSTABLE,EMPTY - OK - // CRASH,CRASH - NOT OK - // CRASH,BROKEN - NOT OK - // CRASH,EMPTY - NOT OK - // BROKEN,BROKEN - NOT OK - // BROKEN,EMPTY - NOT OK - // EMPTY,EMPTY - OK if writing, not if reading private static long writeResult( byte pointerStateA, byte pointerStateB, long generationA, long generationB ) { if ( pointerStateA == STABLE ) @@ -387,6 +324,23 @@ private static long generationState( long generationA, long generationB ) return generationA > generationB ? FLAG_GEN_A_BIG : generationB > generationA ? FLAG_GEN_B_BIG : FLAG_GEN_EQUAL; } + /** + * Pointer state of a GSP (generation, pointer, checksum). Can be any of: + * + * + * @param stableGeneration stable generation. + * @param unstableGeneration unstable generation. + * @param generation GSP generation. + * @param pointer GSP pointer. + * @param checksumIsCorrect whether or not GSP checksum matches checksum of {@code generation} and {@code pointer}. + * @return one of the available pointer states. + */ static byte pointerState( long stableGeneration, long unstableGeneration, long generation, long pointer, boolean checksumIsCorrect ) { @@ -498,6 +452,12 @@ else if ( bits == FLAG_GEN_B_BIG ) } } + /** + * Name of the provided {@code pointerState} gotten from {@link #pointerState(long, long, long, long, boolean)}. + * + * @param pointerState pointer state to get name for. + * @return name of {@code pointerState}. + */ static String pointerStateName( byte pointerState ) { switch ( pointerState ) diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/Generation.java b/community/index/src/main/java/org/neo4j/index/gbptree/Generation.java index b0946dda3f7ba..93d6632999c6e 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/Generation.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/Generation.java @@ -32,6 +32,13 @@ class Generation private static final long UNSTABLE_GENERATION_MASK = 0xFFFFFFFFL; private static final int STABLE_GENERATION_SHIFT = Integer.SIZE; + /** + * Takes one stable and one unstable generation (both unsigned ints) and crams them into one {@code long}. + * + * @param stableGeneration stable generation. + * @param unstableGeneration unstable generation. + * @return the two generation numbers as one {@code long}. + */ public static long generation( long stableGeneration, long unstableGeneration ) { GenSafePointer.assertGenerationOnWrite( stableGeneration ); @@ -40,13 +47,25 @@ public static long generation( long stableGeneration, long unstableGeneration ) return (stableGeneration << STABLE_GENERATION_SHIFT) | unstableGeneration; } - public static long unstableGeneration( long rawGeneration ) + /** + * Extracts and returns unstable generation from generation {@code long}. + * + * @param generation generation variable containing both stable and unstable generations. + * @return unstable generation from generation. + */ + public static long unstableGeneration( long generation ) { - return rawGeneration & UNSTABLE_GENERATION_MASK; + return generation & UNSTABLE_GENERATION_MASK; } - public static long stableGeneration( long rawGeneration ) + /** + * Extracts and returns stable generation from generation {@code long}. + * + * @param generation generation variable containing both stable and unstable generations. + * @return stable generation from generation. + */ + public static long stableGeneration( long generation ) { - return rawGeneration >>> STABLE_GENERATION_SHIFT; + return generation >>> STABLE_GENERATION_SHIFT; } } diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/IdProvider.java b/community/index/src/main/java/org/neo4j/index/gbptree/IdProvider.java index 4029f9b1a736d..f101897692985 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/IdProvider.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/IdProvider.java @@ -21,13 +21,33 @@ import java.io.IOException; +import org.neo4j.io.pagecache.PageCursor; + /** * Provide tree node (page) ids which can be used for storing tree node data. * Bytes on returned page ids must be empty (all zeros). */ interface IdProvider { + /** + * Acquires a page id, guaranteed to currently not be used. The bytes on the page at this id + * are all guaranteed to be zero at the point of returning from this method. + * + * @param stableGeneration current stable generation. + * @param unstableGeneration current unstable generation. + * @return page id guaranteed to current not be used and whose bytes are all zeros. + * @throws IOException on {@link PageCursor} error. + */ long acquireNewId( long stableGeneration, long unstableGeneration ) throws IOException; + /** + * Releases a page id which has previously been used, but isn't anymore, effectively allowing + * it to be reused and returned from {@link #acquireNewId(long, long)}. + * + * @param stableGeneration current stable generation. + * @param unstableGeneration current unstable generation. + * @param id page id to release. + * @throws IOException on {@link PageCursor} error. + */ void releaseId( long stableGeneration, long unstableGeneration, long id ) throws IOException; } diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/KeySearch.java b/community/index/src/main/java/org/neo4j/index/gbptree/KeySearch.java index 629fc2b6e2a81..bd69f17b78f57 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/KeySearch.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/KeySearch.java @@ -23,6 +23,9 @@ import org.neo4j.io.pagecache.PageCursor; +/** + * Methods for (binary-)searching keys in a tree node. + */ class KeySearch { private static final int POSITION_MASK = 0x7FFFFFFF; @@ -45,8 +48,11 @@ class KeySearch * @param key KEY to search for * @param readKey KEY to use as temporary storage during calculation. * @param keyCount number of keys in node when starting search - * @return first position i for which bTreeNode.keyComparator().compare( key, bTreeNode.keyAt( i ) <= 0, - * or keyCount if no such key exists. + * @return search result where least significant 31 bits are first position i for which + * bTreeNode.keyComparator().compare( key, bTreeNode.keyAt( i ) <= 0, or keyCount if no such key exists. + * highest bit (sign bit) says whether or not the exact key was found in the node, if so set to 1, otherwise 0. + * To extract position from the returned search result, then use {@link #positionOf(int)}. + * To extract whether or not the exact key was found, then use {@link #isHit(int)}. */ static int search( PageCursor cursor, TreeNode bTreeNode, KEY key, KEY readKey, int keyCount ) @@ -115,11 +121,24 @@ private static int searchResult( int pos, boolean hit ) return (pos & POSITION_MASK) | ((hit ? 1 : 0) << 31); } + /** + * Extracts the position from a search result from {@link #search(PageCursor, TreeNode, Object, Object, int)}. + * + * @param searchResult search result from {@link #search(PageCursor, TreeNode, Object, Object, int)}. + * @return position of the search result. + */ static int positionOf( int searchResult ) { return searchResult & POSITION_MASK; } + /** + * Extracts whether or not the searched key was found from search result from + * {@link #search(PageCursor, TreeNode, Object, Object, int)}. + * + * @param searchResult search result form {@link #search(PageCursor, TreeNode, Object, Object, int)}. + * @return whether or not the searched key was found. + */ static boolean isHit( int searchResult ) { return (searchResult & HIT_MASK) != 0; diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/PointerChecking.java b/community/index/src/main/java/org/neo4j/index/gbptree/PointerChecking.java index 0b9937bf452d7..8071459241739 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/PointerChecking.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/PointerChecking.java @@ -21,6 +21,9 @@ import org.neo4j.io.pagecache.PageCursor; +/** + * Methods for ensuring a read {@link GenSafePointer GSP pointer} is valid. + */ class PointerChecking { /** diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/TreeState.java b/community/index/src/main/java/org/neo4j/index/gbptree/TreeState.java index 59ccef90878a5..541a202270a40 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/TreeState.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/TreeState.java @@ -24,11 +24,38 @@ import org.neo4j.io.pagecache.PageCache; import org.neo4j.io.pagecache.PageCursor; +/** + * Tree state is defined as top level tree meta data which changes as the tree and its constructs changes, such as: + * + * This class also knows how to {@link #write(PageCursor, long, long, long, long, long, long, long, int, int)} + * and {@link #read(PageCursor)} tree state to and from a {@link PageCursor}, although doesn't care where + * in the store that is. + */ class TreeState { + /** + * Page id this tree state has been read from. + */ private final long pageId; + + /** + * Stable generation of the tree. + */ private final long stableGeneration; + + /** + * Unstable generation of the tree. + */ private final long unstableGeneration; + + /** + * Page id which is the root of the tree. + */ private final long rootId; /** @@ -41,10 +68,32 @@ class TreeState * since {@link PageCache} doesn't allow shrinking files. */ private final long lastId; + + /** + * Page id to write new released tree node ids into. + */ private final long freeListWritePageId; + + /** + * Page id to read released tree node ids from, when acquiring ids. + */ private final long freeListReadPageId; + + /** + * Offset in page {@link #freeListWritePageId} to write new released tree node ids at. + */ private final int freeListWritePos; + + /** + * Offset in page {@link #freeListReadPageId} to read released tree node ids from, when acquiring ids. + */ private final int freeListReadPos; + + /** + * Due to writing with potential concurrent page flushing tree state is written twice, the second + * state acting as checksum. If both states match this variable should be set to {@code true}, + * otherwise to {@code false}. + */ private boolean valid; TreeState( long pageId, long stableGeneration, long unstableGeneration, long rootId, long rootGen, long lastId, @@ -147,6 +196,13 @@ static void write( PageCursor cursor, long stableGeneration, long unstableGenera freeListWritePageId, freeListReadPageId, freeListWritePos, freeListReadPos ); // Write checksum } + /** + * Reads tree state from {@code cursor} at its current offset. If checksum matches then {@link #valid} + * is set to {@code true}, otherwise {@code false}. + * + * @param cursor {@link PageCursor} to read tree state from, at its current offset. + * @return {@link TreeState} instance containing read tree state. + */ static TreeState read( PageCursor cursor ) { TreeState state = readStateOnce( cursor ); diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/TreeStatePair.java b/community/index/src/main/java/org/neo4j/index/gbptree/TreeStatePair.java index 8410898762eb7..b60dd6a29344b 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/TreeStatePair.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/TreeStatePair.java @@ -26,8 +26,23 @@ import org.neo4j.io.pagecache.PageCursor; +/** + * Pair of {@link TreeState}, ability to make decision about which of the two to read and write respectively, + * depending on the {@link TreeState#isValid() validity} and {@link TreeState#stableGeneration()} of each. + */ class TreeStatePair { + /** + * Reads the tree state pair, one from each of {@code pageIdA} and {@code pageIdB}, deciding their validity + * and returning them as a {@link Pair}. + * + * @param cursor {@link PageCursor} to use when reading. This cursor will be moved to the two pages + * one after the other, to read their states. + * @param pageIdA page id containing the first state. + * @param pageIdB page id containing the second state. + * @return {@link Pair} of both tree states. + * @throws IOException on {@link PageCursor} reading error. + */ static Pair readStatePages( PageCursor cursor, long pageIdA, long pageIdB ) throws IOException { TreeState stateA; @@ -43,6 +58,12 @@ static Pair readStatePages( PageCursor cursor, long pageIdA return Pair.of( stateA, stateB ); } + /** + * @param states the two states to compare. + * @return newest (w/ regards to {@link TreeState#stableGeneration()}) {@link TreeState#isValid() valid} + * {@link TreeState} of the two. + * @throws IllegalStateException if none were valid. + */ static TreeState selectNewestValidState( Pair states ) { return selectNewestValidStateOptionally( states ).orElseThrow( () -> @@ -50,6 +71,11 @@ static TreeState selectNewestValidState( Pair states ) states.getLeft(), states.getRight() ) ); } + /** + * @param states the two states to compare. + * @return oldest (w/ regards to {@link TreeState#stableGeneration()}) {@link TreeState#isValid() invalid} + * {@link TreeState} of the two. If both are invalid then the {@link Pair#getLeft() first one} is returned. + */ static TreeState selectOldestOrInvalid( Pair states ) { TreeState newestValidState = selectNewestValidStateOptionally( states ).orElse( states.getRight() );