Skip to content

Commit

Permalink
6980847: (fs) Files.copy needs to be "tuned"
Browse files Browse the repository at this point in the history
Reviewed-by: alanb
  • Loading branch information
Brian Burkhalter committed Jun 23, 2022
1 parent d579916 commit b8db0c3
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 32 deletions.
117 changes: 111 additions & 6 deletions src/java.base/unix/classes/sun/nio/fs/UnixCopyFile.java
Expand Up @@ -26,6 +26,7 @@
package sun.nio.fs;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryNotEmptyException;
Expand All @@ -36,6 +37,9 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import jdk.internal.misc.Blocker;
import sun.nio.ch.DirectBuffer;
import sun.nio.ch.IOStatus;
import sun.nio.ch.Util;
import static sun.nio.fs.UnixNativeDispatcher.*;
import static sun.nio.fs.UnixConstants.*;

Expand All @@ -44,6 +48,9 @@
*/

class UnixCopyFile {
// minimum size of a temporary direct buffer
private static final int MIN_BUFFER_SIZE = 16384;

private UnixCopyFile() { }

// The flags that control how a file is copied or moved
Expand Down Expand Up @@ -217,6 +224,47 @@ private static void copyDirectory(UnixPath source,
}
}

// calculate the least common multiple of two values;
// the parameters in general will be powers of two likely in the
// range [4096, 65536] so this algorithm is expected to converge
// when it is rarely called
private static long lcm(long x, long y) {
assert x > 0 && y > 0 : "Non-positive parameter";

long u = x;
long v = y;

while (u != v) {
if (u < v)
u += x;
else // u > v
v += y;
}

return u;
}

// calculate temporary direct buffer size
private static int temporaryBufferSize(UnixPath source, UnixPath target) {
int bufferSize = MIN_BUFFER_SIZE;
try {
long bss = UnixFileStoreAttributes.get(source).blockSize();
long bst = UnixFileStoreAttributes.get(target).blockSize();
if (bss > 0 && bst > 0) {
bufferSize = (int)(bss == bst ? bss : lcm(bss, bst));
}
if (bufferSize < MIN_BUFFER_SIZE) {
int factor = (MIN_BUFFER_SIZE + bufferSize - 1)/bufferSize;
bufferSize *= factor;
}
} catch (UnixException ignored) {
}
return bufferSize;
}

// whether direct copying is supported on this platform
private static volatile boolean directCopyNotSupported;

// copy regular file from source to target
private static void copyFile(UnixPath source,
UnixFileAttributes attrs,
Expand Down Expand Up @@ -248,17 +296,43 @@ private static void copyFile(UnixPath source,
// set to true when file and attributes copied
boolean complete = false;
try {
// transfer bytes to target file
try {
boolean copied = false;
if (!directCopyNotSupported) {
// copy bytes to target using platform function
long comp = Blocker.begin();
try {
transfer(fo, fi, addressToPollForCancel);
int res = directCopy0(fo, fi, addressToPollForCancel);
if (res == 0) {
copied = true;
} else if (res == IOStatus.UNSUPPORTED) {
directCopyNotSupported = true;
}
} catch (UnixException x) {
x.rethrowAsIOException(source, target);
} finally {
Blocker.end(comp);
}
} catch (UnixException x) {
x.rethrowAsIOException(source, target);
}

if (!copied) {
// copy bytes to target via a temporary direct buffer
int bufferSize = temporaryBufferSize(source, target);
ByteBuffer buf = Util.getTemporaryDirectBuffer(bufferSize);
try {
long comp = Blocker.begin();
try {
bufferedCopy0(fo, fi, ((DirectBuffer)buf).address(),
bufferSize, addressToPollForCancel);
} catch (UnixException x) {
x.rethrowAsIOException(source, target);
} finally {
Blocker.end(comp);
}
} finally {
Util.releaseTemporaryDirectBuffer(buf);
}
}

// copy owner/permissions
if (flags.copyPosixAttributes) {
try {
Expand Down Expand Up @@ -628,7 +702,38 @@ static void copy(final UnixPath source,

// -- native methods --

static native void transfer(int dst, int src, long addressToPollForCancel)
/**
* Copies data between file descriptors {@code src} and {@code dst} using
* a platform-specific function or system call possibly having kernel
* support.
*
* @param dst destination file descriptor
* @param src source file descriptor
* @param addressToPollForCancel address to check for cancellation
* (a non-zero value written to this address indicates cancel)
*
* @return 0 on success, UNAVAILABLE if the platform function would block,
* UNSUPPORTED_CASE if the call does not work with the given
* parameters, or UNSUPPORTED if direct copying is not supported
* on this platform
*/
private static native int directCopy0(int dst, int src,
long addressToPollForCancel)
throws UnixException;

/**
* Copies data between file descriptors {@code src} and {@code dst} using
* an intermediate temporary direct buffer.
*
* @param dst destination file descriptor
* @param src source file descriptor
* @param address the address of the temporary direct buffer's array
* @param size the size of the temporary direct buffer's array
* @param addressToPollForCancel address to check for cancellation
* (a non-zero value written to this address indicates cancel)
*/
private static native void bufferedCopy0(int dst, int src, long address,
int size, long addressToPollForCancel)
throws UnixException;

static {
Expand Down
71 changes: 49 additions & 22 deletions src/java.base/unix/native/libnio/fs/UnixCopyFile.c
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -27,11 +27,15 @@
#include "jni_util.h"
#include "jlong.h"

#include "nio.h"

#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#if defined(__linux__)
#include <sys/sendfile.h>
#include <fcntl.h>
#elif defined(_ALLBSD_SOURCE)
#include <copyfile.h>
#endif
Expand Down Expand Up @@ -68,14 +72,28 @@ int fcopyfile_callback(int what, int stage, copyfile_state_t state,
}
#endif

// Transfer via user-space buffers
void transfer(JNIEnv* env, jint dst, jint src, volatile jint* cancel)
// Copy via an intermediate temporary direct buffer
JNIEXPORT void JNICALL
Java_sun_nio_fs_UnixCopyFile_bufferCopy0
(JNIEnv* env, jclass this, jint dst, jint src, jlong address,
jint transferSize, jlong cancelAddress)
{
char buf[8192];
volatile jint* cancel = (jint*)jlong_to_ptr(cancelAddress);

char* buf = (char*)address;

#if defined(__linux__)
int advice = POSIX_FADV_SEQUENTIAL | // sequential data access
POSIX_FADV_NOREUSE | // will access only once
POSIX_FADV_WILLNEED; // will access in near future

// ignore the return value hence any failure
posix_fadvise(src, 0, 0, advice);
#endif

for (;;) {
ssize_t n, pos, len;
RESTARTABLE(read((int)src, &buf, sizeof(buf)), n);
RESTARTABLE(read((int)src, buf, transferSize), n);
if (n <= 0) {
if (n < 0)
throwUnixException(env, errno);
Expand All @@ -101,12 +119,18 @@ void transfer(JNIEnv* env, jint dst, jint src, volatile jint* cancel)
}
}

/**
* Transfer all bytes from src to dst within the kernel if possible (Linux),
* otherwise via user-space buffers
*/
JNIEXPORT void JNICALL
Java_sun_nio_fs_UnixCopyFile_transfer
// Copy all bytes from src to dst, within the kernel if possible (Linux),
// and return zero, otherwise return the appropriate status code.
//
// Return value
// 0 on success
// IOS_UNAVAILABLE if the platform function would block
// IOS_UNSUPPORTED_CASE if the call does not work with the given parameters
// IOS_UNSUPPORTED if direct copying is not supported on this platform
// IOS_THROWN if a Java exception is thrown
//
JNIEXPORT jint JNICALL
Java_sun_nio_fs_UnixCopyFile_directCopy0
(JNIEnv* env, jclass this, jint dst, jint src, jlong cancelAddress)
{
volatile jint* cancel = (jint*)jlong_to_ptr(cancelAddress);
Expand All @@ -119,20 +143,21 @@ Java_sun_nio_fs_UnixCopyFile_transfer
ssize_t bytes_sent;
do {
RESTARTABLE(sendfile64(dst, src, NULL, count), bytes_sent);
if (bytes_sent == -1) {
if (errno == EINVAL || errno == ENOSYS) {
// Fall back to copying via user-space buffers
transfer(env, dst, src, cancel);
} else {
throwUnixException(env, errno);
}
return;
if (bytes_sent < 0) {
if (errno == EAGAIN)
return IOS_UNAVAILABLE;
if (errno == EINVAL || errno == ENOSYS)
return IOS_UNSUPPORTED_CASE;
throwUnixException(env, errno);
return IOS_THROWN;
}
if (cancel != NULL && *cancel != 0) {
throwUnixException(env, ECANCELED);
return;
return IOS_THROWN;
}
} while (bytes_sent > 0);

return 0;
#elif defined(_ALLBSD_SOURCE)
copyfile_state_t state;
if (cancel != NULL) {
Expand All @@ -147,11 +172,13 @@ Java_sun_nio_fs_UnixCopyFile_transfer
if (state != NULL)
copyfile_state_free(state);
throwUnixException(env, errno_fcopyfile);
return;
return IOS_THROWN;
}
if (state != NULL)
copyfile_state_free(state);

return 0;
#else
transfer(env, dst, src, cancel);
return IOS_UNSUPPORTED;
#endif
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -132,6 +132,7 @@ private WindowsConstants() { }
// copy flags
public static final int COPY_FILE_FAIL_IF_EXISTS = 0x00000001;
public static final int COPY_FILE_COPY_SYMLINK = 0x00000800;
public static final int COPY_FILE_NO_BUFFERING = 0x00001000;

// move flags
public static final int MOVEFILE_REPLACE_EXISTING = 0x00000001;
Expand Down
17 changes: 14 additions & 3 deletions src/java.base/windows/classes/sun/nio/fs/WindowsFileCopy.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,8 +25,14 @@

package sun.nio.fs;

import java.nio.file.*;
import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.LinkOption;
import java.nio.file.LinkPermission;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.ExecutionException;

import static sun.nio.fs.WindowsNativeDispatcher.*;
Expand All @@ -37,6 +43,9 @@
*/

class WindowsFileCopy {
// file size above which copying uses unbuffered I/O
private static final long UNBUFFERED_IO_THRESHOLD = 314572800; // 300 MiB

private WindowsFileCopy() {
}

Expand Down Expand Up @@ -174,7 +183,9 @@ static void copy(final WindowsPath source,

// Use CopyFileEx if the file is not a directory or junction
if (!sourceAttrs.isDirectory() && !sourceAttrs.isDirectoryLink()) {
final int flags = (!followLinks) ? COPY_FILE_COPY_SYMLINK : 0;
boolean isBuffering = sourceAttrs.size() <= UNBUFFERED_IO_THRESHOLD;
final int flags = (followLinks ? 0 : COPY_FILE_COPY_SYMLINK) |
(isBuffering ? 0 : COPY_FILE_NO_BUFFERING);

if (interruptible) {
// interruptible copy
Expand Down

1 comment on commit b8db0c3

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.