Skip to content

Commit

Permalink
Javadoc updates for SwapperSet
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvest committed May 26, 2017
1 parent 3b0b440 commit 9ebddc0
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 34 deletions.
Expand Up @@ -416,12 +416,12 @@ private void evict( long pageRef, EvictionEvent evictionEvent ) throws IOExcepti
if ( swapperId != 0 ) if ( swapperId != 0 )
{ {
// If the swapper id is non-zero, then the page was not only loaded, but also bound, and possibly modified. // If the swapper id is non-zero, then the page was not only loaded, but also bound, and possibly modified.
SwapperSet.Allocation allocation = swappers.getAllocation( swapperId ); SwapperSet.SwapperMapping swapperMapping = swappers.getAllocation( swapperId );
if ( allocation != null ) if ( swapperMapping != null )
{ {
// The allocation can be null if the file has been unmapped, but there are still pages // The allocation can be null if the file has been unmapped, but there are still pages
// lingering in the cache that were bound to file page in that file. // lingering in the cache that were bound to file page in that file.
PageSwapper swapper = allocation.swapper; PageSwapper swapper = swapperMapping.swapper;
evictionEvent.setSwapper( swapper ); evictionEvent.setSwapper( swapper );


if ( isModified( pageRef ) ) if ( isModified( pageRef ) )
Expand Down
Expand Up @@ -28,40 +28,54 @@
import org.neo4j.collection.primitive.PrimitiveIntSet; import org.neo4j.collection.primitive.PrimitiveIntSet;
import org.neo4j.io.pagecache.PageSwapper; import org.neo4j.io.pagecache.PageSwapper;


/**
* The SwapperSet maintains the set of allocated {@link PageSwapper}s, and their mapping to swapper ids.
* These swapper ids are a limited resource, so they must eventually be reused as files are mapped and unmapped.
* Before a swapper id can be reused, we have to make sure that there are no pages in the page cache, that
* are bound to the old swapper id. To ensure this, we have to periodically {@link MuninnPageCache#vacuum(SwapperSet)}
* the page cache. The vacuum process will then fully evict all pages that are bound to a page swapper id that
* was freed before the start of the vacuum process.
*/
final class SwapperSet final class SwapperSet
{ {
// The sentinel is used to reserve swapper id 0 as a special value. // The sentinel is used to reserve swapper id 0 as a special value.
private static final Allocation SENTINEL = new Allocation( 0, null ); private static final SwapperMapping SENTINEL = new SwapperMapping( 0, null );
// The tombstone is used as a marker to reserve allocation entries that have been freed, but not yet vacuumed. // The tombstone is used as a marker to reserve allocation entries that have been freed, but not yet vacuumed.
// An allocation cannot be reused until it has been vacuumed. // An allocation cannot be reused until it has been vacuumed.
private static final Allocation TOMBSTONE = new Allocation( 0, null ); private static final SwapperMapping TOMBSTONE = new SwapperMapping( 0, null );
private static final int MAX_SWAPPER_ID = Short.MAX_VALUE; private static final int MAX_SWAPPER_ID = Short.MAX_VALUE;
private volatile Allocation[] allocations = new Allocation[] { SENTINEL }; private volatile SwapperMapping[] swapperMappings = new SwapperMapping[] { SENTINEL };
private final PrimitiveIntSet free = Primitive.intSet(); private final PrimitiveIntSet free = Primitive.intSet();
private final Object vacuumLock = new Object(); private final Object vacuumLock = new Object();
private int freeCounter; // Used in `free`; Guarded by `this` private int freeCounter; // Used in `free`; Guarded by `this`


public static final class Allocation /**
* The mapping entry between a {@link PageSwapper} and its swapper id.
*/
static final class SwapperMapping
{ {
public final int id; public final int id;
public final PageSwapper swapper; public final PageSwapper swapper;


private Allocation( int id, PageSwapper swapper ) private SwapperMapping( int id, PageSwapper swapper )
{ {
this.id = id; this.id = id;
this.swapper = swapper; this.swapper = swapper;
} }
} }


public Allocation getAllocation( int id ) /**
* Get the {@link SwapperMapping} for the given swapper id.
*/
SwapperMapping getAllocation( int id )
{ {
checkId( id ); checkId( id );
Allocation allocation = allocations[id]; SwapperMapping swapperMapping = swapperMappings[id];
if ( allocation == null || allocation == TOMBSTONE ) if ( swapperMapping == null || swapperMapping == TOMBSTONE )
{ {
return null; return null;
} }
return allocation; return swapperMapping;
} }


private void checkId( int id ) private void checkId( int id )
Expand All @@ -72,9 +86,12 @@ private void checkId( int id )
} }
} }


public synchronized int allocate( PageSwapper swapper ) /**
* Allocate a new swapper id for the given {@link PageSwapper}.
*/
synchronized int allocate( PageSwapper swapper )
{ {
Allocation[] allocations = this.allocations; SwapperMapping[] swapperMappings = this.swapperMappings;


// First look for an available freed slot. // First look for an available freed slot.
synchronized ( free ) synchronized ( free )
Expand All @@ -83,36 +100,40 @@ public synchronized int allocate( PageSwapper swapper )
{ {
int id = free.iterator().next(); int id = free.iterator().next();
free.remove( id ); free.remove( id );
allocations[id] = new Allocation( id, swapper ); swapperMappings[id] = new SwapperMapping( id, swapper );
this.allocations = allocations; // Volatile store synchronizes-with loads in getters. this.swapperMappings = swapperMappings; // Volatile store synchronizes-with loads in getters.
return id; return id;
} }
} }


// No free slot was found above, so we extend the array to make room for a new slot. // No free slot was found above, so we extend the array to make room for a new slot.
int id = allocations.length; int id = swapperMappings.length;
if ( id + 1 > MAX_SWAPPER_ID ) if ( id + 1 > MAX_SWAPPER_ID )
{ {
throw new IllegalStateException( "All swapper ids are allocated: " + MAX_SWAPPER_ID ); throw new IllegalStateException( "All swapper ids are allocated: " + MAX_SWAPPER_ID );
} }
allocations = Arrays.copyOf( allocations, id + 1 ); swapperMappings = Arrays.copyOf( swapperMappings, id + 1 );
allocations[id] = new Allocation( id, swapper ); swapperMappings[id] = new SwapperMapping( id, swapper );
this.allocations = allocations; // Volatile store synchronizes-with loads in getters. this.swapperMappings = swapperMappings; // Volatile store synchronizes-with loads in getters.
return id; return id;
} }


public synchronized boolean free( int id ) /**
* Free the given swapper id, and return {@code true} if it is time for a
* {@link MuninnPageCache#vacuum(SwapperSet)}, otherwise it returns {@code false}.
*/
synchronized boolean free( int id )
{ {
checkId( id ); checkId( id );
Allocation[] allocations = this.allocations; SwapperMapping[] swapperMappings = this.swapperMappings;
Allocation current = allocations[id]; SwapperMapping current = swapperMappings[id];
if ( current == null || current == TOMBSTONE ) if ( current == null || current == TOMBSTONE )
{ {
throw new IllegalStateException( throw new IllegalStateException(
"PageSwapper allocation id " + id + " is currently not allocated. Likely a double free bug." ); "PageSwapper allocation id " + id + " is currently not allocated. Likely a double free bug." );
} }
allocations[id] = TOMBSTONE; swapperMappings[id] = TOMBSTONE;
this.allocations = allocations; // Volatile store synchronizes-with loads in getters. this.swapperMappings = swapperMappings; // Volatile store synchronizes-with loads in getters.
freeCounter++; freeCounter++;
if ( freeCounter == 20 ) if ( freeCounter == 20 )
{ {
Expand All @@ -122,7 +143,13 @@ public synchronized boolean free( int id )
return false; return false;
} }


public void vacuum( Consumer<IntPredicate> evictAllLoadedPagesCallback ) /**
* Collect all freed page swapper ids, and pass them to the given callback, after which the freed ids will be
* elegible for reuse.
* This is done with careful synchronisation such that allocating and freeing of ids is allowed to mostly proceed
* concurrently.
*/
void vacuum( Consumer<IntPredicate> evictAllLoadedPagesCallback )
{ {
// We do this complicated locking to avoid blocking allocate() and free() as much as possible, while still only // We do this complicated locking to avoid blocking allocate() and free() as much as possible, while still only
// allow a single thread to do vacuum at a time, and at the same time have consistent locking around the // allow a single thread to do vacuum at a time, and at the same time have consistent locking around the
Expand All @@ -131,11 +158,11 @@ public void vacuum( Consumer<IntPredicate> evictAllLoadedPagesCallback )
{ {
// Collect currently free ids. // Collect currently free ids.
PrimitiveIntSet freeIds = Primitive.intSet(); PrimitiveIntSet freeIds = Primitive.intSet();
Allocation[] allocations = this.allocations; SwapperMapping[] swapperMappings = this.swapperMappings;
for ( int id = 0; id < allocations.length; id++ ) for ( int id = 0; id < swapperMappings.length; id++ )
{ {
Allocation allocation = allocations[id]; SwapperMapping swapperMapping = swapperMappings[id];
if ( allocation == TOMBSTONE ) if ( swapperMapping == TOMBSTONE )
{ {
freeIds.add( id ); freeIds.add( id );
} }
Expand All @@ -157,9 +184,9 @@ public void vacuum( Consumer<IntPredicate> evictAllLoadedPagesCallback )
while ( itr.hasNext() ) while ( itr.hasNext() )
{ {
int freeId = itr.next(); int freeId = itr.next();
allocations[freeId] = null; swapperMappings[freeId] = null;
} }
this.allocations = allocations; // Volatile store synchronizes-with loads in getters. this.swapperMappings = swapperMappings; // Volatile store synchronizes-with loads in getters.
} }
synchronized ( free ) synchronized ( free )
{ {
Expand Down
Expand Up @@ -57,8 +57,8 @@ public void mustReturnAllocationWithSwapper() throws Exception
DummyPageSwapper b = new DummyPageSwapper( "b", 43 ); DummyPageSwapper b = new DummyPageSwapper( "b", 43 );
int idA = set.allocate( a ); int idA = set.allocate( a );
int idB = set.allocate( b ); int idB = set.allocate( b );
SwapperSet.Allocation allocA = set.getAllocation( idA ); SwapperSet.SwapperMapping allocA = set.getAllocation( idA );
SwapperSet.Allocation allocB = set.getAllocation( idB ); SwapperSet.SwapperMapping allocB = set.getAllocation( idB );
assertThat( allocA.swapper, is( a ) ); assertThat( allocA.swapper, is( a ) );
assertThat( allocB.swapper, is( b ) ); assertThat( allocB.swapper, is( b ) );
} }
Expand Down

0 comments on commit 9ebddc0

Please sign in to comment.