diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/PageCache.java b/community/io/src/main/java/org/neo4j/io/pagecache/PageCache.java index 450402372f10..faa88f46488f 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/PageCache.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/PageCache.java @@ -24,6 +24,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.OpenOption; import java.nio.file.StandardOpenOption; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -69,7 +70,7 @@ public interface PageCache extends AutoCloseable * If no mapping exist for this file, then returned {@link Optional} will report {@link Optional#isPresent()} * false. *

- * NOTE! User is responsible for closing the returned paged file. + * NOTE: The calling code is responsible for closing the returned paged file, if any. * * @param file The file to try to get the mapped paged file for. * @return {@link Optional} containing the {@link PagedFile} mapped by this {@link PageCache} for given file, or an @@ -78,11 +79,26 @@ public interface PageCache extends AutoCloseable */ Optional getExistingMapping( File file ) throws IOException; - /** Flush all dirty pages */ + /** + * List a snapshot of the current file mappings. + *

+ * The mappings can change as soon as this method returns. However, the returned {@link PagedFile}s will remain + * valid even if they are closed elsewhere. + *

+ * NOTE: The calling code is responsible for closing all the returned paged files. + * + * @throws IOException if page cache has been closed or page eviction problems occur. + */ + List listExistingMappings() throws IOException; + + /** + * Flush all dirty pages. + */ void flushAndForce() throws IOException; /** * Flush all dirty pages, but limit the rate of IO as advised by the given IOPSLimiter. + * * @param limiter The {@link IOLimiter} that determines if pauses or sleeps should be injected into the flushing * process to keep the IO rate down. */ @@ -92,21 +108,22 @@ public interface PageCache extends AutoCloseable * Close the page cache to prevent any future mapping of files. * This also releases any internal resources, including the {@link PageSwapperFactory} through its * {@link PageSwapperFactory#close() close} method. + * * @throws IllegalStateException if not all files have been unmapped, with {@link PagedFile#close()}, prior to * closing the page cache. In this case, the page cache WILL NOT be considered to be successfully closed. * @throws RuntimeException if the {@link PageSwapperFactory#close()} method throws. In this case the page cache * WILL BE considered to have been closed successfully. - **/ + */ void close() throws IllegalStateException; /** * The size in bytes of the pages managed by this cache. - **/ + */ int pageSize(); /** * The max number of cached pages. - **/ + */ int maxCachedPages(); /** diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCache.java b/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCache.java index 8c5f6dbec0ab..1e09c0a70b52 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCache.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/impl/muninn/MuninnPageCache.java @@ -24,6 +24,7 @@ import java.nio.file.CopyOption; import java.nio.file.OpenOption; import java.nio.file.StandardOpenOption; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -476,6 +477,25 @@ private void assertNotMapped( File file, FileIsMappedException.Operation operati } } + @Override + public synchronized List listExistingMappings() throws IOException + { + assertHealthy(); + ensureThreadsInitialised(); + + List list = new ArrayList<>(); + FileMapping current = mappedFiles; + + while ( current != null ) + { + MuninnPagedFile pagedFile = current.pagedFile; + pagedFile.incrementRefCount(); + list.add( pagedFile ); + current = current.next; + } + return list; + } + /** * Note: Must be called while synchronizing on the MuninnPageCache instance. */ diff --git a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java index 96c2b9c32477..fb4c13c8c8f7 100644 --- a/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java +++ b/community/io/src/test/java/org/neo4j/adversaries/pagecache/AdversarialPageCache.java @@ -27,6 +27,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.OpenOption; import java.nio.file.StandardOpenOption; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -83,6 +84,18 @@ public Optional getExistingMapping( File file ) throws IOException return optional; } + @Override + public List listExistingMappings() throws IOException + { + adversary.injectFailure( IOException.class, SecurityException.class ); + List list = delegate.listExistingMappings(); + for ( int i = 0; i < list.size(); i++ ) + { + list.set( i, new AdversarialPagedFile( list.get( i ), adversary ) ); + } + return list; + } + @Override public void flushAndForce() throws IOException { diff --git a/community/io/src/test/java/org/neo4j/io/pagecache/DelegatingPageCache.java b/community/io/src/test/java/org/neo4j/io/pagecache/DelegatingPageCache.java index 77d76e33102a..d9f0c99c1537 100644 --- a/community/io/src/test/java/org/neo4j/io/pagecache/DelegatingPageCache.java +++ b/community/io/src/test/java/org/neo4j/io/pagecache/DelegatingPageCache.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.OpenOption; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -45,6 +46,13 @@ public Optional getExistingMapping( File file ) throws IOException return delegate.getExistingMapping( file ); } + @Override + public List listExistingMappings() throws IOException + { + return delegate.listExistingMappings(); + } + + @Override public int pageSize() { return delegate.pageSize(); 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 625c53c67b52..44633c20466b 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 @@ -1151,29 +1151,6 @@ public void writeToPreviouslyBoundCursorAfterNextReturnsFalseMustThrow() throws verifyOnWriteCursor( this::checkPreviouslyBoundWriteCursorAfterFailedNext ); } - @Test - public void tryMappedPagedFileShouldReportMappedFilePresent() throws Exception - { - PageCache cache = createStandardPageCache(); - final File file = file( "a" ); - try ( PagedFile pf = cache.map( file, filePageSize ) ) - { - final Optional optional = cache.getExistingMapping( file ); - assertTrue( optional.isPresent() ); - final PagedFile actual = optional.get(); - assertThat( actual, sameInstance( pf ) ); - actual.close(); - } - } - - @Test - public void tryMappedPagedFileShouldReportNonMappedFileNotPresent() throws Exception - { - PageCache cache = createStandardPageCache(); - final Optional dont_exist = cache.getExistingMapping( new File( "dont_exist" ) ); - assertFalse( dont_exist.isPresent() ); - } - private void verifyOnReadCursor( ThrowingConsumer testTemplate ) throws IOException { @@ -1310,6 +1287,62 @@ private void checkPreviouslyBoundWriteCursorAfterFailedNext( PageCursorAction ac } } + @Test + public void tryMappedPagedFileShouldReportMappedFilePresent() throws Exception + { + configureStandardPageCache(); + final File file = file( "a" ); + try ( PagedFile pf = pageCache.map( file, filePageSize ) ) + { + final Optional optional = pageCache.getExistingMapping( file ); + assertTrue( optional.isPresent() ); + final PagedFile actual = optional.get(); + assertThat( actual, sameInstance( pf ) ); + actual.close(); + } + } + + @Test + public void tryMappedPagedFileShouldReportNonMappedFileNotPresent() throws Exception + { + configureStandardPageCache(); + final Optional dontExist = pageCache.getExistingMapping( new File( "dont_exist" ) ); + assertFalse( dontExist.isPresent() ); + } + + @Test + public void mustListExistingMappings() throws Exception + { + configureStandardPageCache(); + File f1 = existingFile( "1" ); + File f2 = existingFile( "2" ); + File f3 = existingFile( "3" ); // Not mapped at the time of calling listExistingMappings. + existingFile( "4" ); // Never mapped. + try ( PagedFile pf1 = pageCache.map( f1, filePageSize ); + PagedFile pf2 = pageCache.map( f2, filePageSize ) ) + { + pageCache.map( f3, filePageSize ).close(); + List existingMappings = pageCache.listExistingMappings(); + assertThat( existingMappings.size(), is( 2 ) ); + assertThat( existingMappings, containsInAnyOrder( pf1, pf2 ) ); + for ( PagedFile existingMapping : existingMappings ) + { + existingMapping.close(); + } + } + } + + @Test + public void listExistingMappingsMustThrowOnClosedPageCache() throws Exception + { + configureStandardPageCache(); + T pc = pageCache; + pageCache = null; + pc.close(); + expectedException.expect( IllegalStateException.class ); + pc.listExistingMappings(); + } + @Test( timeout = SHORT_TIMEOUT_MILLIS ) public void lastPageMustBeAccessibleWithNoGrowSpecified() throws IOException { diff --git a/enterprise/com/src/main/java/org/neo4j/com/storecopy/ExternallyManagedPageCache.java b/enterprise/com/src/main/java/org/neo4j/com/storecopy/ExternallyManagedPageCache.java index 4dc924488cf2..8971d78dcd9a 100644 --- a/enterprise/com/src/main/java/org/neo4j/com/storecopy/ExternallyManagedPageCache.java +++ b/enterprise/com/src/main/java/org/neo4j/com/storecopy/ExternallyManagedPageCache.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.OpenOption; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -76,6 +77,12 @@ public Optional getExistingMapping( File file ) throws IOException return delegate.getExistingMapping( file ); } + @Override + public List listExistingMappings() throws IOException + { + return delegate.listExistingMappings(); + } + @Override public void flushAndForce() throws IOException {