# HG changeset patch # User jjg # Date 1455055643 28800 # Node ID 2eb1d877da0f28e232c1487ff5120b250c6a3f41 # Parent db483b34fa7148d257a429acddbde9c13687dcae 8147801: java.nio.file.ClosedFileSystemException when using Javadoc API's in JDK9 Reviewed-by: jlahoda diff -r db483b34fa71 -r 2eb1d877da0f langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java Wed Jul 05 21:19:32 2017 +0200 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java Tue Feb 09 14:07:23 2016 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2016, 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 @@ -55,8 +55,6 @@ import com.sun.tools.javac.code.Lint; import com.sun.tools.javac.code.Source; -import com.sun.tools.javac.file.FSInfo; -import com.sun.tools.javac.file.Locations; import com.sun.tools.javac.main.Option; import com.sun.tools.javac.main.OptionHelper; import com.sun.tools.javac.main.OptionHelper.GrumpyHelper; @@ -88,6 +86,27 @@ options = Options.instance(context); classLoaderClass = options.get("procloader"); locations.update(log, Lint.instance(context), FSInfo.instance(context)); + + // Setting this option is an indication that close() should defer actually closing + // the file manager until after a specified period of inactivity. + // This is to accomodate clients which save references to Symbols created for use + // within doclets or annotation processors, and which then attempt to use those + // references after the tool exits, having closed any internally managed file manager. + // Ideally, such clients should run the tool via the javax.tools API, providing their + // own file manager, which can be closed by the client when all use of that file + // manager is complete. + // If the option has a numeric value, it will be interpreted as the duration, + // in seconds, of the period of inactivity to wait for, before the file manager + // is actually closed. + // See also deferredClose(). + String s = options.get("fileManager.deferClose"); + if (s != null) { + try { + deferredCloseTimeout = (int) (Float.parseFloat(s) * 1000); + } catch (NumberFormatException e) { + deferredCloseTimeout = 60 * 1000; // default: one minute, in millis + } + } } protected Locations createLocations() { @@ -116,6 +135,42 @@ */ public boolean autoClose; + /** + * Wait for a period of inactivity before calling close(). + * The length of the period of inactivity is given by {@code deferredCloseTimeout} + */ + protected void deferredClose() { + Thread t = new Thread(getClass().getName() + " DeferredClose") { + @Override + public void run() { + try { + synchronized (BaseFileManager.this) { + long now = System.currentTimeMillis(); + while (now < lastUsedTime + deferredCloseTimeout) { + BaseFileManager.this.wait(lastUsedTime + deferredCloseTimeout - now); + now = System.currentTimeMillis(); + } + deferredCloseTimeout = 0; + close(); + } + } catch (InterruptedException e) { + } catch (IOException e) { + } + } + }; + t.setDaemon(true); + t.start(); + } + + synchronized void updateLastUsedTime() { + if (deferredCloseTimeout > 0) { // avoid updating the time unnecessarily + lastUsedTime = System.currentTimeMillis(); + } + } + + private long lastUsedTime = System.currentTimeMillis(); + protected long deferredCloseTimeout = 0; + protected Source getSource() { String sourceName = options.get(Option.SOURCE); Source source = null; diff -r db483b34fa71 -r 2eb1d877da0f langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java Wed Jul 05 21:19:32 2017 +0200 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java Tue Feb 09 14:07:23 2016 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2016, 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 @@ -525,8 +525,16 @@ */ @Override @DefinedBy(Api.COMPILER) public void close() throws IOException { - for (FileSystem fs: fileSystems.values()) + if (deferredCloseTimeout > 0) { + deferredClose(); + return; + } + + for (FileSystem fs: fileSystems.values()) { fs.close(); + } + fileSystems.clear(); + contentCache.clear(); } @Override @DefinedBy(Api.COMPILER) diff -r db483b34fa71 -r 2eb1d877da0f langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/PathFileObject.java --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/PathFileObject.java Wed Jul 05 21:19:32 2017 +0200 +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/PathFileObject.java Tue Feb 09 14:07:23 2016 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2016, 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 @@ -432,11 +432,13 @@ @Override @DefinedBy(Api.COMPILER) public InputStream openInputStream() throws IOException { + fileManager.updateLastUsedTime(); return Files.newInputStream(path); } @Override @DefinedBy(Api.COMPILER) public OutputStream openOutputStream() throws IOException { + fileManager.updateLastUsedTime(); fileManager.flushCache(this); ensureParentDirectoriesExist(); return Files.newOutputStream(path); @@ -471,6 +473,7 @@ @Override @DefinedBy(Api.COMPILER) public Writer openWriter() throws IOException { + fileManager.updateLastUsedTime(); fileManager.flushCache(this); ensureParentDirectoriesExist(); return new OutputStreamWriter(Files.newOutputStream(path), fileManager.getEncodingName()); diff -r db483b34fa71 -r 2eb1d877da0f langtools/test/tools/javac/diags/CheckResourceKeys.java --- a/langtools/test/tools/javac/diags/CheckResourceKeys.java Wed Jul 05 21:19:32 2017 +0200 +++ b/langtools/test/tools/javac/diags/CheckResourceKeys.java Tue Feb 09 14:07:23 2016 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2016, 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 @@ -289,6 +289,7 @@ // -XD option names "process.packages", "ignore.symbol.file", + "fileManager.deferClose", // prefix/embedded strings "compiler.", "compiler.misc.", diff -r db483b34fa71 -r 2eb1d877da0f langtools/test/tools/javadoc/8147801/T8147801.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javadoc/8147801/T8147801.java Tue Feb 09 14:07:23 2016 -0800 @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016, 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 8147801 + * @summary java.nio.file.ClosedFileSystemException when using Javadoc API's in JDK9 + * @modules jdk.javadoc/com.sun.tools.javadoc + * @library jarsrc + * @build lib.* p.* + * @run main T8147801 + */ + +import java.io.IOException; +import java.nio.file.ClosedFileSystemException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.FieldDoc; +import com.sun.javadoc.RootDoc; + +/* + * This test verifies the use of the hidden fileManager.deferClose + * option, to work around the limitation that javadoc objects + * (like RootDoc and related types) should cannot normally be used + * after javadoc exits, closing its file manager (if it opened it.) + * + * The test runs javadoc on a chain of classes, 1 in source form, + * and 2 in a jar file. javadoc/javac will "complete" classes found + * in source, but will eagerly "classes" in class form. + * The chain is p/Test.java -> lib/Lib1.class -> lib/Lib2.class. + * After javadoc exits, the classes are examined, to finally force + * the classes to be completed, possibly causing javac to try and access + * references into a .jar file system which has now been closed. + * + * The test runs two test cases -- one without the workaround option, + * to test the validity of the test case, and one with the workaround + * option, to test that it works as expected. + */ +public class T8147801 { + public static void main(String... args) throws Exception { + new T8147801().run(); + } + + void run() throws Exception { + initJar(); + test(false); + test(true); + if (errors > 0) { + throw new Exception(errors + " errors occurred"); + } + } + + void test(boolean withOption) { + System.err.println("Testing " + (withOption ? "with" : "without") + " option"); + try { + RootDoc root = getRootDoc(withOption); + for (ClassDoc cd: root.specifiedClasses()) { + dump("", cd); + } + if (!withOption) { + error("expected option did not occur"); + } + } catch (ClosedFileSystemException e) { + if (withOption) { + error("Unexpected exception: " + e); + } else { + System.err.println("Exception received as expected: " + e); + } + } + System.err.println(); + } + + RootDoc getRootDoc(boolean withOption) { + List opts = new ArrayList<>(); + if (withOption) + opts.add("-XDfileManager.deferClose=10"); + opts.add("-doclet"); + opts.add(getClass().getName()); + opts.add("-classpath"); + opts.add(jarPath.toString()); + opts.add(Paths.get(System.getProperty("test.src"), "p", "Test.java").toString()); + System.err.println("javadoc opts: " + opts); + int rc = com.sun.tools.javadoc.Main.execute( + "javadoc", + // by specifying our own class loader, we get the same Class instance as this + getClass().getClassLoader(), + opts.toArray(new String[opts.size()])); + if (rc != 0) { + error("unexpected exit from javadoc or doclet: " + rc); + } + return cachedRoot; + } + + void dump(String prefix, ClassDoc cd) { + System.err.println(prefix + "class: " + cd); + for (FieldDoc fd: cd.fields()) { + System.err.println(fd); + if (fd.type().asClassDoc() != null) { + dump(prefix + " ", fd.type().asClassDoc()); + } + } + } + + void initJar() throws IOException { + Path testClasses = Paths.get(System.getProperty("test.classes")); + jarPath = Paths.get("lib.jar"); + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(jarPath))) { + String[] classNames = {"Lib1.class", "Lib2.class"}; + for (String cn : classNames) { + out.putNextEntry(new JarEntry("lib/" + cn)); + Path libClass = testClasses.resolve("jarsrc").resolve("lib").resolve(cn); + out.write(Files.readAllBytes(libClass)); + } + } + } + + void error(String msg) { + System.err.println("Error: " + msg); + errors++; + } + + Path jarPath; + int errors; + + // Bad doclet caches the RootDoc for later use + + static RootDoc cachedRoot; + + public static boolean start(RootDoc root) { + cachedRoot = root; + return true; + } +} diff -r db483b34fa71 -r 2eb1d877da0f langtools/test/tools/javadoc/8147801/jarsrc/lib/Lib1.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javadoc/8147801/jarsrc/lib/Lib1.java Tue Feb 09 14:07:23 2016 -0800 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, 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. + */ + +package lib; + +public class Lib1 { + public Lib2 lib2; +} diff -r db483b34fa71 -r 2eb1d877da0f langtools/test/tools/javadoc/8147801/jarsrc/lib/Lib2.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javadoc/8147801/jarsrc/lib/Lib2.java Tue Feb 09 14:07:23 2016 -0800 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, 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. + */ + +package lib; + +public class Lib2 { + int i; +} diff -r db483b34fa71 -r 2eb1d877da0f langtools/test/tools/javadoc/8147801/p/Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javadoc/8147801/p/Test.java Tue Feb 09 14:07:23 2016 -0800 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 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. + */ + +package p; + +public class Test { + public lib.Lib1 lib1; +} +