8164900: Add support for O_DIRECT

Add support for Direct I/O in FileChannel

Co-authored-by: Volker Simonis <volker.simonis@gmail.com>
Reviewed-by: alanb, bpb, alanbur, coffeys, aph, clanger, plevart, mli, psandoz, simonis
This commit is contained in:
Lucy Lu 2017-10-17 16:51:11 -07:00 committed by Brian Burkhalter
parent 97db013bd3
commit ec1c3bce45
30 changed files with 1523 additions and 45 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2017, 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
@ -111,6 +111,31 @@ public abstract class FileStore {
*/
public abstract long getUsableSpace() throws IOException;
/**
* Returns the number of bytes per block in this file store.
*
* <p> File storage is typically organized into discrete sequences of bytes
* called <i>blocks</i>. A block is the smallest storage unit of a file store.
* Every read and write operation is performed on a multiple of blocks.
*
* @implSpec The implementation in this class throws an
* {@code UnsupportedOperationException}.
*
* @return a positive value representing the block size of this file store,
* in bytes
*
* @throws IOException
* if an I/O error occurs
*
* @throws UnsupportedOperationException
* if the operation is not supported
*
* @since 10
*/
public long getBlockSize() throws IOException {
throw new UnsupportedOperationException();
}
/**
* Returns the number of unallocated bytes in the file store.
*

View file

@ -41,6 +41,11 @@ import java.nio.channels.OverlappingFileLockException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.FileStore;
import java.nio.file.FileSystemException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
@ -87,6 +92,12 @@ public class FileChannelImpl
// Positional-read is 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;
@ -103,14 +114,22 @@ public class FileChannelImpl
}
private FileChannelImpl(FileDescriptor fd, String path, boolean readable,
boolean writable, Object parent)
boolean writable, boolean direct, Object parent)
{
this.fd = fd;
this.readable = readable;
this.writable = writable;
this.parent = parent;
this.path = path;
this.direct = direct;
this.nd = new FileDispatcherImpl();
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
@ -125,7 +144,14 @@ public class FileChannelImpl
boolean readable, boolean writable,
Object parent)
{
return new FileChannelImpl(fd, path, readable, writable, parent);
return new FileChannelImpl(fd, path, readable, writable, false, parent);
}
public static FileChannel open(FileDescriptor fd, String path,
boolean readable, boolean writable,
boolean direct, Object parent)
{
return new FileChannelImpl(fd, path, readable, writable, direct, parent);
}
private void ensureOpen() throws IOException {
@ -181,6 +207,8 @@ public class FileChannelImpl
if (!readable)
throw new NonReadableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
int n = 0;
int ti = -1;
try {
@ -189,7 +217,7 @@ public class FileChannelImpl
if (!isOpen())
return 0;
do {
n = IOUtil.read(fd, dst, -1, nd);
n = IOUtil.read(fd, dst, -1, direct, alignment, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
@ -209,6 +237,8 @@ public class FileChannelImpl
if (!readable)
throw new NonReadableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
long n = 0;
int ti = -1;
try {
@ -217,7 +247,8 @@ public class FileChannelImpl
if (!isOpen())
return 0;
do {
n = IOUtil.read(fd, dsts, offset, length, nd);
n = IOUtil.read(fd, dsts, offset, length,
direct, alignment, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
@ -233,6 +264,8 @@ public class FileChannelImpl
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
int n = 0;
int ti = -1;
try {
@ -241,7 +274,7 @@ public class FileChannelImpl
if (!isOpen())
return 0;
do {
n = IOUtil.write(fd, src, -1, nd);
n = IOUtil.write(fd, src, -1, direct, alignment, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
@ -261,6 +294,8 @@ public class FileChannelImpl
if (!writable)
throw new NonWritableChannelException();
synchronized (positionLock) {
if (direct)
Util.checkChannelPositionAligned(position(), alignment);
long n = 0;
int ti = -1;
try {
@ -269,7 +304,8 @@ public class FileChannelImpl
if (!isOpen())
return 0;
do {
n = IOUtil.write(fd, srcs, offset, length, nd);
n = IOUtil.write(fd, srcs, offset, length,
direct, alignment, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
@ -752,6 +788,8 @@ public class FileChannelImpl
throw new IllegalArgumentException("Negative position");
if (!readable)
throw new NonReadableChannelException();
if (direct)
Util.checkChannelPositionAligned(position, alignment);
ensureOpen();
if (nd.needsPositionLock()) {
synchronized (positionLock) {
@ -774,7 +812,7 @@ public class FileChannelImpl
if (!isOpen())
return -1;
do {
n = IOUtil.read(fd, dst, position, nd);
n = IOUtil.read(fd, dst, position, direct, alignment, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {
@ -791,6 +829,8 @@ public class FileChannelImpl
throw new IllegalArgumentException("Negative position");
if (!writable)
throw new NonWritableChannelException();
if (direct)
Util.checkChannelPositionAligned(position, alignment);
ensureOpen();
if (nd.needsPositionLock()) {
synchronized (positionLock) {
@ -811,7 +851,7 @@ public class FileChannelImpl
if (!isOpen())
return -1;
do {
n = IOUtil.write(fd, src, position, nd);
n = IOUtil.write(fd, src, position, direct, alignment, nd);
} while ((n == IOStatus.INTERRUPTED) && isOpen());
return IOStatus.normalize(n);
} finally {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2017, 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
@ -61,4 +61,6 @@ abstract class FileDispatcher extends NativeDispatcher {
abstract boolean canTransferToDirectly(SelectableChannel sc);
abstract boolean transferToDirectlyNeedsPositionLock();
abstract int setDirectIO(FileDescriptor fd, String path);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2017, 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
@ -47,22 +47,38 @@ public class IOUtil {
NativeDispatcher nd)
throws IOException
{
if (src instanceof DirectBuffer)
return writeFromNativeBuffer(fd, src, position, nd);
return write(fd, src, position, false, -1, nd);
}
static int write(FileDescriptor fd, ByteBuffer src, long position,
boolean directIO, int alignment, NativeDispatcher nd)
throws IOException
{
if (src instanceof DirectBuffer) {
return writeFromNativeBuffer(fd, src, position,
directIO, alignment, nd);
}
// Substitute a native buffer
int pos = src.position();
int lim = src.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
ByteBuffer bb;
if (directIO) {
Util.checkRemainingBufferSizeAligned(rem, alignment);
bb = Util.getTemporaryAlignedDirectBuffer(rem, alignment);
} else {
bb = Util.getTemporaryDirectBuffer(rem);
}
try {
bb.put(src);
bb.flip();
// Do not update src until we see how many bytes were written
src.position(pos);
int n = writeFromNativeBuffer(fd, bb, position, nd);
int n = writeFromNativeBuffer(fd, bb, position,
directIO, alignment, nd);
if (n > 0) {
// now update src
src.position(pos + n);
@ -74,7 +90,8 @@ public class IOUtil {
}
private static int writeFromNativeBuffer(FileDescriptor fd, ByteBuffer bb,
long position, NativeDispatcher nd)
long position, boolean directIO,
int alignment, NativeDispatcher nd)
throws IOException
{
int pos = bb.position();
@ -82,6 +99,11 @@ public class IOUtil {
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (directIO) {
Util.checkBufferPositionAligned(bb, pos, alignment);
Util.checkRemainingBufferSizeAligned(rem, alignment);
}
int written = 0;
if (rem == 0)
return 0;
@ -100,12 +122,19 @@ public class IOUtil {
static long write(FileDescriptor fd, ByteBuffer[] bufs, NativeDispatcher nd)
throws IOException
{
return write(fd, bufs, 0, bufs.length, nd);
return write(fd, bufs, 0, bufs.length, false, -1, nd);
}
static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
NativeDispatcher nd)
throws IOException
{
return write(fd, bufs, offset, length, false, -1, nd);
}
static long write(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
boolean directIO, int alignment, NativeDispatcher nd)
throws IOException
{
IOVecWrapper vec = IOVecWrapper.get(length);
@ -122,12 +151,20 @@ public class IOUtil {
int lim = buf.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (directIO)
Util.checkRemainingBufferSizeAligned(rem, alignment);
if (rem > 0) {
vec.setBuffer(iov_len, buf, pos, rem);
// allocate shadow buffer to ensure I/O is done with direct buffer
if (!(buf instanceof DirectBuffer)) {
ByteBuffer shadow = Util.getTemporaryDirectBuffer(rem);
ByteBuffer shadow;
if (directIO)
shadow = Util.getTemporaryAlignedDirectBuffer(rem,
alignment);
else
shadow = Util.getTemporaryDirectBuffer(rem);
shadow.put(buf);
shadow.flip();
vec.setShadow(iov_len, shadow);
@ -185,16 +222,33 @@ public class IOUtil {
static int read(FileDescriptor fd, ByteBuffer dst, long position,
NativeDispatcher nd)
throws IOException
{
return read(fd, dst, position, false, -1, nd);
}
static int read(FileDescriptor fd, ByteBuffer dst, long position,
boolean directIO, int alignment, NativeDispatcher nd)
throws IOException
{
if (dst.isReadOnly())
throw new IllegalArgumentException("Read-only buffer");
if (dst instanceof DirectBuffer)
return readIntoNativeBuffer(fd, dst, position, nd);
return readIntoNativeBuffer(fd, dst, position,
directIO, alignment, nd);
// Substitute a native buffer
ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
ByteBuffer bb;
int rem = dst.remaining();
if (directIO) {
Util.checkRemainingBufferSizeAligned(rem, alignment);
bb = Util.getTemporaryAlignedDirectBuffer(rem,
alignment);
} else {
bb = Util.getTemporaryDirectBuffer(rem);
}
try {
int n = readIntoNativeBuffer(fd, bb, position, nd);
int n = readIntoNativeBuffer(fd, bb, position,
directIO, alignment,nd);
bb.flip();
if (n > 0)
dst.put(bb);
@ -205,7 +259,8 @@ public class IOUtil {
}
private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb,
long position, NativeDispatcher nd)
long position, boolean directIO,
int alignment, NativeDispatcher nd)
throws IOException
{
int pos = bb.position();
@ -213,6 +268,11 @@ public class IOUtil {
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (directIO) {
Util.checkBufferPositionAligned(bb, pos, alignment);
Util.checkRemainingBufferSizeAligned(rem, alignment);
}
if (rem == 0)
return 0;
int n = 0;
@ -230,12 +290,19 @@ public class IOUtil {
static long read(FileDescriptor fd, ByteBuffer[] bufs, NativeDispatcher nd)
throws IOException
{
return read(fd, bufs, 0, bufs.length, nd);
return read(fd, bufs, 0, bufs.length, false, -1, nd);
}
static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
NativeDispatcher nd)
throws IOException
{
return read(fd, bufs, offset, bufs.length, false, -1, nd);
}
static long read(FileDescriptor fd, ByteBuffer[] bufs, int offset, int length,
boolean directIO, int alignment, NativeDispatcher nd)
throws IOException
{
IOVecWrapper vec = IOVecWrapper.get(length);
@ -255,12 +322,21 @@ public class IOUtil {
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (directIO)
Util.checkRemainingBufferSizeAligned(rem, alignment);
if (rem > 0) {
vec.setBuffer(iov_len, buf, pos, rem);
// allocate shadow buffer to ensure I/O is done with direct buffer
if (!(buf instanceof DirectBuffer)) {
ByteBuffer shadow = Util.getTemporaryDirectBuffer(rem);
ByteBuffer shadow;
if (directIO) {
shadow = Util.getTemporaryAlignedDirectBuffer(rem,
alignment);
} else {
shadow = Util.getTemporaryDirectBuffer(rem);
}
vec.setShadow(iov_len, shadow);
buf = shadow;
pos = shadow.position();

View file

@ -37,7 +37,7 @@ import java.util.Iterator;
import java.util.Set;
import jdk.internal.misc.Unsafe;
import sun.security.action.GetPropertyAction;
import java.io.IOException;
public class Util {
@ -236,6 +236,33 @@ public class Util {
}
}
/**
* Returns a temporary buffer of at least the given size and
* aligned to the alignment
*/
public static ByteBuffer getTemporaryAlignedDirectBuffer(int size,
int alignment) {
if (isBufferTooLarge(size)) {
return ByteBuffer.allocateDirect(size + alignment - 1)
.alignedSlice(alignment);
}
BufferCache cache = bufferCache.get();
ByteBuffer buf = cache.get(size);
if (buf != null) {
if (buf.alignmentOffset(0, alignment) == 0) {
return buf;
}
} else {
if (!cache.isEmpty()) {
buf = cache.removeFirst();
free(buf);
}
}
return ByteBuffer.allocateDirect(size + alignment - 1)
.alignedSlice(alignment);
}
/**
* Releases a temporary buffer by returning to the cache or freeing it.
*/
@ -459,4 +486,37 @@ public class Util {
}
return dbb;
}
static void checkBufferPositionAligned(ByteBuffer bb,
int pos, int alignment)
throws IOException
{
if (bb.alignmentOffset(pos, alignment) != 0) {
throw new IOException("Current location of the bytebuffer ("
+ pos + ") is not a multiple of the block size ("
+ alignment + ")");
}
}
static void checkRemainingBufferSizeAligned(int rem,
int alignment)
throws IOException
{
if (rem % alignment != 0) {
throw new IOException("Number of remaining bytes ("
+ rem + ") is not a multiple of the block size ("
+ alignment + ")");
}
}
static void checkChannelPositionAligned(long position,
int alignment)
throws IOException
{
if (position % alignment != 0) {
throw new IOException("Channel position (" + position
+ ") is not a multiple of the block size ("
+ alignment + ")");
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2017, 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
@ -137,6 +137,8 @@ public final class ExtendedOptions {
public static final InternalOption<Void> FILE_TREE = new InternalOption<>();
public static final InternalOption<Void> DIRECT = new InternalOption<>();
public static final InternalOption<Integer> SENSITIVITY_HIGH = new InternalOption<>();
public static final InternalOption<Integer> SENSITIVITY_MEDIUM = new InternalOption<>();
public static final InternalOption<Integer> SENSITIVITY_LOW = new InternalOption<>();