diff --git a/community/index/src/main/java/org/neo4j/index/gbptree/GBPTree.java b/community/index/src/main/java/org/neo4j/index/gbptree/GBPTree.java index 9c15a6a4ff5f5..8f7d0a53260a5 100644 --- a/community/index/src/main/java/org/neo4j/index/gbptree/GBPTree.java +++ b/community/index/src/main/java/org/neo4j/index/gbptree/GBPTree.java @@ -19,6 +19,8 @@ */ package org.neo4j.index.gbptree; +import org.apache.commons.lang3.tuple.Pair; + import java.io.File; import java.io.IOException; import java.nio.file.NoSuchFileException; @@ -125,14 +127,14 @@ public class GBPTree implements Index, IdProvider * Stable generation, i.e. generation which has survived the last {@link #flush()}. * Unsigned int. */ - private volatile long stableGeneration = 0; + private volatile long stableGeneration = GenSafePointer.MIN_GENERATION; /** * Unstable generation, i.e. the current generation under evolution. This generation will be the * {@link #stableGeneration} in the next {@link #flush()}. * Unsigned int. */ - private volatile long unstableGeneration = 1; + private volatile long unstableGeneration = stableGeneration + 1; /** * Opens an index {@code indexFile} in the {@code pageCache}, creating and initializing it if it doesn't exist. @@ -182,49 +184,9 @@ private PagedFile openOrCreate( PageCache pageCache, File indexFile, try { - // Read header - long layoutIdentifier; - int majorVersion; - int minorVersion; - try ( PageCursor metaCursor = openMetaPageCursor( pagedFile ) ) - { - do - { - pageSize = metaCursor.getInt(); - rootId = metaCursor.getLong(); - lastId = metaCursor.getLong(); - layoutIdentifier = metaCursor.getLong(); - majorVersion = metaCursor.getInt(); - minorVersion = metaCursor.getInt(); - layout.readMetaData( metaCursor ); - } - while ( metaCursor.shouldRetry() ); - checkOutOfBounds( metaCursor ); - } - if ( layoutIdentifier != layout.identifier() ) - { - throw new IllegalArgumentException( "Tried to open " + indexFile + " using different layout " - + layout.identifier() + " than what it was created with " + layoutIdentifier ); - } - if ( majorVersion != layout.majorVersion() || minorVersion != layout.minorVersion() ) - { - throw new IllegalArgumentException( "Index is of another version than the layout " + - "it tries to be opened with. Index version is [" + majorVersion + "." + minorVersion + "]" + - ", but tried to load the index with version [" + - layout.majorVersion() + "." + layout.minorVersion() + "]" ); - } - // This index was created with another page size, re-open with that actual page size - if ( pageSize != pageCache.pageSize() ) - { - if ( pageSize > pageCache.pageSize() ) - { - throw new IllegalStateException( "Index was created with page size:" + pageSize - + ", but page cache used to open it this time has a smaller page size:" - + pageCache.pageSize() + " so cannot be opened" ); - } - pagedFile.close(); - pagedFile = pageCache.map( indexFile, pageSize ); - } + readMeta( indexFile, layout, pagedFile ); + pagedFile = mapWithCorrectPageSize( pageCache, indexFile, pagedFile ); + readState( pagedFile ); return pagedFile; } catch ( Throwable t ) @@ -242,6 +204,7 @@ private PagedFile openOrCreate( PageCache pageCache, File indexFile, } catch ( NoSuchFileException e ) { + // First time. Initiate meta and state page pageSize = pageSizeForCreation == 0 ? pageCache.pageSize() : pageSizeForCreation; if ( pageSize > pageCache.pageSize() ) { @@ -254,23 +217,46 @@ private PagedFile openOrCreate( PageCache pageCache, File indexFile, PagedFile pagedFile = pageCache.map( indexFile, pageSize, StandardOpenOption.CREATE ); // Write header - try ( PageCursor metaCursor = openMetaPageCursor( pagedFile ) ) - { - metaCursor.putInt( pageSize ); - metaCursor.putLong( rootId ); - metaCursor.putLong( lastId ); - metaCursor.putLong( layout.identifier() ); - metaCursor.putInt( layout.majorVersion() ); - metaCursor.putInt( layout.minorVersion() ); - layout.writeMetaData( metaCursor ); - checkOutOfBounds( metaCursor ); - } + writeMeta( layout, pagedFile ); + writeState( pagedFile ); pagedFile.flushAndForce(); created = true; return pagedFile; } } + private void readState( PagedFile pagedFile ) throws IOException + { + try ( PageCursor cursor = pagedFile.io( 0L /*ignored*/, PagedFile.PF_SHARED_WRITE_LOCK ) ) + { + Pair states = TreeStatePair.readStatePages( + cursor, IdSpace.STATE_PAGE_A, IdSpace.STATE_PAGE_B ); + + TreeState state = TreeStatePair.selectNewestValidState( states ); + rootId = state.rootId(); + lastId = state.lastId(); + stableGeneration = state.stableGeneration(); + unstableGeneration = state.unstableGeneration(); + } + } + + private void writeState( PagedFile pagedFile ) throws IOException + { + try ( PageCursor cursor = pagedFile.io( 0L /*ignored*/, PagedFile.PF_SHARED_WRITE_LOCK ) ) + { + Pair states = TreeStatePair.readStatePages( + cursor, IdSpace.STATE_PAGE_A, IdSpace.STATE_PAGE_B ); + TreeState oldestState = TreeStatePair.selectOldestOrInvalid( states ); + long pageToOverwrite = oldestState.pageId(); + if ( !cursor.next( pageToOverwrite ) ) + { + throw new IllegalStateException( "Could not go to state page " + pageToOverwrite ); + } + cursor.setOffset( 0 ); + TreeState.write( cursor, stableGeneration, unstableGeneration, rootId, lastId ); + } + } + private PageCursor openMetaPageCursor( PagedFile pagedFile ) throws IOException { PageCursor metaCursor = pagedFile.io( IdSpace.META_PAGE_ID, PagedFile.PF_SHARED_WRITE_LOCK ); @@ -281,6 +267,70 @@ private PageCursor openMetaPageCursor( PagedFile pagedFile ) throws IOException return metaCursor; } + private void readMeta( File indexFile, Layout layout, PagedFile pagedFile ) + throws IOException + { + // Read meta + long layoutIdentifier; + int majorVersion; + int minorVersion; + try ( PageCursor metaCursor = openMetaPageCursor( pagedFile ) ) + { + do + { + pageSize = metaCursor.getInt(); + layoutIdentifier = metaCursor.getLong(); + majorVersion = metaCursor.getInt(); + minorVersion = metaCursor.getInt(); + layout.readMetaData( metaCursor ); + } + while ( metaCursor.shouldRetry() ); + checkOutOfBounds( metaCursor ); + } + if ( layoutIdentifier != layout.identifier() ) + { + throw new IllegalArgumentException( "Tried to open " + indexFile + " using different layout " + + layout.identifier() + " than what it was created with " + layoutIdentifier ); + } + if ( majorVersion != layout.majorVersion() || minorVersion != layout.minorVersion() ) + { + throw new IllegalArgumentException( "Index is of another version than the layout " + + "it tries to be opened with. Index version is [" + majorVersion + "." + minorVersion + "]" + + ", but tried to load the index with version [" + + layout.majorVersion() + "." + layout.minorVersion() + "]" ); + } + } + + private void writeMeta( Layout layout, PagedFile pagedFile ) throws IOException + { + try ( PageCursor metaCursor = openMetaPageCursor( pagedFile ) ) + { + metaCursor.putInt( pageSize ); + metaCursor.putLong( layout.identifier() ); + metaCursor.putInt( layout.majorVersion() ); + metaCursor.putInt( layout.minorVersion() ); + layout.writeMetaData( metaCursor ); + checkOutOfBounds( metaCursor ); + } + } + + private PagedFile mapWithCorrectPageSize( PageCache pageCache, File indexFile, PagedFile pagedFile ) throws IOException + { + // This index was created with another page size, re-open with that actual page size + if ( pageSize != pageCache.pageSize() ) + { + if ( pageSize > pageCache.pageSize() ) + { + throw new IllegalStateException( "Index was created with page size:" + pageSize + + ", but page cache used to open it this time has a smaller page size:" + + pageCache.pageSize() + " so cannot be opened" ); + } + pagedFile.close(); + return pageCache.map( indexFile, pageSize ); + } + return pagedFile; + } + @Override public RawCursor,IOException> seek( KEY fromInclusive, KEY toExclusive ) throws IOException { @@ -338,37 +388,12 @@ public long acquireNewId() return lastId; } - // Utility method - public void printTree() throws IOException - { - try ( PageCursor cursor = pagedFile.io( rootId, PagedFile.PF_SHARED_READ_LOCK ) ) - { - cursor.next(); - TreePrinter.printTree( cursor, bTreeNode, layout, stableGeneration, unstableGeneration, System.out ); - } - } - - // Utility method - boolean consistencyCheck() throws IOException - { - try ( PageCursor cursor = pagedFile.io( rootId, PagedFile.PF_SHARED_READ_LOCK ) ) - { - cursor.next(); - return new ConsistencyChecker<>( bTreeNode, layout, stableGeneration, unstableGeneration ) - .check( cursor ); - } - } - @Override public void flush() throws IOException { - try ( PageCursor cursor = openMetaPageCursor( pagedFile ) ) - { - cursor.putLong( 4, rootId ); - cursor.putLong( 12, lastId ); - // generations should be incremented as part of flush, but this functionality doesn't exist yet. - checkOutOfBounds( cursor ); - } + stableGeneration = unstableGeneration; + unstableGeneration++; + writeState( pagedFile ); } @Override @@ -408,6 +433,27 @@ private void checkOutOfBounds( PageCursor cursor ) } } + // Utility method + public void printTree() throws IOException + { + try ( PageCursor cursor = pagedFile.io( rootId, PagedFile.PF_SHARED_READ_LOCK ) ) + { + cursor.next(); + TreePrinter.printTree( cursor, bTreeNode, layout, stableGeneration, unstableGeneration, System.out ); + } + } + + // Utility method + boolean consistencyCheck() throws IOException + { + try ( PageCursor cursor = pagedFile.io( rootId, PagedFile.PF_SHARED_READ_LOCK ) ) + { + cursor.next(); + return new ConsistencyChecker<>( bTreeNode, layout, stableGeneration, unstableGeneration ) + .check( cursor ); + } + } + private class SingleIndexWriter implements IndexWriter { private final InternalTreeLogic treeLogic; 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 new file mode 100644 index 0000000000000..6980f3417cb4b --- /dev/null +++ b/community/index/src/main/java/org/neo4j/index/gbptree/TreeState.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.gbptree; + +import java.io.IOException; + +import org.neo4j.io.pagecache.PageCursor; + +class TreeState +{ + private final long pageId; + private final long stableGeneration; + private final long unstableGeneration; + private final long rootId; + private final long lastId; + private final boolean valid; + + TreeState( long pageId, long stableGeneration, long unstableGeneration, long rootId, long lastId, boolean valid ) + { + this.pageId = pageId; + this.stableGeneration = stableGeneration; + this.unstableGeneration = unstableGeneration; + this.rootId = rootId; + this.lastId = lastId; + this.valid = valid; + } + + long pageId() + { + return pageId; + } + + long stableGeneration() + { + return stableGeneration; + } + + long unstableGeneration() + { + return unstableGeneration; + } + + long rootId() + { + return rootId; + } + + long lastId() + { + return lastId; + } + + boolean isValid() + { + return valid; + } + + static void write( PageCursor cursor, long stableGeneration, long unstableGeneration, long rootId, + long lastId ) throws IOException + { + GenSafePointer.assertGeneration( stableGeneration ); + GenSafePointer.assertGeneration( unstableGeneration ); + + + writeStateOnce( cursor, stableGeneration, unstableGeneration, rootId, lastId ); // Write state + writeStateOnce( cursor, stableGeneration, unstableGeneration, rootId, lastId ); // Write checksum + } + + static TreeState read( PageCursor cursor ) + { + long pageId = cursor.getCurrentPageId(); + + long stableGeneration = cursor.getInt() & GenSafePointer.GENERATION_MASK; + long unstableGeneration = cursor.getInt() & GenSafePointer.GENERATION_MASK; + long rootId = cursor.getLong(); + long lastId = cursor.getLong(); + + long checksumStableGeneration = cursor.getInt() & GenSafePointer.GENERATION_MASK; + long checksumUnstableGeneration = cursor.getInt() & GenSafePointer.GENERATION_MASK; + long checksumRootId = cursor.getLong(); + long checksumLastId = cursor.getLong(); + + boolean valid = stableGeneration == checksumStableGeneration && + unstableGeneration == checksumUnstableGeneration && + rootId == checksumRootId && + lastId == checksumLastId; + + boolean isEmpty = stableGeneration == 0L && unstableGeneration == 0L && rootId == 0L && lastId == 0L; + valid &= !isEmpty; + + return new TreeState( pageId, stableGeneration, unstableGeneration, rootId, lastId, valid ); + } + + private static void writeStateOnce( PageCursor cursor, long stableGeneration, long unstableGeneration, + long rootId, long lastId ) + { + cursor.putInt( (int) stableGeneration ); + cursor.putInt( (int) unstableGeneration ); + cursor.putLong( rootId ); + cursor.putLong( lastId ); + } + + @Override + public String toString() + { + return String.format( "pageId=%d, stableGeneration=%d, unstableGeneration=%d, rootId=%s, lastId=%d, valid=%b", + pageId, stableGeneration, unstableGeneration, rootId, lastId, valid ); + } +} 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 new file mode 100644 index 0000000000000..d74af89d91cdc --- /dev/null +++ b/community/index/src/main/java/org/neo4j/index/gbptree/TreeStatePair.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.gbptree; + +import org.apache.commons.lang3.tuple.Pair; + +import java.io.IOException; + +import org.neo4j.io.pagecache.PageCursor; + +import static java.lang.String.format; + +public class TreeStatePair +{ + static Pair readStatePages( PageCursor cursor, long pageIdA, long pageIdB ) throws IOException + { + TreeState stateA; + TreeState stateB; + + // Use write lock to avoid should retry. Fine because there can be no concurrency at this point anyway + + if ( !cursor.next( pageIdA ) ) + { + throw new IllegalStateException( "Could not open STATE_PAGE_A " + IdSpace.STATE_PAGE_A ); + } + stateA = TreeState.read( cursor ); + + if ( !cursor.next( pageIdB ) ) + { + throw new IllegalStateException( "Could not open STATE_PAGE_B " + IdSpace.STATE_PAGE_B ); + } + stateB = TreeState.read( cursor ); + return Pair.of( stateA, stateB ); + } + + static TreeState selectNewestValidState( Pair states ) + { + TreeState selected = selectNewestValidStateOrNull( states ); + if ( selected != null ) + { + return selected; + } + + // Fail + throw new IllegalStateException( format( "Unexpected combination of state.%n STATE_A=%s%n STATE_B=%s", + states.getLeft(), states.getRight() ) ); + } + + static TreeState selectOldestOrInvalid( Pair states ) + { + TreeState newestValidState = selectNewestValidStateOrNull( states ); + if ( newestValidState == null ) + { + return states.getLeft(); + } + return newestValidState == states.getLeft() ? states.getRight() : states.getLeft(); + } + + private static TreeState selectNewestValidStateOrNull( Pair states ) + { + TreeState stateA = states.getLeft(); + TreeState stateB = states.getRight(); + + if ( stateA.isValid() != stateB.isValid() ) + { + // return only valid + return stateA.isValid() ? stateA : stateB; + } + else if ( stateA.isValid() && stateB.isValid() ) + { + // return newest + if ( stateA.stableGeneration() > stateB.stableGeneration() && + stateA.unstableGeneration() > stateB.unstableGeneration() ) + { + return stateA; + } + else if ( stateA.stableGeneration() < stateB.stableGeneration() && + stateA.unstableGeneration() < stateB.unstableGeneration() ) + { + return stateB; + } + } + + // return null communicating that this combination didn't result in any valid "newest" state + return null; + } +} diff --git a/community/index/src/test/java/org/neo4j/index/gbptree/PageAwareByteArrayCursor.java b/community/index/src/test/java/org/neo4j/index/gbptree/PageAwareByteArrayCursor.java index 3534a6982fa6e..e69896aea7843 100644 --- a/community/index/src/test/java/org/neo4j/index/gbptree/PageAwareByteArrayCursor.java +++ b/community/index/src/test/java/org/neo4j/index/gbptree/PageAwareByteArrayCursor.java @@ -41,7 +41,12 @@ class PageAwareByteArrayCursor extends PageCursor PageAwareByteArrayCursor( int pageSize ) { - this( new ArrayList<>(), pageSize, 0 ); + this( pageSize, 0 ); + } + + PageAwareByteArrayCursor( int pageSize, long nextPageId ) + { + this( new ArrayList<>(), pageSize, nextPageId ); } private PageAwareByteArrayCursor( List pages, int pageSize, long nextPageId ) diff --git a/community/index/src/test/java/org/neo4j/index/gbptree/TreeStatePairTest.java b/community/index/src/test/java/org/neo4j/index/gbptree/TreeStatePairTest.java new file mode 100644 index 0000000000000..c59fcd82fc6c7 --- /dev/null +++ b/community/index/src/test/java/org/neo4j/index/gbptree/TreeStatePairTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.gbptree; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.neo4j.io.pagecache.PageCursor; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +@RunWith( Parameterized.class ) +public class TreeStatePairTest +{ + @Parameterized.Parameters( name = "{0},{1}" ) + public static Collection variants() + { + // State A, State B, Select newest, Select oldest + + Collection variants = new ArrayList<>(); + variants.add( new Object[] {State.EMPTY, State.EMPTY, Selected.FAIL, Selected.A} ); + variants.add( new Object[] {State.EMPTY, State.BROKEN, Selected.FAIL, Selected.A} ); + variants.add( new Object[] {State.EMPTY, State.VALID, Selected.B, Selected.A} ); + + variants.add( new Object[] {State.BROKEN, State.EMPTY, Selected.FAIL, Selected.A} ); + variants.add( new Object[] {State.BROKEN, State.BROKEN, Selected.FAIL, Selected.A} ); + variants.add( new Object[] {State.BROKEN, State.VALID, Selected.B, Selected.A} ); + + variants.add( new Object[] {State.VALID, State.EMPTY, Selected.A, Selected.B} ); + variants.add( new Object[] {State.VALID, State.BROKEN, Selected.A, Selected.B} ); + variants.add( new Object[] {State.VALID, State.OLD_VALID, Selected.A, Selected.B} ); + variants.add( new Object[] {State.VALID, State.VALID, Selected.FAIL, Selected.A} ); + variants.add( new Object[] {State.OLD_VALID, State.VALID, Selected.B, Selected.A} ); + return variants; + } + + private static final long PAGE_A = 1; + private static final long PAGE_B = 2; + + @Parameterized.Parameter( 0 ) + public State stateA; + @Parameterized.Parameter( 1 ) + public State stateB; + @Parameterized.Parameter( 2 ) + public Selected expectedNewest; + @Parameterized.Parameter( 3 ) + public Selected expectedOldest; + + private final PageAwareByteArrayCursor cursor = new PageAwareByteArrayCursor( 256 ); + + @Test + public void shouldCorrectSelectNewestAndOldestState() throws Exception + { + // GIVEN + cursor.next( PAGE_A ); + stateA.write( cursor ); + cursor.next( PAGE_B ); + stateB.write( cursor ); + + // WHEN + Pair states = TreeStatePair.readStatePages( cursor, PAGE_A, PAGE_B ); + + // THEN + expectedNewest.verify( states, SelectionUseCase.NEWEST ); + expectedOldest.verify( states, SelectionUseCase.OLDEST ); + } + + enum SelectionUseCase + { + NEWEST + { + @Override + TreeState select( Pair states ) + { + return TreeStatePair.selectNewestValidState( states ); + } + }, + OLDEST + { + @Override + TreeState select( Pair states ) + { + return TreeStatePair.selectOldestOrInvalid( states ); + } + }; + + abstract TreeState select( Pair states ); + } + + enum State + { + EMPTY + { + @Override + void write( PageCursor cursor ) + { + // Nothing to write + } + }, + BROKEN + { + @Override + void write( PageCursor cursor ) throws IOException + { + TreeState.write( cursor, 1, 2, 3, 4 ); + cursor.rewind(); + // flip some of the bits as to break the checksum + long someOfTheBits = cursor.getLong( cursor.getOffset() ); + cursor.putLong( cursor.getOffset(), ~someOfTheBits ); + } + }, + VALID + { + @Override + void write( PageCursor cursor ) throws IOException + { + TreeState.write( cursor, 5, 6, 7, 8 ); + } + }, + OLD_VALID + { + @Override + void write( PageCursor cursor ) throws IOException + { + TreeState.write( cursor, 2, 3, 4, 5 ); + } + }; + + abstract void write( PageCursor cursor ) throws IOException; + } + + enum Selected + { + FAIL + { + @Override + void verify( Pair states, SelectionUseCase selection ) + { + try + { + selection.select( states ); + fail( "Should have thrown" ); + } + catch ( IllegalStateException e ) + { + // good + } + } + }, + A + { + @Override + void verify( Pair states, SelectionUseCase selection ) + { + assertSame( states.getLeft(), selection.select( states ) ); + } + }, + B + { + @Override + void verify( Pair states, SelectionUseCase selection ) + { + assertSame( states.getRight(), selection.select( states ) ); + } + }; + + abstract void verify( Pair states, SelectionUseCase selection ); + } +} \ No newline at end of file diff --git a/community/index/src/test/java/org/neo4j/index/gbptree/TreeStateTest.java b/community/index/src/test/java/org/neo4j/index/gbptree/TreeStateTest.java new file mode 100644 index 0000000000000..1d9f23b0556f3 --- /dev/null +++ b/community/index/src/test/java/org/neo4j/index/gbptree/TreeStateTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.index.gbptree; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import org.neo4j.io.pagecache.PageCursor; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TreeStateTest +{ + private final int pageSize = 256; + private final PageAwareByteArrayCursor cursor = new PageAwareByteArrayCursor( pageSize ); + + @Before + public void initiateCursor() throws IOException + { + cursor.next(); + } + + @Test + public void readEmptyStateShouldThrow() throws Exception + { + // GIVEN empty state + + // WHEN + TreeState state = TreeState.read( cursor ); + + // THEN + assertFalse( state.isValid() ); + } + + @Test + public void shouldReadValidPage() throws Exception + { + // GIVEN valid state + TreeState.write( cursor, 1, 2, 3, 4 ); + cursor.rewind(); + + // WHEN + boolean valid = TreeState.read( cursor ).isValid(); + + // THEN + assertTrue( valid ); + } + + @Test + public void readBrokenStateShouldFail() throws Exception + { + // GIVEN broken state + TreeState.write( cursor, 1, 2, 3, 4 ); + cursor.rewind(); + assertTrue( TreeState.read( cursor ).isValid() ); + cursor.rewind(); + breakChecksum( cursor ); + + // WHEN + TreeState state = TreeState.read( cursor ); + + // THEN + assertFalse( state.isValid() ); + } + + @Test + public void shouldNotWriteInvalidStableGeneration() throws Exception + { + // GIVEN + long generation = GenSafePointer.MAX_GENERATION + 1; + + // WHEN + try + { + TreeState.write( cursor, generation, 2, 3, 4 ); + fail( "Should have failed" ); + } + catch ( IllegalArgumentException e ) + { + // THEN good + } + } + + @Test + public void shouldNotWriteInvalidUnstableGeneration() throws Exception + { + // GIVEN + long generation = GenSafePointer.MAX_GENERATION + 1; + + // WHEN + try + { + TreeState.write( cursor, 1, generation, 3, 4 ); + fail( "Should have failed" ); + } + catch ( IllegalArgumentException e ) + { + // THEN good + } + } + + private void breakChecksum( PageCursor cursor ) + { + // Doesn't matter which bits we destroy actually. Destroying the first ones requires + // no additional knowledge about where checksum is stored + long existing = cursor.getLong( cursor.getOffset() ); + cursor.putLong( cursor.getOffset(), ~existing ); + } +} \ No newline at end of file