diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java b/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java index 0ac1137a7423c..e899b263c198f 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java @@ -299,7 +299,8 @@ public abstract class PageCursor implements AutoCloseable public abstract boolean checkAndClearBoundsFlag(); /** - * Check if a cursor error has been set, and if so, remove it from the cursor and throw it. + * Check if a cursor error has been set on this or any linked cursor, and if so, remove it from the cursor + * and throw it as a {@link CursorException}. */ public abstract void checkAndClearCursorError() throws CursorException; @@ -321,6 +322,12 @@ public abstract class PageCursor implements AutoCloseable */ public abstract void setCursorError( String message ); + /** + * Unconditionally clear any error condition that has been set on this or any linked cursor, without throwing an + * exception. + */ + public abstract void clearCursorError(); + /** * Open a new page cursor with the same pf_flags as this cursor, as if calling the {@link PagedFile#io(long, int)} * on the relevant paged file. This cursor will then also delegate to the linked cursor when checking diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/impl/CompositePageCursor.java b/community/io/src/main/java/org/neo4j/io/pagecache/impl/CompositePageCursor.java index c0795cb0d65bd..9dca3472f6971 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/impl/CompositePageCursor.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/impl/CompositePageCursor.java @@ -470,6 +470,13 @@ public void setCursorError( String message ) cursor( 0 ).setCursorError( message ); } + @Override + public void clearCursorError() + { + first.clearCursorError(); + second.clearCursorError(); + } + @Override public PageCursor openLinkedCursor( long pageId ) { diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/impl/DelegatingPageCursor.java b/community/io/src/main/java/org/neo4j/io/pagecache/impl/DelegatingPageCursor.java index d58f61cf2c6eb..b70a0399a0178 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/impl/DelegatingPageCursor.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/impl/DelegatingPageCursor.java @@ -155,6 +155,12 @@ public void setCursorError( String message ) delegate.setCursorError( message ); } + @Override + public void clearCursorError() + { + delegate.clearCursorError(); + } + @Override public PageCursor openLinkedCursor( long pageId ) { diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/CursorExceptionWithPreciseStackTrace.java b/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/CursorExceptionWithPreciseStackTrace.java new file mode 100644 index 0000000000000..40b4cfce17913 --- /dev/null +++ b/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/CursorExceptionWithPreciseStackTrace.java @@ -0,0 +1,36 @@ +/* + * 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.io.pagecache.impl.muninn; + +import org.neo4j.io.pagecache.CursorException; + +/** + * This exception is used to indicate in the stack trace, whether or not + * {@link MuninnPageCursor#usePreciseCursorErrorStackTraces} is enabled. + * + * This is useful because it allows us to clearly see how much we can trust the stack traces for decoding errors. + */ +final class CursorExceptionWithPreciseStackTrace extends CursorException +{ + public CursorExceptionWithPreciseStackTrace( String message ) + { + super( message ); + } +} diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCursor.java b/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCursor.java index 0438114e0110d..b1ccbe01b0e28 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCursor.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCursor.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.util.Objects; import org.neo4j.concurrent.BinaryLatch; import org.neo4j.io.pagecache.CursorException; @@ -38,6 +39,9 @@ abstract class MuninnPageCursor extends PageCursor private static final boolean tracePinnedCachePageId = flag( MuninnPageCursor.class, "tracePinnedCachePageId", false ); + private static final boolean usePreciseCursorErrorStackTraces = + flag( MuninnPageCursor.class, "usePreciseCursorErrorStackTraces", true ); + // Size of the respective primitive types in bytes. private static final int SIZE_OF_BYTE = Byte.BYTES; private static final int SIZE_OF_SHORT = Short.BYTES; @@ -60,7 +64,10 @@ abstract class MuninnPageCursor extends PageCursor private int filePageSize; private int offset; private boolean outOfBounds; - private String cursorExceptionMessage; + // This is a String with the exception message if usePreciseCursorErrorStackTraces is false, otherwise it is a + // CursorExceptionWithPreciseStackTrace with the message and stack trace pointing more or less directly at the + // offending code. + private Object cursorException; MuninnPageCursor( long victimPage ) { @@ -104,7 +111,7 @@ public final void reset( MuninnPage page ) @Override public final boolean next( long pageId ) throws IOException { - if ( currentPageId == nextPageId ) + if ( currentPageId == pageId ) { return true; } @@ -166,7 +173,7 @@ void clearPageState() pageSize = 0; // make all future bound checks fail page = null; // make all future page navigation fail currentPageId = UNBOUND_PAGE_ID; - cursorExceptionMessage = null; + cursorException = null; } @Override @@ -708,18 +715,26 @@ public void checkAndClearCursorError() throws CursorException MuninnPageCursor cursor = this; do { - String message = cursor.cursorExceptionMessage; - if ( message != null ) + Object error = cursor.cursorException; + if ( error != null ) { clearCursorError( cursor ); - throw new CursorException( message ); + if ( usePreciseCursorErrorStackTraces ) + { + throw (CursorExceptionWithPreciseStackTrace) error; + } + else + { + throw new CursorException( (String) error ); + } } cursor = cursor.linkedCursor; } while ( cursor != null ); } - protected void clearCursorError() + @Override + public void clearCursorError() { clearCursorError( this ); } @@ -728,7 +743,7 @@ private void clearCursorError( MuninnPageCursor cursor ) { while ( cursor != null ) { - cursor.cursorExceptionMessage = null; + cursor.cursorException = null; cursor = cursor.linkedCursor; } } @@ -742,6 +757,14 @@ public void raiseOutOfBounds() @Override public void setCursorError( String message ) { - this.cursorExceptionMessage = message; + Objects.requireNonNull( message ); + if ( usePreciseCursorErrorStackTraces ) + { + this.cursorException = new CursorExceptionWithPreciseStackTrace( message ); + } + else + { + this.cursorException = message; + } } } diff --git a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java index 9fd2d7469ecf8..986e687588ad9 100644 --- a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java +++ b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialReadPageCursor.java @@ -371,20 +371,26 @@ public boolean shouldRetry() throws IOException IllegalStateException.class ); if ( state.hasPreparedInconsistentRead() ) { - delegate.shouldRetry(); - delegate.setOffset( 0 ); + resetDelegate(); return true; } if ( state.hasInconsistentRead() ) { - delegate.shouldRetry(); - delegate.setOffset( 0 ); + resetDelegate(); return true; } boolean retry = delegate.shouldRetry(); return retry || (linkedCursor != null && linkedCursor.shouldRetry()); } + private void resetDelegate() throws IOException + { + delegate.shouldRetry(); + delegate.setOffset( 0 ); + delegate.checkAndClearBoundsFlag(); + delegate.clearCursorError(); + } + @Override public int copyTo( int sourceOffset, PageCursor targetCursor, int targetOffset, int lengthInBytes ) { @@ -424,6 +430,12 @@ public void setCursorError( String message ) delegate.setCursorError( message ); } + @Override + public void clearCursorError() + { + delegate.clearCursorError(); + } + @Override public PageCursor openLinkedCursor( long pageId ) { diff --git a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java index 384dec70c9ab5..70b0d7e7d2840 100644 --- a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java +++ b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialWritePageCursor.java @@ -291,6 +291,12 @@ public void setCursorError( String message ) delegate.setCursorError( message ); } + @Override + public void clearCursorError() + { + delegate.clearCursorError(); + } + @Override public PageCursor openLinkedCursor( long pageId ) { diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheTest.java b/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheTest.java index e1f5d42156b6e..3d376af2ff5bd 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheTest.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/PageCacheTest.java @@ -2011,34 +2011,51 @@ public void tracerMustBeNotifiedAboutPinUnpinFaultAndEvictEventsWhenReading() th getPageCache( fs, maxPages, pageCachePageSize, tracer ); long countedPages = 0; + long countedFaults = 0; try ( PagedFile pagedFile = pageCache.map( file( "a" ), filePageSize ); PageCursor cursor = pagedFile.io( 0, PF_SHARED_READ_LOCK ) ) { while ( cursor.next() ) { - assertTrue( cursor.next( cursor.getCurrentPageId() ) ); + countedPages++; + countedFaults++; + } + + // Using next( pageId ) to the already-pinned page id does not count, + // so we only increment once for this section + countedPages++; + for ( int i = 0; i < 20; i++ ) + { + assertTrue( cursor.next( 1 ) ); + } + + // But if we use next( pageId ) to a page that is different from the one already pinned, + // then it counts + for ( int i = 0; i < 20; i++ ) + { + assertTrue( cursor.next( i ) ); countedPages++; } } - assertThat( "wrong count of pins", tracer.pins(), is( countedPages * 2 ) ); - assertThat( "wrong count of unpins", tracer.unpins(), is( countedPages * 2 ) ); + assertThat( "wrong count of pins", tracer.pins(), is( countedPages ) ); + assertThat( "wrong count of unpins", tracer.unpins(), is( countedPages ) ); // We might be unlucky and fault in the second next call, on the page // we brought up in the first next call. That's why we assert that we // have observed *at least* the countedPages number of faults. long faults = tracer.faults(); long bytesRead = tracer.bytesRead(); - assertThat( "wrong count of faults", faults, greaterThanOrEqualTo( countedPages ) ); + assertThat( "wrong count of faults", faults, greaterThanOrEqualTo( countedFaults ) ); assertThat( "wrong number of bytes read", - bytesRead, greaterThanOrEqualTo( countedPages * filePageSize ) ); + bytesRead, greaterThanOrEqualTo( countedFaults * filePageSize ) ); // Every page we move forward can put the freelist behind so the cache // wants to evict more pages. Plus, every page fault we do could also // block and get a page directly transferred to it, and these kinds of // evictions can count in addition to the evictions we do when the // cache is behind on keeping the freelist full. assertThat( "wrong count of evictions", tracer.evictions(), - both( greaterThanOrEqualTo( countedPages - maxPages ) ) + both( greaterThanOrEqualTo( countedFaults - maxPages ) ) .and( lessThanOrEqualTo( countedPages + faults ) ) ); } @@ -2057,15 +2074,19 @@ public void tracerMustBeNotifiedAboutPinUnpinFaultFlushAndEvictionEventsWhenWrit { assertTrue( cursor.next() ); assertThat( cursor.getCurrentPageId(), is( i ) ); - assertTrue( cursor.next( i ) ); + assertTrue( cursor.next( i ) ); // This does not count as a pin assertThat( cursor.getCurrentPageId(), is( i ) ); writeRecords( cursor ); } + + // This counts as a single pin + assertTrue( cursor.next( 0 ) ); + assertTrue( cursor.next( 0 ) ); } - assertThat( "wrong count of pins", tracer.pins(), is( pagesToGenerate * 2 ) ); - assertThat( "wrong count of unpins", tracer.unpins(), is( pagesToGenerate * 2 ) ); + assertThat( "wrong count of pins", tracer.pins(), is( pagesToGenerate + 1 ) ); + assertThat( "wrong count of unpins", tracer.unpins(), is( pagesToGenerate + 1 ) ); // We might be unlucky and fault in the second next call, on the page // we brought up in the first next call. That's why we assert that we @@ -4565,6 +4586,82 @@ public void openingLinkedCursorOnClosedCursorMustThrow() throws Exception } } + @Test + public void settingNullCursorErrorMustThrow() throws Exception + { + getPageCache( fs, maxPages, pageCachePageSize, PageCacheTracer.NULL ); + try ( PagedFile pf = pageCache.map( file( "a" ), filePageSize ); + PageCursor writer = pf.io( 0, PF_SHARED_WRITE_LOCK ); + PageCursor reader = pf.io( 0, PF_SHARED_READ_LOCK ) ) + { + assertTrue( writer.next() ); + try + { + writer.setCursorError( null ); + fail( "setting null cursor error on write cursor should have thrown" ); + } + catch ( Exception e ) + { + // all good + } + + assertTrue( reader.next() ); + try + { + reader.setCursorError( null ); + fail( "setting null cursor error in read cursor should have thrown" ); + } + catch ( Exception e ) + { + // all good + } + } + } + + @Test + public void clearCursorErrorMustUnsetErrorCondition() throws Exception + { + getPageCache( fs, maxPages, pageCachePageSize, PageCacheTracer.NULL ); + try ( PagedFile pf = pageCache.map( file( "a" ), filePageSize ); + PageCursor writer = pf.io( 0, PF_SHARED_WRITE_LOCK ); + PageCursor reader = pf.io( 0, PF_SHARED_READ_LOCK ) ) + { + assertTrue( writer.next() ); + writer.setCursorError( "boo" ); + writer.clearCursorError(); + writer.checkAndClearCursorError(); + + assertTrue( reader.next() ); + reader.setCursorError( "boo" ); + reader.clearCursorError(); + reader.checkAndClearCursorError(); + } + } + + @Test + public void clearCursorErrorMustUnsetErrorConditionOnLinkedCursor() throws Exception + { + getPageCache( fs, maxPages, pageCachePageSize, PageCacheTracer.NULL ); + try ( PagedFile pf = pageCache.map( file( "a" ), filePageSize ); + PageCursor writer = pf.io( 0, PF_SHARED_WRITE_LOCK ); + PageCursor reader = pf.io( 0, PF_SHARED_READ_LOCK ) ) + { + assertTrue( writer.next() ); + PageCursor linkedWriter = writer.openLinkedCursor( 1 ); + assertTrue( linkedWriter.next() ); + linkedWriter.setCursorError( "boo" ); + writer.clearCursorError(); + writer.checkAndClearCursorError(); + + assertTrue( reader.next() ); + PageCursor linkedReader = reader.openLinkedCursor( 1 ); + assertTrue( linkedReader.next() ); + linkedReader.setCursorError( "boo" ); + reader.clearCursorError(); + reader.checkAndClearCursorError(); + } + } + @Test( timeout = SHORT_TIMEOUT_MILLIS ) public void readableByteChannelMustBeOpenUntilClosed() throws Exception { diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java b/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java index a28047a4bc0ad..f3a236dc6ed32 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/StubPageCursor.java @@ -160,6 +160,12 @@ public void setCursorError( String message ) this.cursorErrorMessage = message; } + @Override + public void clearCursorError() + { + this.cursorErrorMessage = null; + } + @Override public PageCursor openLinkedCursor( long pageId ) { diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/harness/MunninPageCacheHarnessTest.java b/community/io/src/test/java/org/neo4j/io/pagecache/harness/MuninnPageCacheHarnessTest.java similarity index 94% rename from community/io/src/test/java/org/neo4j/io/pagecache/harness/MunninPageCacheHarnessTest.java rename to community/io/src/test/java/org/neo4j/io/pagecache/harness/MuninnPageCacheHarnessTest.java index 787a022b1f7ac..95343d2ed368d 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/harness/MunninPageCacheHarnessTest.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/harness/MuninnPageCacheHarnessTest.java @@ -22,7 +22,7 @@ import org.neo4j.io.pagecache.impl.muninn.MuninnPageCache; import org.neo4j.io.pagecache.impl.muninn.MuninnPageCacheFixture; -public class MunninPageCacheHarnessTest extends PageCacheHarnessTest +public class MuninnPageCacheHarnessTest extends PageCacheHarnessTest { @Override protected Fixture createFixture() diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/harness/MuninnPageCacheHarnessWithRealFileSystemIT.java b/community/io/src/test/java/org/neo4j/io/pagecache/harness/MuninnPageCacheHarnessWithRealFileSystemIT.java index 65bb129efe280..f8cd116884eee 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/harness/MuninnPageCacheHarnessWithRealFileSystemIT.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/harness/MuninnPageCacheHarnessWithRealFileSystemIT.java @@ -25,7 +25,7 @@ import org.neo4j.io.pagecache.impl.muninn.MuninnPageCache; import org.neo4j.test.TargetDirectory; -public class MuninnPageCacheHarnessWithRealFileSystemIT extends MunninPageCacheHarnessTest +public class MuninnPageCacheHarnessWithRealFileSystemIT extends MuninnPageCacheHarnessTest { @Rule public TargetDirectory.TestDirectory directory = TargetDirectory.testDirForTest( getClass() ); diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/impl/CompositePageCursorTest.java b/community/io/src/test/java/org/neo4j/io/pagecache/impl/CompositePageCursorTest.java index 4572d8e13348a..629696bf392bb 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/impl/CompositePageCursorTest.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/impl/CompositePageCursorTest.java @@ -1245,4 +1245,18 @@ public void checkAndClearCursorErrorWillOnlyCheckFirstCursorIfBothHaveErrorsSet( assertThat( e.getMessage(), is( "second boo" ) ); } } + + @Test + public void clearCursorErrorMustClearBothCursors() throws Exception + { + PageCursor cursor = CompositePageCursor.compose( first, PAGE_SIZE, second, PAGE_SIZE ); + first.setCursorError( "first boo" ); + second.setCursorError( "second boo" ); + cursor.clearCursorError(); + + // Now these must not throw + first.checkAndClearCursorError(); + second.checkAndClearCursorError(); + cursor.checkAndClearCursorError(); + } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java index 46394f678f969..b930d2b3dc6fe 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/CommonAbstractStore.java @@ -52,6 +52,7 @@ import static org.neo4j.io.pagecache.PagedFile.PF_SHARED_WRITE_LOCK; import static org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE; import static org.neo4j.kernel.impl.store.record.RecordLoad.CHECK; +import static org.neo4j.kernel.impl.store.record.RecordLoad.NORMAL; /** * Contains common implementation of {@link RecordStore}. @@ -220,33 +221,6 @@ protected void initialiseNewStoreFile( PagedFile file ) throws IOException idGeneratorFactory.create( idFileName, getNumberOfReservedLowIds(), true ); } - protected void checkForOutOfBounds( PageCursor pageCursor, long recordId ) - { - if ( pageCursor.checkAndClearBoundsFlag() ) - { - throwOutOfBoundsException( recordId ); - } - } - - private void throwOutOfBoundsException( long recordId ) - { - RECORD record = newRecord(); - record.setId( recordId ); - long pageId = pageIdForRecord( recordId ); - int offset = offsetForId( recordId ); - throw new UnderlyingStorageException( buildOutOfBoundsExceptionMessage( - record, pageId, offset, recordSize, storeFile.pageSize(), storageFileName.getAbsolutePath() ) ); - } - - protected static String buildOutOfBoundsExceptionMessage( AbstractBaseRecord record, long pageId, int offset, - int recordSize, int pageSize, String filename ) - { - return "Access to record " + record + " went out of bounds of the page. The record size is " + - recordSize + " bytes, and the access was at offset " + offset + " bytes into page " + - pageId + ", and the pages have a capacity of " + pageSize + " bytes. " + - "The mapped store file in question is " + filename; - } - protected void createHeaderRecord( PageCursor cursor ) throws IOException { int offset = cursor.getOffset(); @@ -332,17 +306,17 @@ public byte[] getRawRecordData( long id ) throws IOException byte[] data = new byte[recordSize]; long pageId = pageIdForRecord( id ); int offset = offsetForId( id ); - try ( PageCursor pageCursor = storeFile.io( pageId, PagedFile.PF_SHARED_READ_LOCK ) ) + try ( PageCursor cursor = storeFile.io( pageId, PagedFile.PF_SHARED_READ_LOCK ) ) { - if ( pageCursor.next() ) + if ( cursor.next() ) { do { - pageCursor.setOffset( offset ); - pageCursor.getBytes( data ); + cursor.setOffset( offset ); + cursor.getBytes( data ); } - while ( pageCursor.shouldRetry() ); - checkForOutOfBounds( pageCursor, id ); + while ( cursor.shouldRetry() ); + checkForDecodingErrors( cursor, id, CHECK ); } } return data; @@ -403,7 +377,7 @@ public boolean isInUse( long id ) recordIsInUse = isInUse( cursor ); } while ( cursor.shouldRetry() ); - checkForOutOfBounds( cursor, id ); + checkForDecodingErrors( cursor, id, NORMAL ); } return recordIsInUse; } @@ -1063,18 +1037,13 @@ private void readIntoRecord( long id, RECORD record, RecordLoad mode, long pageI if ( cursor.next( pageId ) ) { // There is a page in the store that covers this record, go read it - String error; do { prepareForReading( cursor, offset, record ); - error = recordFormat.read( record, cursor, mode, recordSize, storeFile ); + recordFormat.read( record, cursor, mode, recordSize, storeFile ); } while ( cursor.shouldRetry() ); - checkForOutOfBounds( cursor, id ); - if ( error != null ) - { - mode.report( error ); - } + checkForDecodingErrors( cursor, id, mode ); verifyAfterReading( record, mode ); } else @@ -1101,7 +1070,7 @@ public void updateRecord( RECORD record ) recordFormat.write( record, cursor, recordSize, storeFile ); } while ( cursor.shouldRetry() ); - checkForOutOfBounds( cursor, id ); // We don't free ids if something weird goes wrong + checkForDecodingErrors( cursor, id, NORMAL ); // We don't free ids if something weird goes wrong if ( !record.inUse() ) { freeId( id ); @@ -1261,7 +1230,35 @@ protected void verifyAfterNotRead( RECORD record, RecordLoad mode ) } - protected void verifyAfterReading( RECORD record, RecordLoad mode ) + protected final void checkForDecodingErrors( PageCursor cursor, long recordId, RecordLoad mode ) + { + if ( mode.checkForOutOfBounds( cursor ) ) + { + throwOutOfBoundsException( recordId ); + } + mode.clearOrThrowCursorError( cursor ); + } + + private void throwOutOfBoundsException( long recordId ) + { + RECORD record = newRecord(); + record.setId( recordId ); + long pageId = pageIdForRecord( recordId ); + int offset = offsetForId( recordId ); + throw new UnderlyingStorageException( buildOutOfBoundsExceptionMessage( + record, pageId, offset, recordSize, storeFile.pageSize(), storageFileName.getAbsolutePath() ) ); + } + + protected static String buildOutOfBoundsExceptionMessage( AbstractBaseRecord record, long pageId, int offset, + int recordSize, int pageSize, String filename ) + { + return "Access to record " + record + " went out of bounds of the page. The record size is " + + recordSize + " bytes, and the access was at offset " + offset + " bytes into page " + + pageId + ", and the pages have a capacity of " + pageSize + " bytes. " + + "The mapped store file in question is " + filename; + } + + protected final void verifyAfterReading( RECORD record, RecordLoad mode ) { if ( !mode.verify( record ) ) { @@ -1269,7 +1266,7 @@ protected void verifyAfterReading( RECORD record, RecordLoad mode ) } } - protected void prepareForReading( PageCursor cursor, int offset, RECORD record ) + protected final void prepareForReading( PageCursor cursor, int offset, RECORD record ) { // Mark this record as unused. This to simplify implementations of readRecord. // readRecord can behave differently depending on RecordLoad argument and so it may be that diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/MetaDataStore.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/MetaDataStore.java index 5c0f99e333819..4c402d30b85c5 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/MetaDataStore.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/MetaDataStore.java @@ -51,6 +51,7 @@ import static org.neo4j.io.pagecache.PagedFile.PF_SHARED_READ_LOCK; import static org.neo4j.io.pagecache.PagedFile.PF_SHARED_WRITE_LOCK; import static org.neo4j.kernel.impl.store.format.standard.MetaDataRecordFormat.RECORD_SIZE; +import static org.neo4j.kernel.impl.store.record.RecordLoad.NORMAL; public class MetaDataStore extends CommonAbstractStore implements TransactionIdStore, LogVersionRepository @@ -432,7 +433,7 @@ private void incrementVersion( PageCursor cursor ) throws IOException cursor.putLong( offset, value ); } while ( cursor.shouldRetry() ); - checkForOutOfBounds( cursor, Position.LOG_VERSION.id ); + checkForDecodingErrors( cursor, Position.LOG_VERSION.id, NORMAL ); versionField = value; } @@ -521,7 +522,7 @@ long getRecordValue( PageCursor cursor, Position position ) try { record.setId( position.id ); - recordFormat.read( record, cursor, RecordLoad.NORMAL, RECORD_SIZE, storeFile ); + recordFormat.read( record, cursor, NORMAL, RECORD_SIZE, storeFile ); return record.getValue(); } catch ( IOException e ) @@ -580,7 +581,7 @@ private void setRecord( PageCursor cursor, Position position, long value ) throw cursor.putLong( value ); } while ( cursor.shouldRetry() ); - checkForOutOfBounds( cursor, position.id ); + checkForDecodingErrors( cursor, position.id, NORMAL ); } public NeoStoreRecord graphPropertyRecord() @@ -730,7 +731,7 @@ public TransactionId getUpgradeTransaction() upgradeTxChecksumField = getRecordValue( cursor, Position.UPGRADE_TRANSACTION_CHECKSUM ); } while ( cursor.shouldRetry() ); - checkForOutOfBounds( cursor, Position.UPGRADE_TRANSACTION_ID.id ); + checkForDecodingErrors( cursor, Position.UPGRADE_TRANSACTION_ID.id, NORMAL ); } catch ( IOException e ) { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java index 7133fe370a29c..f8b35f6d18e74 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java @@ -205,14 +205,13 @@ public Object getValue( PropertyBlock propertyBlock ) public static void allocateStringRecords( Collection target, byte[] chars, DynamicRecordAllocator allocator ) { - AbstractDynamicStore.allocateRecordsFromBytes( target, chars, - Iterators.emptyIterator(), allocator ); + AbstractDynamicStore.allocateRecordsFromBytes( target, chars, Iterators.emptyIterator(), allocator ); } public static void allocateArrayRecords( Collection target, Object array, DynamicRecordAllocator allocator ) { - DynamicArrayStore.allocateRecords( target, array, Iterators.emptyIterator(), allocator ); + DynamicArrayStore.allocateRecords( target, array, Iterators.emptyIterator(), allocator ); } public void encodeValue( PropertyBlock block, int keyId, Object value ) @@ -394,11 +393,4 @@ public PropertyRecord newRecord() { return new PropertyRecord( -1 ); } - - @Override - protected void verifyAfterReading( PropertyRecord record, RecordLoad mode ) - { - super.verifyAfterReading( record, mode ); - record.verifyRecordIsWellFormed(); - } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/RecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/RecordFormat.java index a41e26a90fead..61feab5db9432 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/RecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/RecordFormat.java @@ -87,12 +87,9 @@ public interface RecordFormat * @param recordSize size of records of this format. This is passed in like this since not all formats * know the record size in advance, but may be read from store header when opening the store. * @param storeFile {@link PagedFile} to get additional {@link PageCursor} from if needed. - * @return An error message describing if something went wrong when reading the record, or {@code null} if - * the record was read successfully. If non-null, the returned message can be fed to - * {@link RecordLoad#report(String)}. * @throws IOException on error reading. */ - String read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + void read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException; /** diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/DynamicRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/DynamicRecordFormat.java index c128f9e91adda..cc2bd80de3a56 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/DynamicRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/DynamicRecordFormat.java @@ -48,7 +48,7 @@ public DynamicRecord newRecord() } @Override - public String read( DynamicRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( DynamicRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) { /* * First 4b @@ -69,7 +69,8 @@ public String read( DynamicRecord record, PageCursor cursor, RecordLoad mode, in { // We must have performed an inconsistent read, // because this many bytes cannot possibly fit in a record! - return payloadTooBigErrorMessage( record, recordSize, nrOfBytes ); + cursor.setCursorError( payloadTooBigErrorMessage( record, recordSize, nrOfBytes ) ); + return; } /* @@ -83,13 +84,12 @@ public String read( DynamicRecord record, PageCursor cursor, RecordLoad mode, in if ( longNextBlock != Record.NO_NEXT_BLOCK.intValue() && nrOfBytes < dataSize || nrOfBytes > dataSize ) { - return format( "Next block set[%d] current block illegal size[%d/%d]", - record.getNextBlock(), record.getLength(), dataSize ); + cursor.setCursorError( illegalBlockSizeMessage( record, dataSize ) ); + return; } readData( record, cursor ); } - return null; } public static String payloadTooBigErrorMessage( DynamicRecord record, int recordSize, int nrOfBytes ) @@ -99,6 +99,12 @@ public static String payloadTooBigErrorMessage( DynamicRecord record, int record record.getId(), nrOfBytes, recordSize ); } + private String illegalBlockSizeMessage( DynamicRecord record, int dataSize ) + { + return format( "Next block set[%d] current block illegal size[%d/%d]", + record.getNextBlock(), record.getLength(), dataSize ); + } + public static void readData( DynamicRecord record, PageCursor cursor ) { int len = record.getLength(); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/MetaDataRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/MetaDataRecordFormat.java index 16203bb24c2f5..114083c294741 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/MetaDataRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/MetaDataRecordFormat.java @@ -47,7 +47,7 @@ public MetaDataRecord newRecord() } @Override - public String read( MetaDataRecord record, PageCursor cursor, RecordLoad mode, int recordSize, + public void read( MetaDataRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { int id = record.getIntId(); @@ -55,7 +55,7 @@ public String read( MetaDataRecord record, PageCursor cursor, RecordLoad mode, i if ( id >= values.length ) { record.initialize( false, FIELD_NOT_PRESENT ); - return null; + return; } Position position = values[id]; @@ -64,7 +64,6 @@ public String read( MetaDataRecord record, PageCursor cursor, RecordLoad mode, i boolean inUse = cursor.getByte() == Record.IN_USE.byteValue(); long value = inUse ? cursor.getLong() : FIELD_NOT_PRESENT; record.initialize( inUse, value ); - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NoRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NoRecordFormat.java index 9f0c3098c8856..22db3a67ae154 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NoRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NoRecordFormat.java @@ -57,11 +57,10 @@ public boolean isInUse( PageCursor cursor ) } @Override - public String read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { record.clear(); - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java index b9ac8995cd45a..f82a68bd1eed1 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormat.java @@ -45,7 +45,7 @@ public NodeRecord newRecord() return new NodeRecord( -1 ); } - public String read( NodeRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( NodeRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { byte headerByte = cursor.getByte(); @@ -69,7 +69,6 @@ public String read( NodeRecord record, PageCursor cursor, RecordLoad mode, int r BaseRecordFormat.longFromIntAndMod( nextProp, propModifier ), dense, BaseRecordFormat.longFromIntAndMod( nextRel, relModifier ), labels ); } - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormatV2_0.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormatV2_0.java index 51d319f60d64a..b113d20b77b51 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormatV2_0.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/NodeRecordFormatV2_0.java @@ -40,7 +40,7 @@ public NodeRecord newRecord() return new NodeRecord( -1 ); } - public String read( NodeRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( NodeRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { long headerByte = cursor.getByte(); @@ -66,7 +66,6 @@ public String read( NodeRecord record, PageCursor cursor, RecordLoad mode, int r longFromIntAndMod( nextRel, relModifier ), labels ); } - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/PropertyRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/PropertyRecordFormat.java index 23df1df95bc69..ec3eb6fdb981e 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/PropertyRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/PropertyRecordFormat.java @@ -51,7 +51,7 @@ public PropertyRecord newRecord() } @Override - public String read( PropertyRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( PropertyRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) { int offsetAtBeginning = cursor.getOffset(); @@ -81,19 +81,20 @@ public String read( PropertyRecord record, PageCursor cursor, RecordLoad mode, i int numberOfBlocksUsed = type.calculateNumberOfBlocksUsed( block ); if ( numberOfBlocksUsed == PropertyType.BLOCKS_USED_FOR_BAD_TYPE_OR_ENCODING ) { - return "Invalid type or encoding of property block"; + cursor.setCursorError( "Invalid type or encoding of property block: " + block + " (type = " + type + ")" ); + return; } int additionalBlocks = numberOfBlocksUsed - 1; if ( additionalBlocks * 8 > RECORD_SIZE - (cursor.getOffset() - offsetAtBeginning ) ) { - return "PropertyRecord claims to have more property blocks than can fit in a record"; + cursor.setCursorError( "PropertyRecord claims to have more property blocks than can fit in a record" ); + return; } while ( additionalBlocks --> 0 ) { record.addLoadedBlock( cursor.getLong() ); } } - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipGroupRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipGroupRecordFormat.java index 6c9f8125a02b8..c5cf8676b1d52 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipGroupRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipGroupRecordFormat.java @@ -47,7 +47,7 @@ public RelationshipGroupRecordFormat() } @Override - public String read( RelationshipGroupRecord record, PageCursor cursor, RecordLoad mode, int recordSize, + public void read( RelationshipGroupRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { // [ , x] in use @@ -81,7 +81,6 @@ public String read( RelationshipGroupRecord record, PageCursor cursor, RecordLoa owningNode, BaseRecordFormat.longFromIntAndMod( nextLowBits, nextMod ) ); } - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormat.java index a7dd38f4546f7..47adc4af39e5d 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormat.java @@ -48,7 +48,7 @@ public RelationshipRecord newRecord() return new RelationshipRecord( -1 ); } - public String read( RelationshipRecord record, PageCursor cursor, RecordLoad mode, int recordSize, + public void read( RelationshipRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { byte headerByte = cursor.getByte(); @@ -103,7 +103,6 @@ public String read( RelationshipRecord record, PageCursor cursor, RecordLoad mod (extraByte & 0x1) != 0, (extraByte & 0x2) != 0 ); } - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormatV2_0.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormatV2_0.java index 66cb75a4dc6db..a75e6464c7695 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormatV2_0.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/RelationshipRecordFormatV2_0.java @@ -41,7 +41,7 @@ public RelationshipRecord newRecord() } @Override - public String read( RelationshipRecord record, PageCursor cursor, RecordLoad mode, int recordSize, + public void read( RelationshipRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { long headerByte = cursor.getByte(); @@ -92,7 +92,6 @@ public String read( RelationshipRecord record, PageCursor cursor, RecordLoad mod longFromIntAndMod( secondNextRel, secondNextRelMod ), false, false ); } - return null; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/TokenRecordFormat.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/TokenRecordFormat.java index c43e0752fb2ac..54c0e144dbe6e 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/TokenRecordFormat.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/format/standard/TokenRecordFormat.java @@ -36,7 +36,7 @@ protected TokenRecordFormat( int recordSize, int idBits ) } @Override - public String read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) { byte inUseByte = cursor.getByte(); boolean inUse = isInUse( inUseByte ); @@ -45,7 +45,6 @@ public String read( RECORD record, PageCursor cursor, RecordLoad mode, int recor { readRecordData( cursor, record, inUse ); } - return null; } protected void readRecordData( PageCursor cursor, RECORD record, boolean inUse ) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/PropertyRecord.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/PropertyRecord.java index 68f661b53682b..2a40fc95cd4fd 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/PropertyRecord.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/PropertyRecord.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.NoSuchElementException; -import org.neo4j.kernel.impl.store.InvalidRecordException; import org.neo4j.kernel.impl.store.PropertyType; import static org.neo4j.kernel.impl.store.record.Record.NO_NEXT_PROPERTY; @@ -65,7 +64,6 @@ public class PropertyRecord extends AbstractBaseRecord implements Iterable deletedRecords; - private String malformedMessage; // state for the Iterator aspect of this class. private int blockRecordsIteratorCursor; @@ -258,19 +256,6 @@ private void ensureBlocksLoaded() } } - public void verifyRecordIsWellFormed() - { - if ( malformedMessage != null ) - { - throw new InvalidRecordException( malformedMessage ); - } - } - - public void setMalformedMessage( String message ) - { - malformedMessage = message; - } - public void setPropertyBlock( PropertyBlock block ) { removePropertyBlock( block.getKeyIndexId() ); diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/RecordLoad.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/RecordLoad.java index b8f44a327c381..5287af091291f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/RecordLoad.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/RecordLoad.java @@ -19,6 +19,7 @@ */ package org.neo4j.kernel.impl.store.record; +import org.neo4j.io.pagecache.CursorException; import org.neo4j.io.pagecache.PageCursor; import org.neo4j.kernel.impl.store.InvalidRecordException; @@ -39,77 +40,66 @@ */ public enum RecordLoad { - NORMAL - { - @Override - public boolean shouldLoad( boolean inUse ) - { - return inUse; - } + NORMAL, CHECK, FORCE; - @Override - public boolean verify( AbstractBaseRecord record ) - { - if ( !record.inUse() ) - { - throw new InvalidRecordException( record + " not in use" ); - } - return true; - } - }, - CHECK + /** + * Checks whether or not a record should be fully loaded from {@link PageCursor}, based on inUse status. + */ + public final boolean shouldLoad( boolean inUse ) { - @Override - public boolean shouldLoad( boolean inUse ) - { - return inUse; - } - - @Override - public boolean verify( AbstractBaseRecord record ) - { - return record.inUse(); - } + // FORCE mode always return true so that record data will always be loaded, even if not in use. + // The other modes only loads records that are in use. + return this == FORCE | inUse; + } - @Override - public void report( String message ) - { - } - }, - FORCE + /** + * Verifies that a record's in use status is in line with the mode, might throw {@link InvalidRecordException}. + */ + public final boolean verify( AbstractBaseRecord record ) { - @Override - public boolean shouldLoad( boolean inUse ) + boolean inUse = record.inUse(); + if ( this == NORMAL & !inUse ) { - // Always return true so that record data will always be loaded, even if not in use. - return true; + throw new InvalidRecordException( record + " not in use" ); } + return this == FORCE | inUse; + } - @Override - public boolean verify( AbstractBaseRecord record ) + /** + * Depending on the mode, this will - if a cursor error has been raised on the given {@link PageCursor} - either + * throw an {@link InvalidRecordException} with the underlying {@link CursorException}, or clear the error condition + * on the cursor. + * @param cursor The {@link PageCursor} to be checked for errors. + */ + public final void clearOrThrowCursorError( PageCursor cursor ) + { + if ( this == NORMAL ) { - return true; + try + { + cursor.checkAndClearCursorError(); + } + catch ( CursorException e ) + { + throw new InvalidRecordException( e ); + } } - - @Override - public void report( String message ) + else { - // Don't report + // The CHECK and FORCE modes do not bother with reporting decoding errors... + // ... but they must still clear them, since the page cursor may be reused to read other records + cursor.clearCursorError(); } - }; - - /** - * Checks whether or not a record should be fully loaded from {@link PageCursor}, based on inUse status. - */ - public abstract boolean shouldLoad( boolean inUse ); + } /** - * Verifies that a record's in use status is in line with the mode, might throw {@link InvalidRecordException}. + * Checks the given {@link PageCursor} to see if its out-of-bounds flag has been raised, and returns {@code true} if + * that is the case and and out-of-bounds condition should be reported up the stack. + * @param cursor The {@link PageCursor} to check the bounds flag for. + * @return {@code true} if an out-of-bounds condition should be reported up the stack, {@code false} otherwise. */ - public abstract boolean verify( AbstractBaseRecord record ); - - public void report( String message ) + public boolean checkForOutOfBounds( PageCursor cursor ) { - throw new InvalidRecordException( message ); + return cursor.checkAndClearBoundsFlag() & this == NORMAL; } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java index a838e0b12a26e..aad21bff59478 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreBehaviourTest.java @@ -46,8 +46,11 @@ import org.neo4j.test.EphemeralFileSystemRule; import org.neo4j.test.PageCacheRule; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.neo4j.helpers.collection.MapUtil.stringMap; +import static org.neo4j.kernel.impl.store.record.RecordLoad.CHECK; +import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE; import static org.neo4j.kernel.impl.store.record.RecordLoad.NORMAL; /** @@ -71,6 +74,7 @@ public class CommonAbstractStoreBehaviourTest private final Queue nextPageId = new ConcurrentLinkedQueue<>(); private final Queue nextPageOffset = new ConcurrentLinkedQueue<>(); + private int cursorErrorOnRecord; private int intsPerRecord = 1; private MyStore store; @@ -88,12 +92,12 @@ public void tearDown() nextPageId.clear(); } - private void assertThrows( ThrowingAction action ) throws Exception + private void assertThrowsUnderlyingStorageException( ThrowingAction action ) throws Exception { try { action.apply(); - fail( "expected an exception" ); + fail( "expected an UnderlyingStorageException exception" ); } catch ( UnderlyingStorageException exception ) { @@ -101,11 +105,41 @@ private void assertThrows( ThrowingAction action ) throws Exception } } - private void verifyExceptionOnAccess( ThrowingAction access ) throws Exception + private void assertThrowsInvalidRecordException( ThrowingAction action ) throws Exception + { + try + { + action.apply(); + fail( "expected an InvalidRecordException exception" ); + } + catch ( InvalidRecordException exception ) + { + // Good! + } + } + + private void verifyExceptionOnOutOfBoundsAccess( ThrowingAction access ) throws Exception + { + prepareStoreForOutOfBoundsAccess(); + assertThrowsUnderlyingStorageException( access ); + } + + private void prepareStoreForOutOfBoundsAccess() { createStore(); nextPageOffset.add( 8190 ); - assertThrows( access ); + } + + private void verifyExceptionOnCursorError( ThrowingAction access ) throws Exception + { + prepareStoreForCursorError(); + assertThrowsInvalidRecordException( access ); + } + + private void prepareStoreForCursorError() + { + createStore(); + cursorErrorOnRecord = 5; } private void createStore() @@ -120,7 +154,7 @@ public void writingOfHeaderRecordDuringInitialiseNewStoreFileMustThrowOnPageOver // 16-byte header will overflow an 8-byte page size Config config = CONFIG.with( stringMap( GraphDatabaseSettings.mapped_memory_page_size.name(), "8" ) ); MyStore store = new MyStore( config, pageCacheRule.getPageCache( fs.get(), config ) ); - assertThrows( () -> store.initialise( true ) ); + assertThrowsUnderlyingStorageException( () -> store.initialise( true ) ); } @Test @@ -132,37 +166,78 @@ public void extractHeaderRecordDuringLoadStorageMustThrowOnPageOverflow() throws config = CONFIG.with( stringMap( GraphDatabaseSettings.mapped_memory_page_size.name(), "8" ) ); MyStore second = new MyStore( config, pageCacheRule.getPageCache( fs.get(), config ) ); - assertThrows( () -> second.initialise( false ) ); + assertThrowsUnderlyingStorageException( () -> second.initialise( false ) ); } @Test - public void getRawRecordDataMustThrowOnPageOverflow() throws Exception + public void getRawRecordDataMustNotThrowOnPageOverflow() throws Exception { - verifyExceptionOnAccess( () -> store.getRawRecordData( 5 ) ); + prepareStoreForOutOfBoundsAccess(); + store.getRawRecordData( 5 ); } @Test public void isInUseMustThrowOnPageOverflow() throws Exception { - verifyExceptionOnAccess( () -> store.isInUse( 5 ) ); + verifyExceptionOnOutOfBoundsAccess( () -> store.isInUse( 5 ) ); + } + + @Test + public void isInUseMustThrowOnCursorError() throws Exception + { + verifyExceptionOnCursorError( () -> store.isInUse( 5 ) ); } @Test public void getRecordMustThrowOnPageOverflow() throws Exception { - verifyExceptionOnAccess( () -> store.getRecord( 5, new IntRecord( 5 ), NORMAL ) ); + verifyExceptionOnOutOfBoundsAccess( () -> store.getRecord( 5, new IntRecord( 5 ), NORMAL ) ); + } + + @Test + public void getRecordMustNotThrowOnPageOverflowWithCheckLoadMode() throws Exception + { + prepareStoreForOutOfBoundsAccess(); + store.getRecord( 5, new IntRecord( 5 ), CHECK ); + } + + @Test + public void getRecordMustNotThrowOnPageOverflowWithForceLoadMode() throws Exception + { + prepareStoreForOutOfBoundsAccess(); + store.getRecord( 5, new IntRecord( 5 ), FORCE ); } @Test public void updateRecordMustThrowOnPageOverflow() throws Exception { - verifyExceptionOnAccess( () -> store.updateRecord( new IntRecord( 5 ) ) ); + verifyExceptionOnOutOfBoundsAccess( () -> store.updateRecord( new IntRecord( 5 ) ) ); + } + + @Test + public void getRecordMustThrowOnCursorError() throws Exception + { + verifyExceptionOnCursorError( () -> store.getRecord( 5, new IntRecord( 5 ), NORMAL ) ); + } + + @Test + public void getRecordMustNotThrowOnCursorErrorWithCheckLoadMode() throws Exception + { + prepareStoreForCursorError(); + store.getRecord( 5, new IntRecord( 5 ), CHECK ); + } + + @Test + public void getRecordMustNotThrowOnCursorErrorWithForceLoadMode() throws Exception + { + prepareStoreForCursorError(); + store.getRecord( 5, new IntRecord( 5 ), FORCE ); } @Test public void recordCursorNextMustThrowOnPageOverflow() throws Exception { - verifyExceptionOnAccess( () -> { + verifyExceptionOnOutOfBoundsAccess( () -> { try ( RecordCursor cursor = store.newRecordCursor( new IntRecord( 0 ) ).acquire( 5, NORMAL ) ) { cursor.next(); @@ -170,6 +245,18 @@ public void recordCursorNextMustThrowOnPageOverflow() throws Exception } ); } + @Test + public void pageCursorErrorsMustNotLingerInRecordCursor() throws Exception + { + createStore(); + RecordCursor cursor = store.newRecordCursor( new IntRecord( 1 ) ).acquire( 1, FORCE ); + cursorErrorOnRecord = 1; + // This will encounter a decoding error, which is ignored because FORCE + assertTrue( cursor.next() ); + // Then this should not fail because of the previous decoding error, even though we stay on the same page + assertTrue( cursor.next( 2, new IntRecord( 2 ), NORMAL ) ); + } + @Test public void rebuildIdGeneratorSlowMustThrowOnPageOverflow() throws Exception { @@ -181,7 +268,7 @@ public void rebuildIdGeneratorSlowMustThrowOnPageOverflow() throws Exception record.value = 0xCAFEBABE; store.updateRecord( record ); intsPerRecord = 8192; - assertThrows( () -> store.makeStoreOk() ); + assertThrowsUnderlyingStorageException( () -> store.makeStoreOk() ); } @Test @@ -193,7 +280,7 @@ public void scanForHighIdMustThrowOnPageOverflow() throws Exception record.value = 0xCAFEBABE; store.updateRecord( record ); intsPerRecord = 8192; - assertThrows( () -> store.makeStoreOk() ); + assertThrowsUnderlyingStorageException( () -> store.makeStoreOk() ); } private static class IntRecord extends AbstractBaseRecord @@ -233,16 +320,20 @@ public IntRecord newRecord() @Override public boolean isInUse( PageCursor cursor ) { + int offset = cursor.getOffset(); + long pageId = cursor.getCurrentPageId(); + long recordId = (offset + pageId * cursor.getCurrentPageSize()) / 4; boolean inUse = false; for ( int i = 0; i < intsPerRecord; i++ ) { inUse |= cursor.getInt() != 0; } + maybeSetCursorError( cursor, recordId ); return inUse; } @Override - public String read( IntRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( IntRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { for ( int i = 0; i < intsPerRecord; i++ ) @@ -250,7 +341,15 @@ public String read( IntRecord record, PageCursor cursor, RecordLoad mode, int re record.value = cursor.getInt(); } record.setInUse( true ); - return null; + maybeSetCursorError( cursor, record.getId() ); + } + + private void maybeSetCursorError( PageCursor cursor, long id ) + { + if ( cursorErrorOnRecord == id ) + { + cursor.setCursorError( "boom" ); + } } @Override diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreTest.java index a8a2a532d9f8f..2ca7c6a078645 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/CommonAbstractStoreTest.java @@ -169,9 +169,13 @@ public void recordCursorPinsEachPageItReads() throws Exception cursor.acquire( 0, RecordLoad.NORMAL ); assertTrue( cursor.next( nodeId1 ) ); assertTrue( cursor.next( nodeId2 ) ); - assertNotNull( tracer.tryObserve( Pin.class ) ); - assertNull( tracer.tryObserve( Event.class ) ); } + // Because both nodes hit the same page, the code will only pin the page once and thus only emit one pin + // event. This pin event will not be observable until after we have closed the cursor. We could + // alternatively have chosen nodeId2 to be on a different page than nodeId1. In that case, the pin event + // for nodeId1 would have been visible after our call to cursor.next( nodeId2 ). + assertNotNull( tracer.tryObserve( Pin.class ) ); + assertNull( tracer.tryObserve( Event.class ) ); } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatTest.java index 71d339b0c9a54..073ccee4119f3 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/store/format/RecordFormatTest.java @@ -46,13 +46,9 @@ import static java.lang.System.currentTimeMillis; import static java.nio.file.StandardOpenOption.CREATE; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; - +import static org.junit.Assert.assertTrue; import static org.neo4j.io.ByteUnit.kibiBytes; import static org.neo4j.kernel.impl.store.record.RecordLoad.NORMAL; @@ -222,11 +218,11 @@ private void readAndVerifyRecord( R written, R re do { cursor.setOffset( offset ); - error = format.read( read, cursor, NORMAL, recordSize, storeFile ); + format.read( read, cursor, NORMAL, recordSize, storeFile ); } while ( cursor.shouldRetry() ); - assertThat( error, is( nullValue() ) ); assertFalse( "Out-of-bounds when reading record " + written, cursor.checkAndClearBoundsFlag() ); + cursor.checkAndClearCursorError(); // THEN if ( written.inUse() ) diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/transaction/state/PrepareTrackingRecordFormats.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/transaction/state/PrepareTrackingRecordFormats.java index 23a868270c301..fa9f9b3bdbd07 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/transaction/state/PrepareTrackingRecordFormats.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/transaction/state/PrepareTrackingRecordFormats.java @@ -181,10 +181,10 @@ public boolean isInUse( PageCursor cursor ) } @Override - public String read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( RECORD record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { - return actual.read( record, cursor, mode, recordSize, storeFile ); + actual.read( record, cursor, mode, recordSize, storeFile ); } @Override diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java index fb83e7bd87bc5..0664078ca1817 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java @@ -90,7 +90,7 @@ protected BaseHighLimitRecordFormat( Function recordSize, i super( recordSize, recordHeaderSize, IN_USE_BIT, HighLimit.DEFAULT_MAXIMUM_BITS_PER_ID ); } - public String read( RECORD record, PageCursor primaryCursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( RECORD record, PageCursor primaryCursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { int primaryStartOffset = primaryCursor.getOffset(); @@ -106,7 +106,9 @@ public String read( RECORD record, PageCursor primaryCursor, RecordLoad mode, in // it may only be read as part of reading the primary unit. record.clear(); // Return and try again - return "Expected record to be the first unit in the chain, but record header says it's not"; + primaryCursor.setCursorError( + "Expected record to be the first unit in the chain, but record header says it's not" ); + return; } // This is a record that is split into multiple record units. We need a bit more clever @@ -121,7 +123,8 @@ public String read( RECORD record, PageCursor primaryCursor, RecordLoad mode, in // We must have made an inconsistent read of the secondary record unit reference. // No point in trying to read this. record.clear(); - return illegalSecondaryReferenceMessage( pageId ); + primaryCursor.setCursorError( illegalSecondaryReferenceMessage( pageId ) ); + return; } secondaryCursor.setOffset( offset + HEADER_BYTE); int primarySize = recordSize - (primaryCursor.getOffset() - primaryStartOffset); @@ -132,13 +135,12 @@ public String read( RECORD record, PageCursor primaryCursor, RecordLoad mode, in int secondarySize = recordSize - HEADER_BYTE; PageCursor composite = CompositePageCursor.compose( primaryCursor, primarySize, secondaryCursor, secondarySize ); - String result = doReadInternal( record, composite, recordSize, headerByte, inUse ); + doReadInternal( record, composite, recordSize, headerByte, inUse ); record.setSecondaryUnitId( secondaryId ); - return result; } else { - return doReadInternal( record, primaryCursor, recordSize, headerByte, inUse ); + doReadInternal( record, primaryCursor, recordSize, headerByte, inUse ); } } @@ -147,7 +149,7 @@ private String illegalSecondaryReferenceMessage( long secondaryId ) return "Illegal secondary record reference: " + secondaryId; } - protected abstract String doReadInternal( + protected abstract void doReadInternal( RECORD record, PageCursor cursor, int recordSize, long inUseByte, boolean inUse ); @Override diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/DynamicRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/DynamicRecordFormat.java index cb6ec134ca228..6e4e3d76dc263 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/DynamicRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/DynamicRecordFormat.java @@ -62,7 +62,7 @@ public DynamicRecord newRecord() } @Override - public String read( DynamicRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( DynamicRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { byte headerByte = cursor.getByte(); @@ -72,14 +72,14 @@ public String read( DynamicRecord record, PageCursor cursor, RecordLoad mode, in int length = cursor.getShort() | cursor.getByte() << 16; if ( length > recordSize | length < 0 ) { - return payloadLengthErrorMessage( record, recordSize, length ); + cursor.setCursorError( payloadLengthErrorMessage( record, recordSize, length ) ); + return; } long next = cursor.getLong(); boolean isStartRecord = (headerByte & START_RECORD_BIT) != 0; record.initialize( inUse, isStartRecord, next, -1, length ); readData( record, cursor ); } - return null; } private String payloadLengthErrorMessage( DynamicRecord record, int recordSize, int length ) diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java index 17e046b562a2c..fe62bfe558053 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java @@ -64,7 +64,7 @@ public NodeRecord newRecord() } @Override - protected String doReadInternal( NodeRecord record, PageCursor cursor, int recordSize, long headerByte, + protected void doReadInternal( NodeRecord record, PageCursor cursor, int recordSize, long headerByte, boolean inUse ) { // Interpret the header byte @@ -76,7 +76,6 @@ protected String doReadInternal( NodeRecord record, PageCursor cursor, int recor long nextProp = decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ); long labelField = decodeCompressedReference( cursor, headerByte, HAS_LABELS_BIT, NULL_LABELS ); record.initialize( inUse, nextProp, dense, nextRel, labelField ); - return null; } @Override diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java index ea59d9c9fad85..ac5f0771b0a19 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java @@ -63,7 +63,7 @@ public PropertyRecord newRecord() } @Override - public String read( PropertyRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) + public void read( PropertyRecord record, PageCursor cursor, RecordLoad mode, int recordSize, PagedFile storeFile ) throws IOException { int offset = cursor.getOffset(); @@ -78,14 +78,14 @@ public String read( PropertyRecord record, PageCursor cursor, RecordLoad mode, i toAbsolute( Reference.decode( cursor ), recordId ) ); if ( (blockCount > record.getBlockCapacity()) | (RECORD_SIZE - (cursor.getOffset() - offset) < blockCount * Long.BYTES) ) { - return "PropertyRecord claims to contain more blocks than can fit in a record"; + cursor.setCursorError( "PropertyRecord claims to contain more blocks than can fit in a record" ); + return; } while ( blockCount-- > 0 ) { record.addLoadedBlock( cursor.getLong() ); } } - return null; } @Override diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java index b0d50430e2be8..8863e1ac38066 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java @@ -65,7 +65,7 @@ public RelationshipGroupRecord newRecord() } @Override - protected String doReadInternal( RelationshipGroupRecord record, PageCursor cursor, int recordSize, long headerByte, + protected void doReadInternal( RelationshipGroupRecord record, PageCursor cursor, int recordSize, long headerByte, boolean inUse ) { record.initialize( inUse, @@ -75,7 +75,6 @@ protected String doReadInternal( RelationshipGroupRecord record, PageCursor curs decodeCompressedReference( cursor, headerByte, HAS_LOOP_BIT, NULL ), decodeCompressedReference( cursor ), decodeCompressedReference( cursor, headerByte, HAS_NEXT_BIT, NULL ) ); - return null; } @Override diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java index 51f29b7968492..95c259509ed89 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java @@ -71,7 +71,7 @@ public RelationshipRecord newRecord() } @Override - protected String doReadInternal( + protected void doReadInternal( RelationshipRecord record, PageCursor cursor, int recordSize, long headerByte, boolean inUse ) { int type = cursor.getShort() & 0xFFFF; @@ -87,7 +87,6 @@ protected String doReadInternal( decodeAbsoluteIfPresent( cursor, headerByte, HAS_SECOND_CHAIN_NEXT_BIT, recordId ), has( headerByte, FIRST_IN_FIRST_CHAIN_BIT ), has( headerByte, FIRST_IN_SECOND_CHAIN_BIT ) ); - return null; } private long decodeAbsoluteOrRelative( PageCursor cursor, long headerByte, int firstInStartBit, long recordId ) diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java index 1fc99a70ed201..87240e45395e7 100644 --- a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java @@ -101,7 +101,7 @@ protected MyRecordFormat() } @Override - protected String doReadInternal( MyRecord record, PageCursor cursor, int recordSize, + protected void doReadInternal( MyRecord record, PageCursor cursor, int recordSize, long inUseByte, boolean inUse ) { int shortsPerRecord = getShortsPerRecord(); @@ -111,7 +111,6 @@ protected String doReadInternal( MyRecord record, PageCursor cursor, int recordS v += (cursor.getByte() & 0xFF); record.value = v; } - return null; } private int getShortsPerRecord()