jdk/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java
2024-04-30 15:39:23 +00:00

1620 lines
54 KiB
Java

/*
* Copyright (c) 2000, 2024, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.nio.ch;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.Arena;
import java.lang.ref.Cleaner.Cleanable;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.Channel;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.FileLockInterruptionException;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Objects;
import jdk.internal.access.JavaIOFileDescriptorAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.event.FileForceEvent;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.SegmentFactories;
import jdk.internal.misc.Blocker;
import jdk.internal.misc.ExtendedMapMode;
import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM;
import jdk.internal.misc.VM.BufferPool;
import jdk.internal.ref.Cleaner;
import jdk.internal.ref.CleanerFactory;
import jdk.internal.access.foreign.UnmapperProxy;
public class FileChannelImpl
extends FileChannel
{
// Access to FileDescriptor internals
private static final JavaIOFileDescriptorAccess fdAccess =
SharedSecrets.getJavaIOFileDescriptorAccess();
// Used to make native read and write calls
private static final FileDispatcher nd = new FileDispatcherImpl();
// File descriptor
private final FileDescriptor fd;
// File access mode (immutable)
private final boolean writable;
private final boolean readable;
private final boolean sync; // O_SYNC or O_DSYNC
// Required to prevent finalization of creating stream (immutable)
private final Closeable parent;
// The path of the referenced file
// (null if the parent stream is created with a file descriptor)
private final String path;
// Thread-safe set of IDs of native threads, for signalling
private final NativeThreadSet threads = new NativeThreadSet(2);
// Lock for operations involving position and size
private final Object positionLock = new Object();
// blocking operations are not interruptible
private volatile boolean uninterruptible;
// DirectIO flag
private final boolean direct;
// IO alignment value for DirectIO
private final int alignment;
// Cleanable with an action which closes this channel's file descriptor
private final Cleanable closer;
private static class Closer implements Runnable {
private final FileDescriptor fd;
Closer(FileDescriptor fd) {
this.fd = fd;
}
public void run() {
try {
fdAccess.close(fd);
} catch (IOException ioe) {
// Rethrow as unchecked so the exception can be propagated as needed
throw new UncheckedIOException("close", ioe);
}
}
}
private FileChannelImpl(FileDescriptor fd, String path, boolean readable,
boolean writable, boolean sync, boolean direct,
Closeable parent)
{
this.fd = fd;
this.path = path;
this.readable = readable;
this.writable = writable;
this.sync = sync;
this.direct = direct;
this.parent = parent;
if (direct) {
assert path != null;
this.alignment = nd.setDirectIO(fd, path);
} else {
this.alignment = -1;
}
// Register a cleaning action if and only if there is no parent
// as the parent will take care of closing the file descriptor.
// FileChannel is used by the LambdaMetaFactory so a lambda cannot
// be used here hence we use a nested class instead.
this.closer = parent != null ? null :
CleanerFactory.cleaner().register(this, new Closer(fd));
}
// Used by FileInputStream::getChannel, FileOutputStream::getChannel,
// and RandomAccessFile::getChannel
public static FileChannel open(FileDescriptor fd, String path,
boolean readable, boolean writable,
boolean sync, boolean direct, Closeable parent)
{
return new FileChannelImpl(fd, path, readable, writable, sync, direct, parent);
}
private void ensureOpen() throws IOException {
if (!isOpen())
throw new ClosedChannelException();
}
public void setUninterruptible() {
uninterruptible = true;
}
private void beginBlocking() {
if (!uninterruptible) begin();
}
private void endBlocking(boolean completed) throws AsynchronousCloseException {
if (!uninterruptible) end(completed);
}
// -- Standard channel operations --
protected void implCloseChannel() throws IOException {
if (!fd.valid())
return; // nothing to do
// Release and invalidate any locks that we still hold
if (fileLockTable != null) {
for (FileLock fl: fileLockTable.removeAll()) {
synchronized (fl) {
if (fl.isValid()) {
nd.release(fd, fl.position(), fl.size());
((FileLockImpl)fl).invalidate();
}
}
}
}
// signal any threads blocked on this channel
threads.signalAndWait();
if (parent != null) {
//
// Close the fd via the parent stream's close method. The parent
// will reinvoke our close method, which is defined in the
// superclass AbstractInterruptibleChannel, but the isOpen logic in
// that method will prevent this method from being reinvoked.
//
parent.close();
} else { // parent == null hence closer != null
//
// Perform the cleaning action so it is not redone when
// this channel becomes phantom reachable.
//
try {
closer.clean();
} catch (UncheckedIOException uioe) {
throw uioe.getCause();
}
}
}
@Override
public int read(ByteBuffer dst) throws IOException {
ensureOpen();
if (!readable)
throw new NonReadableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
int n = 0;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return 0;
do {
boolean attempted = Blocker.begin(direct);
try {
n = IOUtil.read(fd, dst, -1, direct, alignment, nd);
} finally {
Blocker.end(attempted);
}
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
endBlocking(n > 0);
assert IOStatus.check(n);
}
}
}
@Override
public long read(ByteBuffer[] dsts, int offset, int length)
throws IOException
{
Objects.checkFromIndexSize(offset, length, dsts.length);
ensureOpen();
if (!readable)
throw new NonReadableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
long n = 0;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return 0;
do {
boolean attempted = Blocker.begin(direct);
try {
n = IOUtil.read(fd, dsts, offset, length, direct, alignment, nd);
} finally {
Blocker.end(attempted);
}
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
endBlocking(n > 0);
assert IOStatus.check(n);
}
}
}
@Override
public int write(ByteBuffer src) throws IOException {
ensureOpen();
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
int n = 0;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return 0;
do {
boolean attempted = Blocker.begin(sync || direct);
try {
n = IOUtil.write(fd, src, -1, direct, alignment, nd);
} finally {
Blocker.end(attempted);
}
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
endBlocking(n > 0);
assert IOStatus.check(n);
}
}
}
@Override
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException
{
Objects.checkFromIndexSize(offset, length, srcs.length);
ensureOpen();
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
long n = 0;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return 0;
do {
boolean attempted = Blocker.begin(sync || direct);
try {
n = IOUtil.write(fd, srcs, offset, length, direct, alignment, nd);
} finally {
Blocker.end(attempted);
}
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
endBlocking(n > 0);
assert IOStatus.check(n);
}
}
}
// -- Other operations --
@Override
public long position() throws IOException {
ensureOpen();
synchronized (positionLock) {
long p = -1;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return 0;
boolean append = fdAccess.getAppend(fd);
do {
// in append-mode then position is advanced to end before writing
p = (append) ? nd.size(fd) : nd.seek(fd, -1);
} while ((p == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(p);
} finally {
threads.remove(ti);
endBlocking(p > -1);
assert IOStatus.check(p);
}
}
}
@Override
public FileChannel position(long newPosition) throws IOException {
ensureOpen();
if (newPosition < 0)
throw new IllegalArgumentException();
synchronized (positionLock) {
long p = -1;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return null;
do {
p = nd.seek(fd, newPosition);
} while ((p == IOStatus.INTERRUPTED) && isOpen());
return this;
} finally {
threads.remove(ti);
endBlocking(p > -1);
assert IOStatus.check(p);
}
}
}
@Override
public long size() throws IOException {
ensureOpen();
synchronized (positionLock) {
long s = -1;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return -1;
do {
s = nd.size(fd);
} while ((s == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(s);
} finally {
threads.remove(ti);
endBlocking(s > -1);
assert IOStatus.check(s);
}
}
}
@Override
public FileChannel truncate(long newSize) throws IOException {
ensureOpen();
if (newSize < 0)
throw new IllegalArgumentException("Negative size");
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
int rv = -1;
long p = -1;
int ti = -1;
long rp = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return null;
// get current size
long size;
do {
size = nd.size(fd);
} while ((size == IOStatus.INTERRUPTED) && isOpen());
if (!isOpen())
return null;
// get current position
do {
p = nd.seek(fd, -1);
} while ((p == IOStatus.INTERRUPTED) && isOpen());
if (!isOpen())
return null;
assert p >= 0;
// truncate file if given size is less than the current size
if (newSize < size) {
do {
rv = nd.truncate(fd, newSize);
} while ((rv == IOStatus.INTERRUPTED) && isOpen());
if (!isOpen())
return null;
}
// if position is beyond new size then adjust it
if (p > newSize)
p = newSize;
do {
rp = nd.seek(fd, p);
} while ((rp == IOStatus.INTERRUPTED) && isOpen());
return this;
} finally {
threads.remove(ti);
endBlocking(rv > -1);
assert IOStatus.check(rv);
}
}
}
private void implForce(boolean metaData) throws IOException {
ensureOpen();
int rv = -1;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return;
do {
boolean attempted = Blocker.begin();
try {
rv = nd.force(fd, metaData);
} finally {
Blocker.end(attempted);
}
} while ((rv == IOStatus.INTERRUPTED) && isOpen());
} finally {
threads.remove(ti);
endBlocking(rv > -1);
assert IOStatus.check(rv);
}
}
@Override
public void force(boolean metaData) throws IOException {
if (!FileForceEvent.enabled()) {
implForce(metaData);
return;
}
long start = FileForceEvent.timestamp();
implForce(metaData);
FileForceEvent.offer(start, path, metaData);
}
// Assume at first that the underlying kernel supports sendfile/equivalent;
// set this to true if we find out later that it doesn't
//
private static volatile boolean transferToDirectNotSupported;
// Assume at first that the underlying kernel supports copy_file_range/equivalent;
// set this to true if we find out later that it doesn't
//
private static volatile boolean transferFromDirectNotSupported;
/**
* Marks the beginning of a transfer to or from this channel.
* @throws ClosedChannelException if channel is closed
*/
private int beforeTransfer() throws ClosedChannelException {
int ti = threads.add();
if (isOpen()) {
return ti;
} else {
threads.remove(ti);
throw new ClosedChannelException();
}
}
/**
* Marks the end of a transfer to or from this channel.
* @throws AsynchronousCloseException if not completed and the channel is closed
*/
private void afterTransfer(boolean completed, int ti) throws AsynchronousCloseException {
threads.remove(ti);
if (!completed && !isOpen()) {
throw new AsynchronousCloseException();
}
}
/**
* Invoked when ClosedChannelException is thrown during a transfer. This method
* translates it to an AsynchronousCloseException or ClosedByInterruptException. In
* the case of ClosedByInterruptException, it ensures that this channel and the
* source/target channel are closed.
*/
private AsynchronousCloseException transferFailed(ClosedChannelException cce, Channel other) {
ClosedByInterruptException cbie = null;
if (cce instanceof ClosedByInterruptException e) {
assert Thread.currentThread().isInterrupted();
cbie = e;
} else if (!uninterruptible && Thread.currentThread().isInterrupted()) {
cbie = new ClosedByInterruptException();
}
if (cbie != null) {
try {
this.close();
} catch (IOException ioe) {
cbie.addSuppressed(ioe);
}
try {
other.close();
} catch (IOException ioe) {
cbie.addSuppressed(ioe);
}
return cbie;
}
// one of the channels was closed during the transfer
if (cce instanceof AsynchronousCloseException ace) {
return ace;
} else {
var ace = new AsynchronousCloseException();
ace.addSuppressed(cce);
return ace;
}
}
/**
* Transfers bytes from this channel's file to the resource that the given file
* descriptor is connected to.
*/
private long transferToFileDescriptor(long position, int count, FileDescriptor targetFD) {
long n;
boolean append = fdAccess.getAppend(targetFD);
do {
n = nd.transferTo(fd, position, count, targetFD, append);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return n;
}
/**
* Transfers bytes from this channel's file to the given channel's file.
*/
private long transferToFileChannel(long position, int count, FileChannelImpl target)
throws IOException
{
final FileChannelImpl source = this;
boolean completed = false;
try {
beginBlocking();
int sourceIndex = source.beforeTransfer();
try {
int targetIndex = target.beforeTransfer();
try {
long n = transferToFileDescriptor(position, count, target.fd);
completed = (n >= 0);
return IOStatus.normalize(n);
} finally {
target.afterTransfer(completed, targetIndex);
}
} finally {
source.afterTransfer(completed, sourceIndex);
}
} finally {
endBlocking(completed);
}
}
/**
* Transfers bytes from this channel's file to the given channel's socket.
*/
private long transferToSocketChannel(long position, int count, SocketChannelImpl target)
throws IOException
{
final FileChannelImpl source = this;
boolean completed = false;
try {
beginBlocking();
int sourceIndex = source.beforeTransfer();
try {
target.beforeTransferTo();
try {
long n = transferToFileDescriptor(position, count, target.getFD());
completed = (n >= 0);
return IOStatus.normalize(n);
} finally {
target.afterTransferTo(completed);
}
} finally {
source.afterTransfer(completed, sourceIndex);
}
} finally {
endBlocking(completed);
}
}
/**
* Transfers bytes from this channel's file from the given channel's file. This
* implementation uses sendfile, copy_file_range or equivalent.
*/
private long transferToDirectInternal(long position, int count, WritableByteChannel target)
throws IOException
{
assert !nd.transferToDirectlyNeedsPositionLock() || Thread.holdsLock(positionLock);
return switch (target) {
case FileChannelImpl fci -> transferToFileChannel(position, count, fci);
case SocketChannelImpl sci -> transferToSocketChannel(position, count, sci);
default -> IOStatus.UNSUPPORTED_CASE;
};
}
/**
* Transfers bytes from this channel's file to the given channel's file or socket.
* @return the number of bytes transferred, UNSUPPORTED_CASE if this transfer cannot
* be done directly, or UNSUPPORTED if there is no direct support
*/
private long transferToDirect(long position, int count, WritableByteChannel target)
throws IOException
{
if (transferToDirectNotSupported)
return IOStatus.UNSUPPORTED;
if (target instanceof SelectableChannel sc && !nd.canTransferToDirectly(sc))
return IOStatus.UNSUPPORTED_CASE;
long n;
if (nd.transferToDirectlyNeedsPositionLock()) {
synchronized (positionLock) {
long pos = position();
try {
n = transferToDirectInternal(position, count, target);
} finally {
try {
position(pos);
} catch (ClosedChannelException ignore) {
// can't reset position if channel is closed
}
}
}
} else {
n = transferToDirectInternal(position, count, target);
}
if (n == IOStatus.UNSUPPORTED) {
transferToDirectNotSupported = true;
}
return n;
}
// Size threshold above which to use a mapped buffer;
// transferToArbitraryChannel() and transferFromArbitraryChannel()
// are faster for smaller transfers
private static final long MAPPED_TRANSFER_THRESHOLD = 16L*1024L;
// Maximum size to map when using a mapped buffer
private static final long MAPPED_TRANSFER_SIZE = 8L*1024L*1024L;
/**
* Transfers bytes from channel's file to the given channel. This implementation
* memory maps this channel's file.
*/
private long transferToTrustedChannel(long position, long count,
WritableByteChannel target)
throws IOException
{
if (count < MAPPED_TRANSFER_THRESHOLD)
return IOStatus.UNSUPPORTED_CASE;
boolean isSelChImpl = (target instanceof SelChImpl);
if (!((target instanceof FileChannelImpl) || isSelChImpl))
return IOStatus.UNSUPPORTED_CASE;
if (target == this) {
long posThis = position();
if ((posThis - count + 1 <= position)
&& (position - count + 1 <= posThis)
&& !nd.canTransferToFromOverlappedMap()) {
return IOStatus.UNSUPPORTED_CASE;
}
}
long remaining = count;
while (remaining > 0L) {
long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
try {
MappedByteBuffer dbb = map(MapMode.READ_ONLY, position, size);
try {
// write may block, closing this channel will not wake it up
int n = target.write(dbb);
assert n >= 0;
remaining -= n;
if (isSelChImpl) {
// one attempt to write to selectable channel
break;
}
assert n > 0;
position += n;
} finally {
unmap(dbb);
}
} catch (IOException ioe) {
// Only throw exception if no bytes have been written
if (remaining == count)
throw ioe;
break;
}
}
return count - remaining;
}
/**
* Transfers bytes from channel's file to the given channel.
*/
private long transferToArbitraryChannel(long position, long count,
WritableByteChannel target)
throws IOException
{
// Untrusted target: Use a newly-erased buffer
int c = (int) Math.min(count, TRANSFER_SIZE);
ByteBuffer bb = ByteBuffer.allocate(c);
long tw = 0; // Total bytes written
long pos = position;
try {
while (tw < count) {
bb.limit((int) Math.min(count - tw, TRANSFER_SIZE));
int nr = read(bb, pos);
if (nr <= 0)
break;
bb.flip();
// write may block, closing this channel will not wake it up
int nw = target.write(bb);
tw += nw;
if (nw != nr)
break;
pos += nw;
bb.clear();
}
return tw;
} catch (IOException x) {
if (tw > 0)
return tw;
throw x;
}
}
@Override
public long transferTo(long position, long count, WritableByteChannel target)
throws IOException
{
ensureOpen();
if (!target.isOpen())
throw new ClosedChannelException();
if (!readable)
throw new NonReadableChannelException();
if (target instanceof FileChannelImpl && !((FileChannelImpl) target).writable)
throw new NonWritableChannelException();
if ((position < 0) || (count < 0))
throw new IllegalArgumentException();
try {
final long sz = size();
if (position > sz)
return 0;
// System calls supporting fast transfers might not work on files
// which advertise zero size such as those in Linux /proc
if (sz > 0) {
// Now sz > 0 and position <= sz so remaining >= 0 and
// remaining == 0 if and only if sz == position
long remaining = sz - position;
if (remaining >= 0 && remaining < count)
count = remaining;
// Attempt a direct transfer, if the kernel supports it,
// limiting the number of bytes according to which platform
int icount = (int) Math.min(count, nd.maxDirectTransferSize());
long n;
if ((n = transferToDirect(position, icount, target)) >= 0)
return n;
// Attempt a mapped transfer, but only to trusted channel types
if ((n = transferToTrustedChannel(position, count, target)) >= 0)
return n;
}
// fallback to read/write loop
return transferToArbitraryChannel(position, count, target);
} catch (ClosedChannelException e) {
// throw AsynchronousCloseException or ClosedByInterruptException
throw transferFailed(e, target);
}
}
/**
* Transfers bytes into this channel's file from the resource that the given file
* descriptor is connected to.
*/
private long transferFromFileDescriptor(FileDescriptor srcFD, long position, long count) {
long n;
boolean append = fdAccess.getAppend(fd);
do {
n = nd.transferFrom(srcFD, fd, position, count, append);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return n;
}
/**
* Transfers bytes into this channel's file from the given channel's file. This
* implementation uses copy_file_range or equivalent.
*/
private long transferFromDirect(FileChannelImpl src, long position, long count)
throws IOException
{
if (transferFromDirectNotSupported)
return IOStatus.UNSUPPORTED;
final FileChannelImpl target = this;
boolean completed = false;
try {
beginBlocking();
int srcIndex = src.beforeTransfer();
try {
int targetIndex = target.beforeTransfer();
try {
long n = transferFromFileDescriptor(src.fd, position, count);
if (n == IOStatus.UNSUPPORTED) {
transferFromDirectNotSupported = true;
return IOStatus.UNSUPPORTED;
}
completed = (n >= 0);
return IOStatus.normalize(n);
} finally {
target.afterTransfer(completed, targetIndex);
}
} finally {
src.afterTransfer(completed, srcIndex);
}
} finally {
endBlocking(completed);
}
}
/**
* Transfers bytes into this channel's file from the given channel's file. This
* implementation memory maps the given channel's file.
*/
private long transferFromFileChannel(FileChannelImpl src, long position, long count)
throws IOException
{
if (count < MAPPED_TRANSFER_THRESHOLD)
return IOStatus.UNSUPPORTED_CASE;
synchronized (src.positionLock) {
long pos = src.position();
long max = Math.min(count, src.size() - pos);
if (src == this
&& (position() - max + 1 <= pos)
&& (pos - max + 1 <= position())
&& !nd.canTransferToFromOverlappedMap()) {
return IOStatus.UNSUPPORTED_CASE;
}
long remaining = max;
long p = pos;
while (remaining > 0L) {
long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
MappedByteBuffer bb = src.map(MapMode.READ_ONLY, p, size);
try {
long n = write(bb, position);
assert n > 0;
p += n;
position += n;
remaining -= n;
} catch (IOException ioe) {
// Only throw exception if no bytes have been written
if (remaining == max)
throw ioe;
break;
} finally {
unmap(bb);
}
}
long nwritten = max - remaining;
src.position(pos + nwritten);
return nwritten;
}
}
private static final int TRANSFER_SIZE = 8192;
/**
* Transfers bytes into this channel's file from the given channel.
*/
private long transferFromArbitraryChannel(ReadableByteChannel src,
long position, long count)
throws IOException
{
int c = (int) Math.min(count, TRANSFER_SIZE);
ByteBuffer bb = ByteBuffer.allocate(c);
long tw = 0; // Total bytes written
long pos = position;
try {
while (tw < count) {
bb.limit((int) Math.min((count - tw), TRANSFER_SIZE));
// read may block, closing this channel will not wake it up
int nr = src.read(bb);
if (nr <= 0)
break;
bb.flip();
int nw = write(bb, pos);
tw += nw;
if (nw != nr)
break;
pos += nw;
bb.clear();
}
return tw;
} catch (IOException x) {
if (tw > 0)
return tw;
throw x;
}
}
@Override
public long transferFrom(ReadableByteChannel src, long position, long count)
throws IOException
{
ensureOpen();
if (!src.isOpen())
throw new ClosedChannelException();
if (src instanceof FileChannelImpl fci && !fci.readable)
throw new NonReadableChannelException();
if (!writable)
throw new NonWritableChannelException();
if ((position < 0) || (count < 0))
throw new IllegalArgumentException();
try {
// System calls supporting fast transfers might not work on files
// which advertise zero size such as those in Linux /proc
if (src instanceof FileChannelImpl fci && fci.size() > 0) {
long n;
if ((n = transferFromDirect(fci, position, count)) >= 0)
return n;
if ((n = transferFromFileChannel(fci, position, count)) >= 0)
return n;
}
// fallback to read/write loop
return transferFromArbitraryChannel(src, position, count);
} catch (ClosedChannelException e) {
// throw AsynchronousCloseException or ClosedByInterruptException
throw transferFailed(e, src);
}
}
@Override
public int read(ByteBuffer dst, long position) throws IOException {
if (dst == null)
throw new NullPointerException();
if (position < 0)
throw new IllegalArgumentException("Negative position");
ensureOpen();
if (!readable)
throw new NonReadableChannelException();
if (direct)
Util.checkChannelPositionAligned(position, alignment);
if (nd.needsPositionLock()) {
synchronized (positionLock) {
return readInternal(dst, position);
}
} else {
return readInternal(dst, position);
}
}
private int readInternal(ByteBuffer dst, long position) throws IOException {
assert !nd.needsPositionLock() || Thread.holdsLock(positionLock);
int n = 0;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return -1;
do {
boolean attempted = Blocker.begin(direct);
try {
n = IOUtil.read(fd, dst, position, direct, alignment, nd);
} finally {
Blocker.end(attempted);
}
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
endBlocking(n > 0);
assert IOStatus.check(n);
}
}
@Override
public int write(ByteBuffer src, long position) throws IOException {
if (src == null)
throw new NullPointerException();
if (position < 0)
throw new IllegalArgumentException("Negative position");
ensureOpen();
if (!writable)
throw new NonWritableChannelException();
if (direct)
Util.checkChannelPositionAligned(position, alignment);
if (nd.needsPositionLock()) {
synchronized (positionLock) {
return writeInternal(src, position);
}
} else {
return writeInternal(src, position);
}
}
private int writeInternal(ByteBuffer src, long position) throws IOException {
assert !nd.needsPositionLock() || Thread.holdsLock(positionLock);
int n = 0;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return -1;
do {
boolean attempted = Blocker.begin(sync || direct);
try {
n = IOUtil.write(fd, src, position, direct, alignment, nd);
} finally {
Blocker.end(attempted);
}
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
threads.remove(ti);
endBlocking(n > 0);
assert IOStatus.check(n);
}
}
// -- Memory-mapped buffers --
private abstract static sealed class Unmapper
implements Runnable, UnmapperProxy
{
private final long address;
protected final long size;
protected final long cap;
private final FileDescriptor fd;
private final int pagePosition;
private Unmapper(long address, long size, long cap,
FileDescriptor fd, int pagePosition)
{
assert (address != 0);
this.address = address;
this.size = size;
this.cap = cap;
this.fd = fd;
this.pagePosition = pagePosition;
}
@Override
public long address() {
return address + pagePosition;
}
@Override
public FileDescriptor fileDescriptor() {
return fd;
}
@Override
public void run() {
unmap();
}
public long capacity() {
return cap;
}
public void unmap() {
nd.unmap(address, size);
// if this mapping has a valid file descriptor then we close it
if (fd.valid()) {
try {
nd.close(fd);
} catch (IOException ignore) {
// nothing we can do
}
}
decrementStats();
}
protected abstract void incrementStats();
protected abstract void decrementStats();
}
private static final class DefaultUnmapper extends Unmapper {
// keep track of non-sync mapped buffer usage
static volatile int count;
static volatile long totalSize;
static volatile long totalCapacity;
public DefaultUnmapper(long address, long size, long cap,
FileDescriptor fd, int pagePosition) {
super(address, size, cap, fd, pagePosition);
incrementStats();
}
protected void incrementStats() {
synchronized (DefaultUnmapper.class) {
count++;
totalSize += size;
totalCapacity += cap;
}
}
protected void decrementStats() {
synchronized (DefaultUnmapper.class) {
count--;
totalSize -= size;
totalCapacity -= cap;
}
}
public boolean isSync() {
return false;
}
}
private static final class SyncUnmapper extends Unmapper {
// keep track of mapped buffer usage
static volatile int count;
static volatile long totalSize;
static volatile long totalCapacity;
public SyncUnmapper(long address, long size, long cap,
FileDescriptor fd, int pagePosition) {
super(address, size, cap, fd, pagePosition);
incrementStats();
}
protected void incrementStats() {
synchronized (SyncUnmapper.class) {
count++;
totalSize += size;
totalCapacity += cap;
}
}
protected void decrementStats() {
synchronized (SyncUnmapper.class) {
count--;
totalSize -= size;
totalCapacity -= cap;
}
}
public boolean isSync() {
return true;
}
}
private static void unmap(MappedByteBuffer bb) {
Cleaner cl = ((DirectBuffer)bb).cleaner();
if (cl != null)
cl.clean();
}
private static final int MAP_INVALID = -1;
private static final int MAP_RO = 0;
private static final int MAP_RW = 1;
private static final int MAP_PV = 2;
@Override
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
if (size > Integer.MAX_VALUE)
throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");
boolean isSync = isSync(Objects.requireNonNull(mode, "Mode is null"));
int prot = toProt(mode);
Unmapper unmapper = mapInternal(mode, position, size, prot, isSync);
if (unmapper == null) {
// a valid file descriptor is not required
FileDescriptor dummy = new FileDescriptor();
if ((!writable) || (prot == MAP_RO))
return Util.newMappedByteBufferR(0, 0, dummy, null, isSync);
else
return Util.newMappedByteBuffer(0, 0, dummy, null, isSync);
} else if ((!writable) || (prot == MAP_RO)) {
return Util.newMappedByteBufferR((int)unmapper.capacity(),
unmapper.address(),
unmapper.fileDescriptor(),
unmapper, unmapper.isSync());
} else {
return Util.newMappedByteBuffer((int)unmapper.capacity(),
unmapper.address(),
unmapper.fileDescriptor(),
unmapper, unmapper.isSync());
}
}
@Override
public MemorySegment map(MapMode mode, long offset, long size, Arena arena)
throws IOException
{
Objects.requireNonNull(mode,"Mode is null");
Objects.requireNonNull(arena, "Arena is null");
MemorySessionImpl sessionImpl = MemorySessionImpl.toMemorySession(arena);
sessionImpl.checkValidState();
if (offset < 0)
throw new IllegalArgumentException("Requested bytes offset must be >= 0.");
if (size < 0)
throw new IllegalArgumentException("Requested bytes size must be >= 0.");
boolean isSync = isSync(mode);
int prot = toProt(mode);
Unmapper unmapper = mapInternal(mode, offset, size, prot, isSync);
boolean readOnly = false;
if (mode == MapMode.READ_ONLY) {
readOnly = true;
}
return SegmentFactories.mapSegment(size, unmapper, readOnly, sessionImpl);
}
private Unmapper mapInternal(MapMode mode, long position, long size, int prot, boolean isSync)
throws IOException
{
ensureOpen();
if (mode == null)
throw new NullPointerException("Mode is null");
if (position < 0L)
throw new IllegalArgumentException("Negative position");
if (size < 0L)
throw new IllegalArgumentException("Negative size");
if (position + size < 0)
throw new IllegalArgumentException("Position + size overflow");
checkMode(mode, prot, isSync);
long addr = -1;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return null;
long mapSize;
int pagePosition;
synchronized (positionLock) {
long filesize;
do {
filesize = nd.size(fd);
} while ((filesize == IOStatus.INTERRUPTED) && isOpen());
if (!isOpen())
return null;
if (filesize < position + size) { // Extend file size
if (!writable) {
throw new IOException("Channel not open for writing " +
"- cannot extend file to required size");
}
int rv;
do {
rv = nd.truncate(fd, position + size);
} while ((rv == IOStatus.INTERRUPTED) && isOpen());
if (!isOpen())
return null;
}
if (size == 0) {
return null;
}
pagePosition = (int)(position % nd.allocationGranularity());
long mapPosition = position - pagePosition;
mapSize = size + pagePosition;
try {
// If map did not throw an exception, the address is valid
addr = nd.map(fd, prot, mapPosition, mapSize, isSync);
} catch (OutOfMemoryError x) {
// An OutOfMemoryError may indicate that we've exhausted
// memory so force gc and re-attempt map
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException y) {
Thread.currentThread().interrupt();
}
try {
addr = nd.map(fd, prot, mapPosition, mapSize, isSync);
} catch (OutOfMemoryError y) {
// After a second OOME, fail
throw new IOException("Map failed", y);
}
}
} // synchronized
// On Windows, and potentially other platforms, we need an open
// file descriptor for some mapping operations.
FileDescriptor mfd;
try {
mfd = nd.duplicateForMapping(fd);
} catch (IOException ioe) {
nd.unmap(addr, mapSize);
throw ioe;
}
assert (IOStatus.checkAll(addr));
assert (addr % nd.allocationGranularity() == 0);
Unmapper um = (isSync
? new SyncUnmapper(addr, mapSize, size, mfd, pagePosition)
: new DefaultUnmapper(addr, mapSize, size, mfd, pagePosition));
return um;
} finally {
threads.remove(ti);
endBlocking(IOStatus.checkAll(addr));
}
}
private boolean isSync(MapMode mode) {
// Do not want to initialize ExtendedMapMode until
// after the module system has been initialized
return !VM.isModuleSystemInited() ? false :
(mode == ExtendedMapMode.READ_ONLY_SYNC ||
mode == ExtendedMapMode.READ_WRITE_SYNC);
}
private int toProt(MapMode mode) {
int prot;
if (mode == MapMode.READ_ONLY) {
prot = MAP_RO;
} else if (mode == MapMode.READ_WRITE) {
prot = MAP_RW;
} else if (mode == MapMode.PRIVATE) {
prot = MAP_PV;
} else if (mode == ExtendedMapMode.READ_ONLY_SYNC) {
prot = MAP_RO;
} else if (mode == ExtendedMapMode.READ_WRITE_SYNC) {
prot = MAP_RW;
} else {
prot = MAP_INVALID;
}
return prot;
}
private void checkMode(MapMode mode, int prot, boolean isSync) {
if (prot == MAP_INVALID) {
throw new UnsupportedOperationException();
}
if ((mode != MapMode.READ_ONLY) && mode != ExtendedMapMode.READ_ONLY_SYNC && !writable)
throw new NonWritableChannelException();
if (!readable)
throw new NonReadableChannelException();
// reject SYNC request if writeback is not enabled for this platform
if (isSync && !Unsafe.isWritebackEnabled()) {
throw new UnsupportedOperationException();
}
}
/**
* Invoked by sun.management.ManagementFactoryHelper to create the management
* interface for mapped buffers.
*/
public static BufferPool getMappedBufferPool() {
return new BufferPool() {
@Override
public String getName() {
return "mapped";
}
@Override
public long getCount() {
return DefaultUnmapper.count;
}
@Override
public long getTotalCapacity() {
return DefaultUnmapper.totalCapacity;
}
@Override
public long getMemoryUsed() {
return DefaultUnmapper.totalSize;
}
};
}
/**
* Invoked by sun.management.ManagementFactoryHelper to create the management
* interface for sync mapped buffers.
*/
public static BufferPool getSyncMappedBufferPool() {
return new BufferPool() {
@Override
public String getName() {
return "mapped - 'non-volatile memory'";
}
@Override
public long getCount() {
return SyncUnmapper.count;
}
@Override
public long getTotalCapacity() {
return SyncUnmapper.totalCapacity;
}
@Override
public long getMemoryUsed() {
return SyncUnmapper.totalSize;
}
};
}
// -- Locks --
// keeps track of locks on this file
private volatile FileLockTable fileLockTable;
private FileLockTable fileLockTable() throws IOException {
if (fileLockTable == null) {
synchronized (this) {
if (fileLockTable == null) {
int ti = threads.add();
try {
ensureOpen();
fileLockTable = new FileLockTable(this, fd);
} finally {
threads.remove(ti);
}
}
}
}
return fileLockTable;
}
@Override
public FileLock lock(long position, long size, boolean shared)
throws IOException
{
ensureOpen();
if (shared && !readable)
throw new NonReadableChannelException();
if (!shared && !writable)
throw new NonWritableChannelException();
if (size == 0)
size = Long.MAX_VALUE - Math.max(0, position);
FileLockImpl fli = new FileLockImpl(this, position, size, shared);
FileLockTable flt = fileLockTable();
flt.add(fli);
boolean completed = false;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return null;
int n;
do {
boolean attempted = Blocker.begin();
try {
n = nd.lock(fd, true, position, size, shared);
} finally {
Blocker.end(attempted);
}
} while ((n == FileDispatcher.INTERRUPTED) && isOpen());
if (isOpen()) {
if (n == FileDispatcher.RET_EX_LOCK) {
assert shared;
FileLockImpl fli2 = new FileLockImpl(this, position, size,
false);
flt.replace(fli, fli2);
fli = fli2;
}
completed = true;
}
} finally {
if (!completed)
flt.remove(fli);
threads.remove(ti);
try {
endBlocking(completed);
} catch (ClosedByInterruptException e) {
throw new FileLockInterruptionException();
}
}
return fli;
}
@Override
public FileLock tryLock(long position, long size, boolean shared)
throws IOException
{
ensureOpen();
if (shared && !readable)
throw new NonReadableChannelException();
if (!shared && !writable)
throw new NonWritableChannelException();
if (size == 0)
size = Long.MAX_VALUE - Math.max(0, position);
FileLockImpl fli = new FileLockImpl(this, position, size, shared);
FileLockTable flt = fileLockTable();
flt.add(fli);
int result;
int ti = threads.add();
try {
try {
ensureOpen();
result = nd.lock(fd, false, position, size, shared);
} catch (IOException e) {
flt.remove(fli);
throw e;
}
if (result == FileDispatcher.NO_LOCK) {
flt.remove(fli);
return null;
}
if (result == FileDispatcher.RET_EX_LOCK) {
assert shared;
FileLockImpl fli2 = new FileLockImpl(this, position, size,
false);
flt.replace(fli, fli2);
return fli2;
}
return fli;
} finally {
threads.remove(ti);
}
}
void release(FileLockImpl fli) throws IOException {
int ti = threads.add();
try {
ensureOpen();
nd.release(fd, fli.position(), fli.size());
} finally {
threads.remove(ti);
}
assert fileLockTable != null;
fileLockTable.remove(fli);
}
}