--- old/src/java.base/share/classes/sun/nio/ch/FileLockImpl.java 2018-01-29 15:25:30.000000000 -0800 +++ new/src/java.base/share/classes/sun/nio/ch/FileLockImpl.java 2018-01-29 15:25:29.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2018, 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 @@ -26,12 +26,16 @@ package sun.nio.ch; import java.io.IOException; -import java.nio.channels.*; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.Channel; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; -public class FileLockImpl - extends FileLock +class FileLockImpl extends FileLock { private volatile boolean invalid; + private volatile FileLockListener listener; FileLockImpl(FileChannel channel, long position, long size, boolean shared) { @@ -43,6 +47,10 @@ super(channel, position, size, shared); } + void setFileLockListener(FileLockListener listener) { + this.listener = listener; + } + public boolean isValid() { return !invalid; } @@ -50,6 +58,10 @@ void invalidate() { assert Thread.holdsLock(this); invalid = true; + FileLockListener fll; + if ((fll = listener) != null) { + fll.invalidate(); + } } public synchronized void release() throws IOException { --- old/src/java.base/share/classes/sun/nio/ch/FileLockTable.java 2018-01-29 15:25:30.000000000 -0800 +++ new/src/java.base/share/classes/sun/nio/ch/FileLockTable.java 2018-01-29 15:25:30.000000000 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2018, 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 @@ -25,12 +25,16 @@ package sun.nio.ch; -import java.nio.channels.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.lang.ref.*; import java.io.FileDescriptor; import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.nio.channels.Channel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; abstract class FileLockTable { protected FileLockTable() { @@ -85,19 +89,50 @@ * SharedFileLockTable uses a list of file lock references to avoid keeping the * FileLock (and FileChannel) alive. */ - private static class FileLockReference extends WeakReference { + private static class FileLockReference extends WeakReference + implements FileLockListener { private FileKey fileKey; + // Mirror of FileLock state for use when the lock object is no longer + // weakly reachable but the native lock has not yet been released. + private final long position; + private final long size; + private boolean isLockReleased; + FileLockReference(FileLock referent, ReferenceQueue queue, FileKey key) { super(referent, queue); this.fileKey = key; + this.position = referent.position(); + this.size = referent.size(); + this.isLockReleased = !referent.isValid(); + if (referent instanceof FileLockImpl) { + ((FileLockImpl)referent).setFileLockListener(this); + } } FileKey fileKey() { return fileKey; } + + // FileLockListener implementation + public void invalidate() { + this.isLockReleased = true; + } + + boolean isLockReleased() { + return this.isLockReleased; + } + + // Copy of FileLock.overlaps(long,long) + boolean overlaps(long position, long size) { + if (position + size <= this.position) + return false; // That is below this + if (this.position + this.size <= position) + return false; // This is below that + return true; + } } // The system-wide map is a ConcurrentHashMap that is keyed on the FileKey. @@ -213,6 +248,9 @@ // add to result result.add(lock); + } else if (lock == null && !ref.isLockReleased()) { + ref.clear(); + list.remove(index); } else { index++; } @@ -251,8 +289,12 @@ assert Thread.holdsLock(list); for (FileLockReference ref: list) { FileLock fl = ref.get(); - if (fl != null && fl.overlaps(position, size)) + // Check for overlap if the FileLock instance has not been collected + // or the underlying lock has not been released to the file system. + if ((fl != null || !ref.isLockReleased()) + && ref.overlaps(position, size)) { throw new OverlappingFileLockException(); + } } } @@ -264,7 +306,11 @@ List list = lockMap.get(fk); if (list != null) { synchronized (list) { - list.remove(ref); + // Retain the reference in the list if it refers to a + // FileLock which was collected without being released. + if (ref.isLockReleased()) { + list.remove(ref); + } removeKeyIfEmpty(fk, list); } } --- /dev/null 2018-01-29 15:25:31.000000000 -0800 +++ new/src/java.base/share/classes/sun/nio/ch/FileLockListener.java 2018-01-29 15:25:30.000000000 -0800 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018, 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; + +interface FileLockListener +{ + public void invalidate(); +} --- /dev/null 2018-01-29 15:25:31.000000000 -0800 +++ new/test/jdk/java/nio/channels/FileLock/FileLockGC.java 2018-01-29 15:25:31.000000000 -0800 @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018, 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. + */ + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.Files; +import java.nio.file.Path; +import jdk.test.lib.util.FileUtils; + +/* + * @test + * @bug 8166253 + * @summary Verify that OverlappingFileLockException is thrown when expected. + * @library .. /test/lib + * @build jdk.test.lib.util.FileUtils + * @run main/othervm FileLockGC + */ +public class FileLockGC { + public enum TestType { + NO_GC_NO_RELEASE(true), + // A hypothetical 'GC_THEN_RELEASE' case is infeasible + RELEASE(false), + RELEASE_THEN_GC(false), + GC(true); + + private final boolean exceptionExpected; + + TestType(boolean exceptionExpected) { + this.exceptionExpected = exceptionExpected; + } + + boolean exceptionExpected() { + return exceptionExpected; + } + } + + public static void main(String[] args) throws Exception { + final File f = new File(System.getProperty("test.dir", ".") + + File.separator + "junk.txt"); + final Path p = f.toPath(); + int failures = 0; + + for (TestType t : TestType.values()) { + try { + if (!testFileLockGC(f, t)) { + failures++; + } + } finally { + FileUtils.deleteFileIfExistsWithRetry(p); + } + } + + if (failures != 0) { + throw new RuntimeException("Test had " + failures + " failure(s)"); + } + } + + private static boolean testFileLockGC(File f, TestType type) + throws InterruptedException, IOException { + System.out.printf("Test %s starting%n", type.toString()); + + final RandomAccessFile raf1 = new RandomAccessFile(f, "rw"); + + FileLock lock1 = raf1.getChannel().tryLock(); + WeakReference ref1 = new WeakReference(lock1); + + switch (type) { + case GC: + lock1 = null; + do { + System.gc(); + Thread.sleep(10); + } while (ref1.get() != null); + break; + case RELEASE: + lock1.release(); + break; + case RELEASE_THEN_GC: + lock1.release(); + lock1 = null; + do { + System.gc(); + Thread.sleep(10); + } while (ref1.get() != null); + break; + default: // NO_GC_NO_RELEASE + // lock1 is neither collected nor released + break; + } + + final RandomAccessFile raf2 = new RandomAccessFile(f, "rw"); + + boolean success = true; + FileLock lock2 = null; + try { + lock2 = raf2.getChannel().tryLock(); + if (type.exceptionExpected()) { + System.err.printf + ("No expected OverlappingFileLockException for test %s%n", + type.toString()); + success = false; + } + } catch (OverlappingFileLockException ofe) { + if (!type.exceptionExpected()) { + System.err.printf + ("Unexpected OverlappingFileLockException for test %s%n", + type.toString()); + success = false; + } + } finally { + if (lock1 != null) { + lock1.release(); + } + if (lock2 != null) { + lock2.release(); + } + raf2.close(); + raf1.close(); + System.out.printf("Test %s finished%n", type.toString()); + } + + return success; + } +}