Skip to content

Commit

Permalink
Add a PageCursor.setCursorError and .checkAndClearCursrorError feature
Browse files Browse the repository at this point in the history
This will help simplify the design around how consistent record reads report errors up the stack.
  • Loading branch information
chrisvest committed May 20, 2016
1 parent c585ab7 commit 0ca712b
Show file tree
Hide file tree
Showing 11 changed files with 757 additions and 21 deletions.
@@ -0,0 +1,34 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.io.pagecache;

import java.io.IOException;

/**
* Thrown by {@link PageCursor#checkAndClearCursorError()} if an error condition has been set on the cursor with
* {@link PageCursor#setCursorError(String)}.
*/
public class CursorException extends IOException
{
public CursorException( String message )
{
super( message );
}
}
48 changes: 34 additions & 14 deletions community/io/src/main/java/org/neo4j/io/pagecache/PageCursor.java
Expand Up @@ -49,17 +49,17 @@
* </code></pre>
* <p>There are a couple of things to this pattern that are worth noting:
* <ul>
* <li>We use a try-with-resources clause to make sure that the resources
* associated with the PageCursor are always released properly.
* </li>
* <li>We use an if-clause for the next() call if we are only processing
* a single page, to make sure that the page exist and is accessible to us.
* </li>
* <li>We use a while-clause for next() if we are scanning through pages.
* </li>
* <li>We do our processing of the page in a do-while-retry loop, to
* make sure that we processed a page that was in a consistent state.
* </li>
* <li>We use a try-with-resources clause to make sure that the resources associated with the PageCursor are always
* released properly.
* </li>
* <li>We use an if-clause for the next() call if we are only processing a single page, to make sure that the page
* exist and is accessible to us.
* </li>
* <li>We use a while-clause for next() if we are scanning through pages.
* </li>
* <li>We do our processing of the page in a do-while-retry loop, to make sure that we processed a page that was in a
* consistent state.
* </li>
* </ul>
* You can alternatively use the {@link #next(long)} method, to navigate the
* pages you need in a non-linear fashion.
Expand Down Expand Up @@ -258,6 +258,7 @@ public abstract class PageCursor implements AutoCloseable
/**
* Relinquishes all resources associated with this cursor, including the
* cursor itself, and any linked cursors opened through it. The cursor cannot be used after this call.
*
* @see AutoCloseable#close()
*/
public abstract void close();
Expand All @@ -269,8 +270,8 @@ public abstract class PageCursor implements AutoCloseable
* reset to zero.
*
* @throws IOException If the page was evicted while doing IO, the cursor will have
* to do a page fault to get the page back.
* This may throw an IOException.
* to do a page fault to get the page back.
* This may throw an IOException.
*/
public abstract boolean shouldRetry() throws IOException;

Expand All @@ -292,23 +293,42 @@ public abstract class PageCursor implements AutoCloseable
* Discern whether an out-of-bounds access has occurred since the last call to {@link #next()} or
* {@link #next(long)}, or since the last call to {@link #shouldRetry()} that returned {@code true}, or since the
* last call to this method.
*
* @return {@code true} if an access was out of bounds, or the {@link #raiseOutOfBounds()} method has been called.
*/
public abstract boolean checkAndClearBoundsFlag();

/**
* Check if a cursor error has been set, and if so, remove it from the cursor and throw it.
*/
public abstract void checkAndClearCursorError() throws CursorException;

/**
* Explicitly raise the out-of-bounds flag.
*
* @see #checkAndClearBoundsFlag()
*/
public abstract void raiseOutOfBounds();

/**
* Set an error condition on the cursor with the given message.
* <p>
* This will make calls to {@link #checkAndClearCursorError()} throw a {@link CursorException} with the given
* message, unless the error has gotten cleared by a {@link #shouldRetry()} call that returned {@code true},
* a call to {@link #next()} or {@link #next(long)}, or the cursor is closed.
*
* @param message The message of the {@link CursorException} that {@link #checkAndClearCursorError()} will throw.
*/
public abstract void setCursorError( String message );

/**
* 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
* {@link #shouldRetry()} and {@link #checkAndClearBoundsFlag()}.
*
* <p>
* Opening a linked cursor on a cursor that already has a linked cursor, will close the older linked cursor.
* Closing a cursor also closes any linked cursor.
*
* @param pageId The page id that the linked cursor will be placed at after its first call to {@link #next()}.
* @return A cursor that is linked with this cursor.
*/
Expand Down
Expand Up @@ -22,6 +22,7 @@
import java.io.File;
import java.io.IOException;

import org.neo4j.io.pagecache.CursorException;
import org.neo4j.io.pagecache.PageCursor;

/**
Expand Down Expand Up @@ -450,12 +451,25 @@ public boolean checkAndClearBoundsFlag()
return bounds;
}

@Override
public void checkAndClearCursorError() throws CursorException
{
first.checkAndClearCursorError();
second.checkAndClearCursorError();
}

@Override
public void raiseOutOfBounds()
{
outOfBounds = true;
}

@Override
public void setCursorError( String message )
{
cursor( 0 ).setCursorError( message );
}

@Override
public PageCursor openLinkedCursor( long pageId )
{
Expand Down
Expand Up @@ -22,6 +22,7 @@
import java.io.File;
import java.io.IOException;

import org.neo4j.io.pagecache.CursorException;
import org.neo4j.io.pagecache.PageCursor;

/**
Expand Down Expand Up @@ -136,12 +137,24 @@ public boolean checkAndClearBoundsFlag()
return delegate.checkAndClearBoundsFlag();
}

@Override
public void checkAndClearCursorError() throws CursorException
{
delegate.checkAndClearCursorError();
}

@Override
public void raiseOutOfBounds()
{
delegate.raiseOutOfBounds();
}

@Override
public void setCursorError( String message )
{
delegate.setCursorError( message );
}

@Override
public PageCursor openLinkedCursor( long pageId )
{
Expand Down
Expand Up @@ -23,6 +23,7 @@
import java.io.IOException;

import org.neo4j.concurrent.BinaryLatch;
import org.neo4j.io.pagecache.CursorException;
import org.neo4j.io.pagecache.PageCursor;
import org.neo4j.io.pagecache.PageSwapper;
import org.neo4j.io.pagecache.tracing.PageCacheTracer;
Expand Down Expand Up @@ -59,6 +60,7 @@ abstract class MuninnPageCursor extends PageCursor
private int filePageSize;
private int offset;
private boolean outOfBounds;
private String cursorExceptionMessage;

MuninnPageCursor( long victimPage )
{
Expand Down Expand Up @@ -121,7 +123,6 @@ public final void close()
// We null out the pagedFile field to allow it and its (potentially big) translation table to be garbage
// collected when the file is unmapped, since the cursors can stick around in thread local caches, etc.
cursor.pagedFile = null;

}
while ( (cursor = cursor.getAndClearLinkedCursor()) != null );
}
Expand All @@ -146,7 +147,13 @@ private void closeLinkedCursorIfAny()
public PageCursor openLinkedCursor( long pageId )
{
closeLinkedCursorIfAny();
linkedCursor = (MuninnPageCursor) pagedFile.io( pageId, pf_flags );
MuninnPagedFile pf = pagedFile;
if ( pf == null )
{
// This cursor has been closed
throw new IllegalStateException( "Cannot open linked cursor on closed page cursor" );
}
linkedCursor = (MuninnPageCursor) pf.io( pageId, pf_flags );
return linkedCursor;
}

Expand All @@ -159,6 +166,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;
}

@Override
Expand Down Expand Up @@ -682,14 +690,58 @@ public final int getOffset()
@Override
public boolean checkAndClearBoundsFlag()
{
boolean b = outOfBounds;
outOfBounds = false;
return b | (linkedCursor != null && linkedCursor.checkAndClearBoundsFlag());
MuninnPageCursor cursor = this;
boolean result = false;
do
{
result |= cursor.outOfBounds;
cursor.outOfBounds = false;
cursor = cursor.linkedCursor;
}
while ( cursor != null );
return result;
}

@Override
public void checkAndClearCursorError() throws CursorException
{
MuninnPageCursor cursor = this;
do
{
String message = cursor.cursorExceptionMessage;
if ( message != null )
{
clearCursorError( cursor );
throw new CursorException( message );
}
cursor = cursor.linkedCursor;
}
while ( cursor != null );
}

protected void clearCursorError()
{
clearCursorError( this );
}

private void clearCursorError( MuninnPageCursor cursor )
{
while ( cursor != null )
{
cursor.cursorExceptionMessage = null;
cursor = cursor.linkedCursor;
}
}

@Override
public void raiseOutOfBounds()
{
outOfBounds = true;
}

@Override
public void setCursorError( String message )
{
this.cursorExceptionMessage = message;
}
}
Expand Up @@ -99,18 +99,19 @@ public boolean shouldRetry() throws IOException
{
MuninnPage p = page;
boolean needsRetry = p != null && !p.validateReadLock( lockStamp );
needsRetry |= linkedCursor != null && linkedCursor.shouldRetry();
if ( needsRetry )
{
startRetry();
}
boolean linkedNeedsRetry = linkedCursor != null && linkedCursor.shouldRetry();
return needsRetry || linkedNeedsRetry;
return needsRetry;
}

private void startRetry() throws IOException
{
setOffset( 0 );
checkAndClearBoundsFlag();
clearCursorError();
lockStamp = page.tryOptimisticReadLock();
// The page might have been evicted while we held the optimistic
// read lock, so we need to check with page.pin that this is still
Expand Down
Expand Up @@ -31,6 +31,7 @@
import java.util.concurrent.ThreadLocalRandom;

import org.neo4j.adversaries.Adversary;
import org.neo4j.io.pagecache.CursorException;
import org.neo4j.io.pagecache.PageCursor;

/**
Expand Down Expand Up @@ -405,12 +406,24 @@ public boolean checkAndClearBoundsFlag()
return delegate.checkAndClearBoundsFlag() || (linkedCursor != null && linkedCursor.checkAndClearBoundsFlag());
}

@Override
public void checkAndClearCursorError() throws CursorException
{
delegate.checkAndClearCursorError();
}

@Override
public void raiseOutOfBounds()
{
delegate.raiseOutOfBounds();
}

@Override
public void setCursorError( String message )
{
delegate.setCursorError( message );
}

@Override
public PageCursor openLinkedCursor( long pageId )
{
Expand Down
Expand Up @@ -25,6 +25,7 @@
import java.util.Objects;

import org.neo4j.adversaries.Adversary;
import org.neo4j.io.pagecache.CursorException;
import org.neo4j.io.pagecache.PageCursor;

/**
Expand Down Expand Up @@ -272,12 +273,24 @@ public boolean checkAndClearBoundsFlag()
return delegate.checkAndClearBoundsFlag() || (linkedCursor != null && linkedCursor.checkAndClearBoundsFlag());
}

@Override
public void checkAndClearCursorError() throws CursorException
{
delegate.checkAndClearCursorError();
}

@Override
public void raiseOutOfBounds()
{
delegate.raiseOutOfBounds();
}

@Override
public void setCursorError( String message )
{
delegate.setCursorError( message );
}

@Override
public PageCursor openLinkedCursor( long pageId )
{
Expand Down

0 comments on commit 0ca712b

Please sign in to comment.