Skip to content

Commit

Permalink
Add a file rename method to the PageCache interface
Browse files Browse the repository at this point in the history
We need this since only the page cache is aware of any custom IO subsystem configurations we might have, and renaming files is an operation that is used in a fair number of places.
  • Loading branch information
chrisvest authored and burqen committed Sep 14, 2016
1 parent f458b1b commit f0c619b
Show file tree
Hide file tree
Showing 19 changed files with 308 additions and 72 deletions.
Expand Up @@ -135,14 +135,9 @@ public void deleteRecursively( File directory ) throws IOException
} }


@Override @Override
public boolean move( File from, File to, CopyOption... copyOptions ) throws IOException public void renameFile( File from, File to, CopyOption... copyOptions ) throws IOException
{ {
if ( copyOptions.length > 0 ) Files.move( from.toPath(), to.toPath(), copyOptions );
{
Files.move( from.toPath(), to.toPath(), copyOptions );
return true; // will throw if failure
}
return FileUtils.renameFile( from, to );
} }


@Override @Override
Expand Down
Expand Up @@ -38,11 +38,9 @@
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;


import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;


/** /**
Expand Down Expand Up @@ -170,10 +168,9 @@ public void deleteRecursively( File directory ) throws IOException
} }


@Override @Override
public boolean move( File from, File to, CopyOption... copyOptions ) throws IOException public void renameFile( File from, File to, CopyOption... copyOptions ) throws IOException
{ {
Files.move( path( from ), path( to ), copyOptions ); Files.move( path( from ), path( to ), copyOptions );
return true;
} }


@Override @Override
Expand Down
Expand Up @@ -58,7 +58,7 @@ public interface FileSystemAbstraction


void deleteRecursively( File directory ) throws IOException; void deleteRecursively( File directory ) throws IOException;


boolean move( File from, File to, CopyOption... copyOptions ) throws IOException; void renameFile( File from, File to, CopyOption... copyOptions ) throws IOException;


File[] listFiles( File directory ); File[] listFiles( File directory );


Expand Down
33 changes: 5 additions & 28 deletions community/io/src/main/java/org/neo4j/io/fs/FileUtils.java
Expand Up @@ -42,6 +42,7 @@
import java.nio.channels.SeekableByteChannel; import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
Expand Down Expand Up @@ -120,7 +121,7 @@ public static boolean deleteFile( File file )
* Utility method that moves a file from its current location to the * Utility method that moves a file from its current location to the
* new target location. If rename fails (for example if the target is * new target location. If rename fails (for example if the target is
* another disk) a copy/delete will be performed instead. This is not a rename, * another disk) a copy/delete will be performed instead. This is not a rename,
* use {@link #renameFile(File, File)} instead. * use {@link #renameFile(File, File, CopyOption...)} instead.
* *
* @param toMove The File object to move. * @param toMove The File object to move.
* @param target Target file to move to. * @param target Target file to move to.
Expand Down Expand Up @@ -161,7 +162,7 @@ public static void moveFile( File toMove, File target ) throws IOException
* Utility method that moves a file from its current location to the * Utility method that moves a file from its current location to the
* provided target directory. If rename fails (for example if the target is * provided target directory. If rename fails (for example if the target is
* another disk) a copy/delete will be performed instead. This is not a rename, * another disk) a copy/delete will be performed instead. This is not a rename,
* use {@link #renameFile(File, File)} instead. * use {@link #renameFile(File, File, CopyOption...)} instead.
* *
* @param toMove The File object to move. * @param toMove The File object to move.
* @param targetDirectory the destination directory * @param targetDirectory the destination directory
Expand All @@ -181,33 +182,9 @@ public static File moveFileToDirectory( File toMove, File targetDirectory ) thro
return target; return target;
} }


public static boolean renameFile( File srcFile, File renameToFile ) throws IOException public static void renameFile( File srcFile, File renameToFile, CopyOption... copyOptions ) throws IOException
{ {
if ( !srcFile.exists() ) Files.move( srcFile.toPath(), renameToFile.toPath(), copyOptions );
{
throw new FileNotFoundException( "Source file[" + srcFile.getName() + "] not found" );
}
if ( renameToFile.exists() )
{
throw new FileNotFoundException( "Target file[" + renameToFile.getName() + "] already exists" );
}
if ( !renameToFile.getParentFile().isDirectory() )
{
throw new FileNotFoundException( "Target directory[" + renameToFile.getParent() + "] does not exists" );
}
int count = 0;
boolean renamed;
do
{
renamed = srcFile.renameTo( renameToFile );
if ( !renamed )
{
count++;
waitAndThenTriggerGC();
}
}
while ( !renamed && count <= WINDOWS_RETRY_COUNT );
return renamed;
} }


public static void truncateFile( SeekableByteChannel fileChannel, long position ) public static void truncateFile( SeekableByteChannel fileChannel, long position )
Expand Down
14 changes: 14 additions & 0 deletions community/io/src/main/java/org/neo4j/io/pagecache/PageCache.java
Expand Up @@ -21,6 +21,7 @@


import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.OpenOption; import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Optional; import java.util.Optional;
Expand Down Expand Up @@ -94,4 +95,17 @@ public interface PageCache extends AutoCloseable


/** The max number of cached pages. */ /** The max number of cached pages. */
int maxCachedPages(); int maxCachedPages();

/**
* Move the file from the given source path, to the given target path.
*
* Both files have to be unmapped when performing the move, otherwise an exception will be thrown.
*
* @param sourceFile The name of the file to move.
* @param targetFile The new name of the file after the move.
* @param copyOptions Options to modify the behaviour of the move in possibly platform specific ways. In particular,
* {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} may be used to overwrite any existing file at the
* target path name, instead of throwing an exception.
*/
void moveFile( File sourceFile, File targetFile, CopyOption... copyOptions ) throws IOException;
} }
Expand Up @@ -21,6 +21,8 @@


import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Path;


import org.neo4j.io.fs.FileSystemAbstraction; import org.neo4j.io.fs.FileSystemAbstraction;


Expand Down Expand Up @@ -97,4 +99,21 @@ PageSwapper createPageSwapper(
* durable regardless of which method that does the forcing. * durable regardless of which method that does the forcing.
*/ */
void syncDevice() throws IOException; void syncDevice() throws IOException;

/**
* Move the file by the given source file name, to the path indicated by the given target file name.
*
* The provided list of {@link CopyOption CopyOptions} can be used to modify and influence platform specific
* behaviour. In particular, {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} may be used to overwrite any
* existing file at the target path name, instead of throwing an exception.
*
* Implementors are free to assume that neither file name will be mapped by the page cache at the time of the move,
* and thus the move will see no interference from concurrent IO operations.
* @param sourceFile The existing file to move.
* @param targetFile The desired new path of the source file. This file should not exist, unless
* {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} is given as a {@code copyOption}.
* @param copyOptions Options to modify the behaviour of the move in possibly platform specific ways.
* @see java.nio.file.Files#move(Path, Path, CopyOption...)
*/
void moveUnopenedFile( File sourceFile, File targetFile, CopyOption... copyOptions ) throws IOException;
} }
@@ -0,0 +1,31 @@
/*
* 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.impl;

import java.io.File;
import java.io.IOException;

public class CannotMoveMappedFileException extends IOException
{
public CannotMoveMappedFileException( File file )
{
super( "Cannot move mapped file: " + file );
}
}
Expand Up @@ -21,6 +21,7 @@


import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;


import org.neo4j.io.fs.FileSystemAbstraction; import org.neo4j.io.fs.FileSystemAbstraction;
Expand Down Expand Up @@ -70,6 +71,12 @@ public void syncDevice()
// Nothing do to, since we `fsync` files individually in `force()`. // Nothing do to, since we `fsync` files individually in `force()`.
} }


@Override
public void moveUnopenedFile( File sourceFile, File targetFile, CopyOption... copyOptions ) throws IOException
{
fs.renameFile( sourceFile, targetFile, copyOptions );
}

@Override @Override
public String implementationName() public String implementationName()
{ {
Expand Down
Expand Up @@ -21,6 +21,7 @@


import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.OpenOption; import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Arrays; import java.util.Arrays;
Expand All @@ -37,6 +38,7 @@
import org.neo4j.io.pagecache.PageCacheOpenOptions; import org.neo4j.io.pagecache.PageCacheOpenOptions;
import org.neo4j.io.pagecache.PageSwapperFactory; import org.neo4j.io.pagecache.PageSwapperFactory;
import org.neo4j.io.pagecache.PagedFile; import org.neo4j.io.pagecache.PagedFile;
import org.neo4j.io.pagecache.impl.CannotMoveMappedFileException;
import org.neo4j.io.pagecache.tracing.EvictionEvent; import org.neo4j.io.pagecache.tracing.EvictionEvent;
import org.neo4j.io.pagecache.tracing.EvictionRunEvent; import org.neo4j.io.pagecache.tracing.EvictionRunEvent;
import org.neo4j.io.pagecache.tracing.FlushEventOpportunity; import org.neo4j.io.pagecache.tracing.FlushEventOpportunity;
Expand Down Expand Up @@ -370,23 +372,50 @@ public synchronized Optional<PagedFile> getExistingMapping( File file ) throws I
ensureThreadsInitialised(); ensureThreadsInitialised();


file = file.getCanonicalFile(); file = file.getCanonicalFile();
MuninnPagedFile pagedFile = tryGetMappingOrNull( file );
if ( pagedFile != null )
{
pagedFile.incrementRefCount();
return Optional.of( pagedFile );
}
return Optional.empty();
}


private MuninnPagedFile tryGetMappingOrNull( File file ) throws IOException
{
FileMapping current = mappedFiles; FileMapping current = mappedFiles;


// find an existing mapping // find an existing mapping
while ( current != null ) while ( current != null )
{ {
if ( current.file.equals( file ) ) if ( current.file.equals( file ) )
{ {
MuninnPagedFile pagedFile = current.pagedFile; return current.pagedFile;
pagedFile.incrementRefCount();
return Optional.of( current.pagedFile );
} }
current = current.next; current = current.next;
} }


// no mapping exists // no mapping exists
return Optional.empty(); return null;
}

@Override
public synchronized void moveFile( File sourceFile, File targetFile, CopyOption... copyOptions )
throws IOException
{
sourceFile = sourceFile.getCanonicalFile();
targetFile = targetFile.getCanonicalFile();
throwIfMapped( sourceFile );
throwIfMapped( targetFile );
swapperFactory.moveUnopenedFile( sourceFile, targetFile, copyOptions );
}

private void throwIfMapped( File file ) throws IOException
{
if ( tryGetMappingOrNull( file ) != null )
{
throw new CannotMoveMappedFileException( file );
}
} }


/** /**
Expand Down
Expand Up @@ -69,10 +69,10 @@ public StoreChannel open( File fileName, String mode ) throws IOException
return AdversarialFileChannel.wrap( (StoreFileChannel) delegate.open( fileName, mode ), adversary ); return AdversarialFileChannel.wrap( (StoreFileChannel) delegate.open( fileName, mode ), adversary );
} }


public boolean move( File from, File to, CopyOption... copyOptions ) throws IOException public void renameFile( File from, File to, CopyOption... copyOptions ) throws IOException
{ {
adversary.injectFailure( FileNotFoundException.class, SecurityException.class ); adversary.injectFailure( FileNotFoundException.class, SecurityException.class );
return delegate.move( from, to, copyOptions ); delegate.renameFile( from, to, copyOptions );
} }


public OutputStream openAsOutputStream( File fileName, boolean append ) throws IOException public OutputStream openAsOutputStream( File fileName, boolean append ) throws IOException
Expand Down
Expand Up @@ -24,6 +24,8 @@
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.OpenOption; import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Objects; import java.util.Objects;
Expand All @@ -33,6 +35,7 @@
import org.neo4j.io.pagecache.IOLimiter; import org.neo4j.io.pagecache.IOLimiter;
import org.neo4j.io.pagecache.PageCache; import org.neo4j.io.pagecache.PageCache;
import org.neo4j.io.pagecache.PagedFile; import org.neo4j.io.pagecache.PagedFile;
import org.neo4j.io.pagecache.impl.CannotMoveMappedFileException;


/** /**
* A {@linkplain PageCache page cache} that wraps another page cache and an {@linkplain Adversary adversary} to provide * A {@linkplain PageCache page cache} that wraps another page cache and an {@linkplain Adversary adversary} to provide
Expand Down Expand Up @@ -112,4 +115,13 @@ public int maxCachedPages()
{ {
return delegate.maxCachedPages(); return delegate.maxCachedPages();
} }

@Override
public void moveFile( File sourceFile, File targetFile, CopyOption... copyOptions )
throws IOException
{
adversary.injectFailure( CannotMoveMappedFileException.class, FileAlreadyExistsException.class,
IOException.class, SecurityException.class );
delegate.moveFile( sourceFile, targetFile, copyOptions );
}
} }
Expand Up @@ -86,9 +86,9 @@ public long lastModifiedTime( File file ) throws IOException
} }


@Override @Override
public boolean move( File from, File to, CopyOption... copyOptions ) throws IOException public void renameFile( File from, File to, CopyOption... copyOptions ) throws IOException
{ {
return delegate.move( from, to, copyOptions ); delegate.renameFile( from, to, copyOptions );
} }


@Override @Override
Expand Down

0 comments on commit f0c619b

Please sign in to comment.