--- old/make/mapfiles/libnio/mapfile-linux 2016-11-16 10:28:17.515497893 -0800 +++ new/make/mapfiles/libnio/mapfile-linux 2016-11-16 10:28:17.459497893 -0800 @@ -60,6 +60,7 @@ Java_sun_nio_ch_FileChannelImpl_position0; Java_sun_nio_ch_FileChannelImpl_transferTo0; Java_sun_nio_ch_FileChannelImpl_unmap0; + Java_sun_nio_ch_FileChannelImpl_setDirect0; Java_sun_nio_ch_FileDispatcherImpl_close0; Java_sun_nio_ch_FileDispatcherImpl_closeIntFD; Java_sun_nio_ch_FileDispatcherImpl_force0; --- old/src/java.base/share/classes/java/nio/channels/FileChannel.java 2016-11-16 10:28:17.792497896 -0800 +++ new/src/java.base/share/classes/java/nio/channels/FileChannel.java 2016-11-16 10:28:17.733497895 -0800 @@ -287,6 +287,7 @@ return provider.newFileChannel(path, options, attrs); } + @SuppressWarnings({"unchecked", "rawtypes"}) // generic array construction private static final FileAttribute[] NO_ATTRIBUTES = new FileAttribute[0]; --- old/src/java.base/share/classes/java/nio/file/FileStore.java 2016-11-16 10:28:17.991497898 -0800 +++ new/src/java.base/share/classes/java/nio/file/FileStore.java 2016-11-16 10:28:17.934497897 -0800 @@ -112,6 +112,17 @@ public abstract long getUsableSpace() throws IOException; /** + * Returns block size in Bytes to this Java virtual machine on the file + * store. + * + * @return block size + * + * @throws IOException + * if an I/O error occurs + */ + public abstract int getBlockSize() throws IOException; + + /** * Returns the number of unallocated bytes in the file store. * *

The returned number of unallocated bytes is a hint, but not a --- old/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileStore.java 2016-11-16 10:28:18.210497900 -0800 +++ new/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileStore.java 2016-11-16 10:28:18.154497899 -0800 @@ -82,6 +82,11 @@ } @Override + public int getBlockSize() throws IOException { + throw new UnsupportedOperationException("getBlockSize"); + } + + @Override public long getUsableSpace() throws IOException { throw new UnsupportedOperationException("getUsableSpace"); } --- old/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2016-11-16 10:28:18.418497902 -0800 +++ new/src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java 2016-11-16 10:28:18.362497901 -0800 @@ -49,6 +49,12 @@ import jdk.internal.ref.Cleaner; import sun.security.action.GetPropertyAction; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.FileSystemException; + public class FileChannelImpl extends FileChannel { @@ -85,24 +91,55 @@ // Positional-read is not interruptible private volatile boolean uninterruptible; + //directIO + private volatile boolean direct; + + //IO alignment for DirectIO + private volatile int alignment = -1; + 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; + if (direct) + setDirectIO(); this.nd = new FileDispatcherImpl(); } + private void setDirectIO() + { + if (alignment == -1) { + Path fp = Paths.get(path); + try { + setDirect0(fd); + FileStore fs = Files.getFileStore(fp); + this.alignment = fs.getBlockSize(); + } catch (IOException e) { + this.alignment = -1; + throw new UnsupportedOperationException("Error setting up DirectIO"); + } + } + } + // Used by FileInputStream.getChannel(), FileOutputStream.getChannel // and RandomAccessFile.getChannel() public static FileChannel open(FileDescriptor fd, String path, 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 { @@ -150,6 +187,9 @@ } public int read(ByteBuffer dst) throws IOException { + if (direct && ((dst.remaining() % alignment != 0) || + (position() % alignment != 0))) + throw new IllegalArgumentException("IO size or position is not aligned"); ensureOpen(); if (!readable) throw new NonReadableChannelException(); @@ -162,7 +202,7 @@ 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 { @@ -178,6 +218,10 @@ { if ((offset < 0) || (length < 0) || (offset > dsts.length - length)) throw new IndexOutOfBoundsException(); + + if (direct && (position() % alignment != 0)) + throw new IllegalArgumentException("Position is not aligned"); + ensureOpen(); if (!readable) throw new NonReadableChannelException(); @@ -190,7 +234,7 @@ 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 { @@ -202,9 +246,13 @@ } public int write(ByteBuffer src) throws IOException { + ensureOpen(); if (!writable) throw new NonWritableChannelException(); + if (direct && ((src.remaining() % alignment != 0) || + (position() % alignment != 0))) + throw new IllegalArgumentException("IO size or position is not aligned"); synchronized (positionLock) { int n = 0; int ti = -1; @@ -214,7 +262,7 @@ 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 { @@ -233,6 +281,10 @@ ensureOpen(); if (!writable) throw new NonWritableChannelException(); + + if (direct && (position() % alignment != 0)) + throw new IllegalArgumentException("Position is not aligned"); + synchronized (positionLock) { long n = 0; int ti = -1; @@ -242,7 +294,7 @@ 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 { @@ -725,6 +777,10 @@ throw new IllegalArgumentException("Negative position"); if (!readable) throw new NonReadableChannelException(); + if (direct && ((dst.remaining() % alignment != 0) || + (position % alignment != 0))) + throw new IllegalArgumentException("IO size or position is not aligned"); + ensureOpen(); if (nd.needsPositionLock()) { synchronized (positionLock) { @@ -747,7 +803,7 @@ 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 { @@ -764,6 +820,10 @@ throw new IllegalArgumentException("Negative position"); if (!writable) throw new NonWritableChannelException(); + if (direct && ((src.remaining() % alignment != 0) || + (position % alignment != 0))) + throw new IllegalArgumentException("IO size or position is not aligned"); + ensureOpen(); if (nd.needsPositionLock()) { synchronized (positionLock) { @@ -784,7 +844,7 @@ 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 { @@ -1228,6 +1288,8 @@ // Caches fieldIDs private static native long initIDs(); + private native int setDirect0(FileDescriptor fd) throws IOException; + static { IOUtil.load(); allocationGranularity = initIDs(); --- old/src/java.base/share/classes/sun/nio/ch/IOUtil.java 2016-11-16 10:28:18.626497904 -0800 +++ new/src/java.base/share/classes/sun/nio/ch/IOUtil.java 2016-11-16 10:28:18.569497903 -0800 @@ -47,22 +47,37 @@ 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) { + if (rem % alignment != 0) + throw new IllegalArgumentException("DirectByteBuffer is not properly aligned"); + 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); @@ -73,8 +88,8 @@ } } - private static int writeFromNativeBuffer(FileDescriptor fd, ByteBuffer bb, - long position, NativeDispatcher nd) + private static int writeFromNativeBuffer(FileDescriptor fd, ByteBuffer bb, long position, + boolean directIO, int alignment, NativeDispatcher nd) throws IOException { int pos = bb.position(); @@ -82,6 +97,10 @@ assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); + if (directIO && (bb.alignmentOffset(pos, alignment) != 0) && (rem % alignment != 0)) { + throw new IllegalArgumentException("DirectByteBuffer is not properly aligned"); + } + int written = 0; if (rem == 0) return 0; @@ -100,13 +119,20 @@ 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); boolean completed = false; @@ -122,12 +148,19 @@ int lim = buf.limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); + if (directIO && (rem % alignment != 0)) + throw new IllegalArgumentException("IO size or position is not aligned"); + 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); @@ -186,15 +219,29 @@ 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; + if (directIO) { + if (dst.remaining() % alignment != 0) + throw new IllegalArgumentException("DirectByteBuffer is not properly aligned"); + bb = Util.getTemporaryAlignedDirectBuffer(dst.remaining(), alignment); + } else { + bb = Util.getTemporaryDirectBuffer(dst.remaining()); + } 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); @@ -204,14 +251,17 @@ } } - private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb, - long position, NativeDispatcher nd) + private static int readIntoNativeBuffer(FileDescriptor fd, ByteBuffer bb, long position, + boolean directIO, int alignment, NativeDispatcher nd) throws IOException { int pos = bb.position(); int lim = bb.limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); + + if(directIO && !(bb.alignmentOffset(pos, alignment) != 0) && (rem % alignment != 0)) + throw new IllegalArgumentException("DirectByteBuffer is not properly aligned"); if (rem == 0) return 0; @@ -230,13 +280,20 @@ 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); boolean completed = false; @@ -255,12 +312,20 @@ assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); + if (directIO && (rem % alignment != 0)) + throw new IllegalArgumentException("IO size or position is not aligned"); + 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(); --- old/src/java.base/share/classes/sun/nio/ch/Util.java 2016-11-16 10:28:18.829497906 -0800 +++ new/src/java.base/share/classes/sun/nio/ch/Util.java 2016-11-16 10:28:18.772497905 -0800 @@ -234,6 +234,32 @@ } /** + * 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 instanceof DirectBuffer) { + 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. */ public static void releaseTemporaryDirectBuffer(ByteBuffer buf) { --- old/src/java.base/share/classes/sun/nio/fs/ExtendedOptions.java 2016-11-16 10:28:19.032497908 -0800 +++ new/src/java.base/share/classes/sun/nio/fs/ExtendedOptions.java 2016-11-16 10:28:18.976497907 -0800 @@ -137,6 +137,8 @@ public static final InternalOption FILE_TREE = new InternalOption<>(); + public static final InternalOption DIRECT = new InternalOption<>(); + public static final InternalOption SENSITIVITY_HIGH = new InternalOption<>(); public static final InternalOption SENSITIVITY_MEDIUM = new InternalOption<>(); public static final InternalOption SENSITIVITY_LOW = new InternalOption<>(); --- old/src/java.base/unix/classes/sun/nio/fs/UnixChannelFactory.java 2016-11-16 10:28:19.248497910 -0800 +++ new/src/java.base/unix/classes/sun/nio/fs/UnixChannelFactory.java 2016-11-16 10:28:19.191497909 -0800 @@ -64,6 +64,7 @@ boolean deleteOnClose; boolean sync; boolean dsync; + boolean direct; static Flags toFlags(Set options) { Flags flags = new Flags(); @@ -88,6 +89,12 @@ flags.noFollowLinks = true; continue; } + + if (ExtendedOptions.DIRECT.matches(option)) { + flags.direct = true; + continue; + } + if (option == null) throw new NullPointerException(); throw new UnsupportedOperationException(option + " not supported"); @@ -134,7 +141,7 @@ throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed"); FileDescriptor fdObj = open(dfd, path, pathForPermissionCheck, flags, mode); - return FileChannelImpl.open(fdObj, path.toString(), flags.read, flags.write, null); + return FileChannelImpl.open(fdObj, path.toString(), flags.read, flags.write, flags.direct, null); } /** --- old/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template 2016-11-16 10:28:19.447497912 -0800 +++ new/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template 2016-11-16 10:28:19.390497911 -0800 @@ -27,6 +27,10 @@ @@END_COPYRIGHT@@ +#ifdef __linux__ +#define _GNU_SOURCE +#endif + #include #include #include @@ -70,6 +74,15 @@ static final int PREFIX_O_NOFOLLOW = 00; #endif +#ifdef O_DIRECT + static final int PREFIX_DIRECT = O_DIRECT; +#elif F_NOCACHE + static final int PREFIX_DIRECT = F_NOCACHE; +//#elif DIRECTIO_ON +// static final int PREFIX_DIRECT = DIRECTIO_ON; +#else + static final int PREFIX_DIRECT = 00; +#endif static final int PREFIX_S_IAMB = (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH); --- old/src/java.base/unix/classes/sun/nio/fs/UnixFileStore.java 2016-11-16 10:28:19.648497913 -0800 +++ new/src/java.base/unix/classes/sun/nio/fs/UnixFileStore.java 2016-11-16 10:28:19.591497913 -0800 @@ -126,6 +126,12 @@ } @Override + public int getBlockSize() throws IOException { + UnixFileStoreAttributes attrs = readAttributes(); + return (int)attrs.blockSize(); + } + + @Override public long getUnallocatedSpace() throws IOException { UnixFileStoreAttributes attrs = readAttributes(); return attrs.blockSize() * attrs.freeBlocks(); --- old/src/java.base/unix/native/libnio/ch/FileChannelImpl.c 2016-11-16 10:28:19.868497916 -0800 +++ new/src/java.base/unix/native/libnio/ch/FileChannelImpl.c 2016-11-16 10:28:19.810497915 -0800 @@ -276,3 +276,31 @@ #endif } +JNIEXPORT jint JNICALL +Java_sun_nio_ch_FileChannelImpl_setDirect0(JNIEnv *env, jobject this, jobject fObj) +{ + jint fd = fdval(env, fObj); + jint result; + +#ifdef O_DIRECT + result = fcntl(fd, F_SETFL, O_DIRECT); + if (result == -1) { + JNU_ThrowIOExceptionWithLastError(env, "DirectIO setup failed"); + } + return result; +#elif F_NOCACHE + result = fcntl(fd, F_NOCACHE, 1); + if (result == -1) { + JNU_ThrowIOExceptionWithLastError(env, "DirectIO setup failed"); + } + return result; +//#elif DIRECTIO_ON +// result = directio(fd, DIRECTIO_ON); +// if (result == -1) { +// JNU_ThrowIOExceptionWithLastError(env, "DirectIO setup failed"); +// } + return result; +#else + return -1; +#endif +} --- old/src/java.base/windows/classes/sun/nio/fs/WindowsChannelFactory.java 2016-11-16 10:28:20.090497918 -0800 +++ new/src/java.base/windows/classes/sun/nio/fs/WindowsChannelFactory.java 2016-11-16 10:28:20.033497917 -0800 @@ -74,6 +74,7 @@ boolean overlapped; boolean sync; boolean dsync; + boolean direct; // non-standard boolean shareRead = true; @@ -121,6 +122,10 @@ flags.shareDelete = false; continue; } + if (ExtendedOptions.DIRECT.matches(option)) { + flags.direct = true; + continue; + } if (option == null) throw new NullPointerException(); throw new UnsupportedOperationException(); @@ -161,7 +166,7 @@ throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed"); FileDescriptor fdObj = open(pathForWindows, pathToCheck, flags, pSecurityDescriptor); - return FileChannelImpl.open(fdObj, pathForWindows, flags.read, flags.write, null); + return FileChannelImpl.open(fdObj, pathForWindows, flags.read, flags.write, flags.direct, null); } /** @@ -273,6 +278,8 @@ dwFlagsAndAttributes |= FILE_FLAG_OVERLAPPED; if (flags.deleteOnClose) dwFlagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE; + if (flags.direct) + dwFlagsAndAttributes |= FILE_FLAG_NO_BUFFERING; // NOFOLLOW_LINKS and NOFOLLOW_REPARSEPOINT mean open reparse point boolean okayToFollowLinks = true; --- old/src/java.base/windows/classes/sun/nio/fs/WindowsFileStore.java 2016-11-16 10:28:20.290497920 -0800 +++ new/src/java.base/windows/classes/sun/nio/fs/WindowsFileStore.java 2016-11-16 10:28:20.233497919 -0800 @@ -125,7 +125,7 @@ } // read the free space info - private DiskFreeSpace readDiskFreeSpace() throws IOException { + private DiskFreeSpace readDiskFreeSpaceEx() throws IOException { try { return GetDiskFreeSpaceEx(root); } catch (WindowsException x) { @@ -134,19 +134,33 @@ } } + private DiskFreeSpace readDiskFreeSpace() throws IOException { + try { + return GetDiskFreeSpace(root); + } catch (WindowsException x) { + x.rethrowAsIOException(root); + return null; + } + } + @Override public long getTotalSpace() throws IOException { - return readDiskFreeSpace().totalNumberOfBytes(); + return readDiskFreeSpaceEx().totalNumberOfBytes(); } @Override public long getUsableSpace() throws IOException { - return readDiskFreeSpace().freeBytesAvailable(); + return readDiskFreeSpaceEx().freeBytesAvailable(); + } + + @Override + public int getBlockSize() throws IOException { + return (int)readDiskFreeSpace().bytesPerSector(); } @Override public long getUnallocatedSpace() throws IOException { - return readDiskFreeSpace().freeBytesAvailable(); + return readDiskFreeSpaceEx().freeBytesAvailable(); } @Override @@ -165,6 +179,8 @@ return getUsableSpace(); if (attribute.equals("unallocatedSpace")) return getUnallocatedSpace(); + if (attribute.equals("bytesPerSector")) + return getBlockSize(); // windows specific for testing purposes if (attribute.equals("volume:vsn")) return volInfo.volumeSerialNumber(); --- old/src/java.base/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java 2016-11-16 10:28:20.489497921 -0800 +++ new/src/java.base/windows/classes/sun/nio/fs/WindowsNativeDispatcher.java 2016-11-16 10:28:20.432497921 -0800 @@ -485,21 +485,50 @@ buffer.release(); } } + + /** + * GetDiskFreeSpace( + * LPCTSTR lpRootPathName, + * LPDWORD lpSectorsPerCluster, + * LPDWORD lpBytesPerSector, + * LPDWORD lpNumberOfFreeClusters, + * LPDWORD lpTotalNumberOfClusters + * ) + */ + static DiskFreeSpace GetDiskFreeSpace(String path) + throws WindowsException + { + NativeBuffer buffer = asNativeBuffer(path); + try { + DiskFreeSpace space = new DiskFreeSpace(); + GetDiskFreeSpace0(buffer.address(), space); + return space; + } finally { + buffer.release(); + } + } + static class DiskFreeSpace { private long freeBytesAvailable; private long totalNumberOfBytes; private long totalNumberOfFreeBytes; + private long bytesPerSector; private DiskFreeSpace() { } public long freeBytesAvailable() { return freeBytesAvailable; } public long totalNumberOfBytes() { return totalNumberOfBytes; } public long totalNumberOfFreeBytes() { return totalNumberOfFreeBytes; } + public long bytesPerSector() { return bytesPerSector; } } private static native void GetDiskFreeSpaceEx0(long lpDirectoryName, DiskFreeSpace obj) throws WindowsException; + private static native void GetDiskFreeSpace0(long lpRootPathName, + DiskFreeSpace obj) + throws WindowsException; + /** * GetVolumePathName( * LPCTSTR lpszFileName, --- old/src/java.base/windows/native/libnio/ch/FileChannelImpl.c 2016-11-16 10:28:20.703497923 -0800 +++ new/src/java.base/windows/native/libnio/ch/FileChannelImpl.c 2016-11-16 10:28:20.646497923 -0800 @@ -218,3 +218,9 @@ } return chunkSize; } + +JNIEXPORT jint JNICALL +Java_sun_nio_ch_FileChannelImpl_setDirect0(JNIEnv *env, jobject this, jobject fObj) +{ + return 0; +} --- old/src/java.base/windows/native/libnio/fs/WindowsNativeDispatcher.c 2016-11-16 10:28:20.918497925 -0800 +++ new/src/java.base/windows/native/libnio/fs/WindowsNativeDispatcher.c 2016-11-16 10:28:20.860497925 -0800 @@ -59,6 +59,8 @@ static jfieldID diskSpace_totalBytes; static jfieldID diskSpace_totalFree; +static jfieldID diskSpace_bytesPerSector; + static jfieldID account_domain; static jfieldID account_name; static jfieldID account_use; @@ -121,6 +123,8 @@ CHECK_NULL(diskSpace_totalBytes); diskSpace_totalFree = (*env)->GetFieldID(env, clazz, "totalNumberOfFreeBytes", "J"); CHECK_NULL(diskSpace_totalFree); + diskSpace_bytesPerSector = (*env)->GetFieldID(env, clazz, "bytesPerSector", "J"); + CHECK_NULL(diskSpace_bytesPerSector); clazz = (*env)->FindClass(env, "sun/nio/fs/WindowsNativeDispatcher$Account"); CHECK_NULL(clazz); @@ -582,6 +586,30 @@ long_to_jlong(totalNumberOfFreeBytes.QuadPart)); } +JNIEXPORT void JNICALL +Java_sun_nio_fs_WindowsNativeDispatcher_GetDiskFreeSpace0(JNIEnv* env, jclass this, + jlong address, jobject obj) +{ + DWORD sectorsPerCluster; + DWORD bytesPerSector; + DWORD numberOfFreeClusters; + DWORD totalNumberOfClusters; + LPCWSTR lpRootPathName = jlong_to_ptr(address); + + + BOOL res = GetDiskFreeSpaceW(lpRootPathName, + §orsPerCluster, + &bytesPerSector, + &numberOfFreeClusters, + &totalNumberOfClusters); + if (res == 0) { + throwWindowsException(env, GetLastError()); + return; + } + + (*env)->SetLongField(env, obj, diskSpace_bytesPerSector, + long_to_jlong(bytesPerSector)); +} JNIEXPORT jstring JNICALL Java_sun_nio_fs_WindowsNativeDispatcher_GetVolumePathName0(JNIEnv* env, jclass this, --- old/src/jdk.unsupported/share/classes/com/sun/nio/file/ExtendedOpenOption.java 2016-11-16 10:28:21.141497928 -0800 +++ new/src/jdk.unsupported/share/classes/com/sun/nio/file/ExtendedOpenOption.java 2016-11-16 10:28:21.084497927 -0800 @@ -47,7 +47,12 @@ /** * Prevent operations on the file that request delete access. */ - NOSHARE_DELETE(ExtendedOptions.NOSHARE_DELETE); + NOSHARE_DELETE(ExtendedOptions.NOSHARE_DELETE), + + /** + * DirectIO. + */ + DIRECT(ExtendedOptions.DIRECT); ExtendedOpenOption(ExtendedOptions.InternalOption option) { option.register(this); --- old/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java 2016-11-16 10:28:21.375497930 -0800 +++ new/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java 2016-11-16 10:28:21.318497929 -0800 @@ -89,6 +89,11 @@ } @Override + public int getBlockSize() throws IOException { + throw new UnsupportedOperationException("getBlockSize"); + } + + @Override public long getUsableSpace() throws IOException { return new ZipFileStoreAttributes(this).usableSpace(); } --- /dev/null 2016-11-14 12:26:28.251338568 -0800 +++ new/test/java/nio/channels/FileChannel/PreadDirect.java 2016-11-16 10:28:21.533497931 -0800 @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2000, 2010, 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. + * + * 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. + */ + +/* @test + * @bug 4862382 + * @summary Test positional read method of FileChannel with DirectIO + * @key randomness + */ + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.*; +import java.nio.channels.FileChannel; +import java.util.Random; +import java.nio.file.Paths; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.FileStore; +import com.sun.nio.file.ExtendedOpenOption; + +/** + * Testing FileChannel's positional read method. + */ + +public class PreadDirect { + + private static PrintStream err = System.err; + + private static Random generator = new Random(); + + private static int CHARS_PER_GROUP = 4096; + + public static void main(String[] args) throws Exception { + genericTest1(); + genericTest2(); + testNegativePosition(); // This test for bug 4862382 + } + + // This test for bug 4862382 + private static void testNegativePosition() throws Exception { + File blah = File.createTempFile("blah1", null); + blah.deleteOnExit(); + FileOutputStream fos = new FileOutputStream(blah); + fos.write(new byte[4096]); + fos.close(); + + String path = blah.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel fc = FileChannel.open(p, ExtendedOpenOption.DIRECT); + + try { + fc.read(ByteBuffer.allocate(4096), -1L); + throw new RuntimeException("Expected exception not thrown"); + } catch(IllegalArgumentException e) { + // Correct result + } finally { + fc.close(); + blah.delete(); + } + } + + private static void genericTest2() throws Exception { + File blah = File.createTempFile("blah2", null); + blah.deleteOnExit(); + FileOutputStream fos = new FileOutputStream(blah); + fos.write(new byte[4096]); + fos.close(); + + String path = blah.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel fc = FileChannel.open(p, ExtendedOpenOption.DIRECT); + try { + fc.read(ByteBuffer.allocate(4096), 4000); + throw new RuntimeException("Expected exception not thrown"); + } catch(IllegalArgumentException e) { + if (!e.getMessage().contains("IO size or position is not aligned")) + throw new RuntimeException("Read test failed"); + } finally { + fc.close(); + blah.delete(); + } + } + + private static void genericTest1() throws Exception { + StringBuffer sb = new StringBuffer(); + sb.setLength(2); + + File blah = File.createTempFile("blah3", null); + blah.deleteOnExit(); + initTestFile(blah); + + String path = blah.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel c = FileChannel.open(p, ExtendedOpenOption.DIRECT); + FileStore fs = Files.getFileStore(p); + int alignment = fs.getBlockSize(); + + for (int x=0; x<100; x++) { + long offset = generator.nextInt(100) * 4096; + long expectedResult = offset / CHARS_PER_GROUP; + offset = expectedResult * CHARS_PER_GROUP; + ByteBuffer block = ByteBuffer.allocateDirect(4096 + alignment - 1).alignedSlice(alignment); + + long originalPosition = c.position(); + + int totalRead = 0; + while (totalRead < 4096) { + int read = c.read(block, offset); + if (read < 0) + throw new Exception("Read failed"); + totalRead += read; + } + + long newPosition = c.position(); + + for (int i=0; i<2; i++) { + byte aByte = block.get(i); + sb.setCharAt(i, (char)aByte); + } + int result = Integer.parseInt(sb.toString()); + if (result != expectedResult) { + err.println("I expected "+ expectedResult); + err.println("I got "+ result); + throw new Exception("Read test failed"); + } + + // Ensure that file pointer position has not changed + if (originalPosition != newPosition) + throw new Exception("File position modified"); + } + + c.close(); + blah.delete(); + } + + /** + * Creates file blah: + */ + private static void initTestFile(File blah) throws Exception { + FileOutputStream fos = new FileOutputStream(blah); + BufferedWriter awriter + = new BufferedWriter(new OutputStreamWriter(fos, "8859_1")); + + for(int i=0; i<100; i++) { + String number = new Integer(i).toString(); + for (int h=0; h<2-number.length(); h++) + awriter.write("0"); + awriter.write(""+i); + for (int j=0; j < 4094; j++) + awriter.write("0"); + } + awriter.flush(); + awriter.close(); + } +} --- /dev/null 2016-11-14 12:26:28.251338568 -0800 +++ new/test/java/nio/channels/FileChannel/PwriteDirect.java 2016-11-16 10:28:21.729497933 -0800 @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2000, 2010, 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. + * + * 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. + */ + +/* @test + * @bug 4862411 + * @summary Test positional write method of FileChannel with DirectIO + * @key randomness + */ + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.*; +import java.nio.channels.FileChannel; +import java.util.Random; +import java.nio.file.Paths; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.FileStore; +import java.nio.file.StandardOpenOption; +import com.sun.nio.file.ExtendedOpenOption; + +/** + * Testing FileChannel's positional write method. + */ +public class PwriteDirect { + + private static Random generator = new Random(); + + private static File blah; + + public static void main(String[] args) throws Exception { + genericTest(); + testUnwritableChannel(); + } + + // This test for bug 4862411 + private static void testUnwritableChannel() throws Exception { + File blah = File.createTempFile("blah2", null); + blah.deleteOnExit(); + FileOutputStream fos = new FileOutputStream(blah); + fos.write(new byte[4096]); + fos.close(); + + String path = blah.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel fc = FileChannel.open(p, ExtendedOpenOption.DIRECT); + + try { + fc.write(ByteBuffer.allocate(4096),0); + throw new RuntimeException("Expected exception not thrown"); + } catch(NonWritableChannelException e) { + // Correct result + } finally { + fc.close(); + blah.delete(); + } + } + + private static void genericTest() throws Exception { + blah = File.createTempFile("blah", null); + blah.deleteOnExit(); + initTestFile(blah); + + String path = blah.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel c = FileChannel.open(p, StandardOpenOption.WRITE, StandardOpenOption.READ, ExtendedOpenOption.DIRECT); + FileStore fs = Files.getFileStore(p); + int alignment = fs.getBlockSize(); + + for (int x=0; x<100; x++) { + long offset = generator.nextInt(100) * 4096; + ByteBuffer block = ByteBuffer.allocateDirect(4096 + alignment - 1).alignedSlice(alignment); + + // Write known sequence out + for (byte i=0; i<4; i++) { + block.put(i); + } + block.position(0); + long originalPosition = c.position(); + int totalWritten = 0; + while (totalWritten < 4096) { + int written = c.write(block, offset); + if (written < 0) + throw new Exception("Write failed"); + totalWritten += written; + } + + long newPosition = c.position(); + + // Ensure that file pointer position has not changed + if (originalPosition != newPosition) + throw new Exception("File position modified"); + + // Attempt to read sequence back in + block = ByteBuffer.allocateDirect(4096 + alignment - 1).alignedSlice(alignment); + originalPosition = c.position(); + int totalRead = 0; + while (totalRead < 4096) { + int read = c.read(block, offset); + if (read < 0) + throw new Exception("Read failed"); + totalRead += read; + } + newPosition = c.position(); + + // Ensure that file pointer position has not changed + if (originalPosition != newPosition) + throw new Exception("File position modified"); + + for (byte i=0; i<4; i++) { + if (block.get(i) != i) + throw new Exception("Write test failed"); + } + } + c.close(); + blah.delete(); + } + + /** + * Creates file blah: + */ + private static void initTestFile(File blah) throws Exception { + FileOutputStream fos = new FileOutputStream(blah); + BufferedWriter awriter + = new BufferedWriter(new OutputStreamWriter(fos, "8859_1")); + + for(int i=0; i<100; i++) { + String number = new Integer(i).toString(); + for (int h=0; h<4-number.length(); h++) + awriter.write("0"); + awriter.write(""+i); + for (int j=0; j < 4092; j++) + awriter.write("0"); + } + awriter.flush(); + awriter.close(); + } +} --- /dev/null 2016-11-14 12:26:28.251338568 -0800 +++ new/test/java/nio/channels/FileChannel/ReadDirect.java 2016-11-16 10:28:21.926497935 -0800 @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2000, 2010, 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. + * + * 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. + */ + +/* @test + * @summary Test read method of FileChannel with DirectIO + */ + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.*; +import java.nio.channels.FileChannel; +import java.util.Random; +import java.nio.file.Paths; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.FileStore; +import com.sun.nio.file.ExtendedOpenOption; + +/** + * Testing FileChannel's mapping capabilities. + */ + +public class ReadDirect { + + private static PrintStream err = System.err; + + private static Random generator = new Random(); + + private static File blah; + + private static void testRead1() throws Exception { + StringBuffer sb = new StringBuffer(); + sb.setLength(2); + + int charsPerGroup = 4096; + blah = File.createTempFile("blah1", null); + blah.deleteOnExit(); + initTestFile(blah, charsPerGroup); + String path = blah.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel c = FileChannel.open(p, ExtendedOpenOption.DIRECT); + FileStore fs = Files.getFileStore(p); + int alignment = fs.getBlockSize(); + + for (int x=0; x<100; x++) { + long offset = x * charsPerGroup; + long expectedResult = offset / charsPerGroup; + ByteBuffer block = ByteBuffer.allocateDirect(charsPerGroup + alignment - 1).alignedSlice(alignment); + c.read(block); + + for (int i=0; i<2; i++) { + byte aByte = block.get(i); + sb.setCharAt(i, (char)aByte); + } + int result = Integer.parseInt(sb.toString()); + if (result != expectedResult) { + err.println("I expected "+expectedResult); + err.println("I got "+ result); + throw new Exception("Read test failed"); + } + } + c.close(); + blah.delete(); + } + + private static void testRead2() throws Exception { + int charsPerGroup = 4000; + blah = File.createTempFile("blah2", null); + blah.deleteOnExit(); + + FileOutputStream fos = new FileOutputStream(blah); + fos.write(new byte[charsPerGroup]); + fos.close(); + + String path = blah.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel c = FileChannel.open(p, ExtendedOpenOption.DIRECT); + FileStore fs = Files.getFileStore(p); + int alignment = fs.getBlockSize(); + + ByteBuffer block = ByteBuffer.allocate(charsPerGroup); + try { + c.read(block); + throw new RuntimeException("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + if (!e.getMessage().contains("IO size or position is not aligned")) + throw new Exception("Read test failed"); + } finally { + c.close(); + blah.delete(); + } + } + + + public static void main(String[] args) throws Exception { + testRead1(); + testRead2(); + } + + /** + * Creates file blah: + */ + private static void initTestFile(File blah, int charsPerGroup) throws Exception { + FileOutputStream fos = new FileOutputStream(blah); + BufferedWriter awriter + = new BufferedWriter(new OutputStreamWriter(fos, "8859_1")); + + for(int i=0; i<100; i++) { + String number = new Integer(i).toString(); + for (int h=0; h<2-number.length(); h++) + awriter.write("0"); + awriter.write(""+i); + for (int j=0; j < (charsPerGroup - 2); j++) + awriter.write("0"); + } + awriter.flush(); + awriter.close(); + } +} --- /dev/null 2016-11-14 12:26:28.251338568 -0800 +++ new/test/java/nio/channels/FileChannel/WriteDirect.java 2016-11-16 10:28:22.127497937 -0800 @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2001, 2010, 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. + * + * 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. + */ + +/* + * @test + * @summary Test FileChannel write with DirectIO + * @run main/othervm Write + */ + +import java.nio.channels.*; +import java.nio.*; +import java.io.*; +import java.nio.file.Paths; +import java.nio.file.Path; +import java.nio.file.Files; +import java.nio.file.FileStore; +import java.nio.file.StandardOpenOption; +import com.sun.nio.file.ExtendedOpenOption; + +public class WriteDirect { + + public static void main(String[] args) throws Exception { + test1(); + test2(); + } + + static void test1() throws Exception { + File testFile = File.createTempFile("test1", null); + testFile.deleteOnExit(); + + String path = testFile.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel fc = FileChannel.open(p, StandardOpenOption.WRITE, ExtendedOpenOption.DIRECT); + FileStore fs = Files.getFileStore(p); + int alignment = fs.getBlockSize(); + ByteBuffer src = ByteBuffer.allocate(4000); + try { + fc.write(src); + throw new RuntimeException("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + if (!e.getMessage().contains("IO size or position is not aligned")) + throw new Exception("Write failure"); + } finally { + fc.close(); + } + + testFile.delete(); + } + + // Test to see that the appropriate buffers are updated + static void test2() throws Exception { + File testFile = File.createTempFile("test2", null); + testFile.deleteOnExit(); + ByteBuffer[] srcs = new ByteBuffer[4]; + String path = testFile.getAbsolutePath(); + Path p = Paths.get(path); + FileChannel fc = FileChannel.open(p, StandardOpenOption.WRITE, ExtendedOpenOption.DIRECT); + FileStore fs = Files.getFileStore(p); + int alignment = fs.getBlockSize(); + + for (int i=0; i<4; i++) { + srcs[i] = ByteBuffer.allocateDirect(4096 + alignment -1).alignedSlice(alignment); + for (int j=0; j<4096; j++) { + srcs[i].put((byte)i); + } + srcs[i].flip(); + } + + try { + fc.write(srcs, 1, 2); + } finally { + fc.close(); + } + + FileInputStream fis = new FileInputStream(testFile); + fc = fis.getChannel(); + try { + ByteBuffer bb = ByteBuffer.allocateDirect(8192); + fc.read(bb); + bb.flip(); + for (int k=0; k<4096; k++) { + if (bb.get() != 1) + throw new RuntimeException("Write failure"); + } + for (int m=0; m<4096; m++) { + if (bb.get() != 2) + throw new RuntimeException("Write failure"); + } + try { + bb.get(); + throw new RuntimeException("Write failure"); + } catch (BufferUnderflowException bufe) { + // correct result + } + } finally { + fc.close(); + } + + // eagerly clean-up + testFile.delete(); + } +}