--- old/src/java.base/share/classes/sun/nio/ch/FileLockTable.java 2018-01-30 18:58:27.000000000 -0800 +++ new/src/java.base/share/classes/sun/nio/ch/FileLockTable.java 2018-01-30 18:58:26.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 @@ -86,18 +86,39 @@ * FileLock (and FileChannel) alive. */ private static class FileLockReference extends WeakReference { - private FileKey fileKey; + private final FileKey fileKey; + final long position; + final long size; + private volatile boolean invalid; FileLockReference(FileLock referent, ReferenceQueue queue, FileKey key) { super(referent, queue); this.fileKey = key; + this.position = referent.position(); + this.size = referent.size(); } FileKey fileKey() { return fileKey; } + + void invalidate() { + invalid = true; + } + + boolean isValid() { + return !invalid; + } + + 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. @@ -186,6 +207,7 @@ if (lock == fl) { assert (lock != null) && (lock.acquiredBy() == channel); ref.clear(); + ref.invalidate(); list.remove(index); break; } @@ -209,6 +231,7 @@ if (lock != null && lock.acquiredBy() == channel) { // remove the lock from the list ref.clear(); + ref.invalidate(); list.remove(index); // add to result @@ -251,7 +274,7 @@ assert Thread.holdsLock(list); for (FileLockReference ref: list) { FileLock fl = ref.get(); - if (fl != null && fl.overlaps(position, size)) + if ((fl != null || ref.isValid()) && ref.overlaps(position, size)) throw new OverlappingFileLockException(); } } @@ -264,7 +287,9 @@ List list = lockMap.get(fk); if (list != null) { synchronized (list) { - list.remove(ref); + if (!ref.isValid()) { + list.remove(ref); + } removeKeyIfEmpty(fk, list); } } --- /dev/null 2018-01-30 18:58:27.000000000 -0800 +++ new/test/jdk/java/nio/channels/FileLock/FileLockGC.java 2018-01-30 18:58:27.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; + } +}