-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Optimize file copies by using NIO methods #27459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,10 @@ | |
| // limitations under the License. | ||
| package com.google.devtools.build.lib.vfs; | ||
|
|
||
| import static com.google.devtools.build.lib.vfs.FileSystem.translateNioToIoException; | ||
| import static java.nio.charset.StandardCharsets.ISO_8859_1; | ||
| import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; | ||
| import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; | ||
|
|
||
| import com.google.common.base.Preconditions; | ||
| import com.google.common.collect.ImmutableList; | ||
|
|
@@ -374,15 +377,31 @@ public static ByteSink asByteSink(final Path path) { | |
| */ | ||
| @ThreadSafe // but not atomic | ||
| public static void copyFile(Path from, Path to) throws IOException { | ||
| var fromNio = from.getFileSystem().getNioPath(from.asFragment()); | ||
| var toNio = to.getFileSystem().getNioPath(to.asFragment()); | ||
| if (fromNio != null && toNio != null) { | ||
| // Fast path: Files.copy uses various optimizations such as kernel buffers (sendfile on Unix) | ||
| // or copy-on-write (clonefile on macOS, copy_file_range on Linux with a supported file system). | ||
| try { | ||
| java.nio.file.Files.copy(fromNio, toNio, REPLACE_EXISTING, COPY_ATTRIBUTES); | ||
| } catch (IOException e) { | ||
| throw translateNioToIoException(from.asFragment(), e); | ||
| } | ||
| return; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also replace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did that and realized that the copy fallback in |
||
| try { | ||
| // Target may be a symlink, in which case opening a stream below would not actually replace | ||
| // it. | ||
| to.delete(); | ||
| } catch (IOException e) { | ||
| throw new IOException("error copying file: " | ||
| + "couldn't delete destination: " + e.getMessage()); | ||
| } | ||
| try (InputStream in = from.getInputStream(); | ||
| OutputStream out = to.getOutputStream()) { | ||
| ByteStreams.copy(in, out); | ||
| // This may use a faster copy method (such as via an in-kernel buffer) if both streams are | ||
| // backed by files. | ||
| in.transferTo(out); | ||
| } | ||
| to.setLastModifiedTime(from.getLastModifiedTime()); // Preserve mtime. | ||
| if (!from.isWritable()) { | ||
|
|
@@ -400,25 +419,6 @@ public enum MoveResult { | |
| FILE_COPIED, | ||
| } | ||
|
|
||
| /** | ||
| * copyLargeBuffer is a replacement for ByteStreams.copy which uses a larger buffer. Increasing | ||
| * the buffer size is a performance improvement when copying from/to FUSE file systems, where | ||
| * individual requests are more costly, but can also be larger. | ||
| */ | ||
| private static long copyLargeBuffer(InputStream from, OutputStream to) throws IOException { | ||
| byte[] buf = new byte[1 * 1024 * 1024]; // Match libfuse3 maximum FUSE request size of 1 MB. | ||
| long total = 0; | ||
| while (true) { | ||
| int r = from.read(buf); | ||
| if (r == -1) { | ||
| break; | ||
| } | ||
| to.write(buf, 0, r); | ||
| total += r; | ||
| } | ||
| return total; | ||
| } | ||
|
|
||
| /** | ||
| * Moves the file from location "from" to location "to", while overwriting a potentially existing | ||
| * "to". If "from" is a regular file, its last modified time, executable and writable bits are | ||
|
|
@@ -449,29 +449,17 @@ public static MoveResult moveFile(Path from, Path to) throws IOException { | |
| // Fallback to a copy. | ||
| FileStatus stat = from.stat(Symlinks.NOFOLLOW); | ||
| if (stat.isFile()) { | ||
| // Target may be a symlink, in which case opening a stream below would not actually replace | ||
| // it. | ||
| to.delete(); | ||
| try (InputStream in = from.getInputStream(); | ||
| OutputStream out = to.getOutputStream()) { | ||
| copyLargeBuffer(in, out); | ||
| try { | ||
| copyFile(from, to); | ||
| } catch (FileAccessException e) { | ||
| // Rules can accidentally make output non-readable, let's fix that (b/150963503) | ||
| if (!from.isReadable()) { | ||
| from.setReadable(true); | ||
| try (InputStream in = from.getInputStream(); | ||
| OutputStream out = to.getOutputStream()) { | ||
| copyLargeBuffer(in, out); | ||
| } | ||
| copyFile(from, to); | ||
| } else { | ||
| throw e; | ||
| } | ||
| } | ||
| to.setLastModifiedTime(stat.getLastModifiedTime()); // Preserve mtime. | ||
| if (!from.isWritable()) { | ||
| to.setWritable(false); // Make file read-only if original was read-only. | ||
| } | ||
| to.setExecutable(from.isExecutable()); // Copy executable bit. | ||
| } else if (stat.isSymbolicLink()) { | ||
| PathFragment fromTarget = from.readSymbolicLink(); | ||
| try { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.