From baa27a205dddac65a48f066cea74472a0a4400ed Mon Sep 17 00:00:00 2001 From: michaelm Date: Fri, 30 Jan 2009 22:05:30 +0000 Subject: [PATCH] 4167874: URL-downloaded jar files can consume all available file descriptors Summary: added close method to URLClassLoader Reviewed-by: alanb --- .../classes/java/lang/RuntimePermission.java | 7 + .../classes/java/net/URLClassLoader.java | 69 ++++- src/share/classes/sun/misc/URLClassPath.java | 61 +++-- .../URLClassLoader/closetest/CloseTest.java | 246 ++++++++++++++++++ test/java/net/URLClassLoader/closetest/README | 24 ++ .../net/URLClassLoader/closetest/build.sh | 73 ++++++ .../closetest/serverRoot/Test.java | 28 ++ .../closetest/test1/com/foo/Resource1 | 1 + .../closetest/test1/com/foo/Resource2 | 1 + .../closetest/test1/com/foo/TestClass.java | 38 +++ .../closetest/test1/com/foo/TestClass1.java | 26 ++ .../closetest/test2/com/foo/Resource1 | 1 + .../closetest/test2/com/foo/Resource2 | 1 + .../closetest/test2/com/foo/TestClass.java | 38 +++ .../closetest/test2/com/foo/TestClass1.java | 26 ++ 15 files changed, 616 insertions(+), 24 deletions(-) create mode 100644 test/java/net/URLClassLoader/closetest/CloseTest.java create mode 100644 test/java/net/URLClassLoader/closetest/README create mode 100644 test/java/net/URLClassLoader/closetest/build.sh create mode 100644 test/java/net/URLClassLoader/closetest/serverRoot/Test.java create mode 100644 test/java/net/URLClassLoader/closetest/test1/com/foo/Resource1 create mode 100644 test/java/net/URLClassLoader/closetest/test1/com/foo/Resource2 create mode 100644 test/java/net/URLClassLoader/closetest/test1/com/foo/TestClass.java create mode 100644 test/java/net/URLClassLoader/closetest/test1/com/foo/TestClass1.java create mode 100644 test/java/net/URLClassLoader/closetest/test2/com/foo/Resource1 create mode 100644 test/java/net/URLClassLoader/closetest/test2/com/foo/Resource2 create mode 100644 test/java/net/URLClassLoader/closetest/test2/com/foo/TestClass.java create mode 100644 test/java/net/URLClassLoader/closetest/test2/com/foo/TestClass1.java diff --git a/src/share/classes/java/lang/RuntimePermission.java b/src/share/classes/java/lang/RuntimePermission.java index fce9e08f1..717444329 100644 --- a/src/share/classes/java/lang/RuntimePermission.java +++ b/src/share/classes/java/lang/RuntimePermission.java @@ -100,6 +100,13 @@ import java.util.StringTokenizer; * * * + * closeClassLoader + * Closing of a ClassLoader + * Granting this permission allows code to close any URLClassLoader + * that it has a reference to. + * + * + * * setSecurityManager * Setting of the security manager (possibly replacing an existing one) * diff --git a/src/share/classes/java/net/URLClassLoader.java b/src/share/classes/java/net/URLClassLoader.java index 9ffd0287c..afb92b8c9 100644 --- a/src/share/classes/java/net/URLClassLoader.java +++ b/src/share/classes/java/net/URLClassLoader.java @@ -31,10 +31,12 @@ import java.io.File; import java.io.FilePermission; import java.io.InputStream; import java.io.IOException; +import java.io.Closeable; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandlerFactory; import java.util.Enumeration; +import java.util.List; import java.util.NoSuchElementException; import java.util.StringTokenizer; import java.util.jar.Manifest; @@ -70,7 +72,7 @@ import sun.security.util.SecurityConstants; * @author David Connelly * @since 1.2 */ -public class URLClassLoader extends SecureClassLoader { +public class URLClassLoader extends SecureClassLoader implements Closeable { /* The search path for classes and resources */ URLClassPath ucp; @@ -85,13 +87,13 @@ public class URLClassLoader extends SecureClassLoader { * to refer to a JAR file which will be downloaded and opened as needed. * *

If there is a security manager, this method first - * calls the security manager's checkCreateClassLoader method + * calls the security manager's {@code checkCreateClassLoader} method * to ensure creation of a class loader is allowed. * * @param urls the URLs from which to load classes and resources * @param parent the parent class loader for delegation * @exception SecurityException if a security manager exists and its - * checkCreateClassLoader method doesn't allow + * {@code checkCreateClassLoader} method doesn't allow * creation of a class loader. * @see SecurityManager#checkCreateClassLoader */ @@ -169,12 +171,65 @@ public class URLClassLoader extends SecureClassLoader { acc = AccessController.getContext(); } + + /** + * Closes this URLClassLoader, so that it can no longer be used to load + * new classes or resources that are defined by this loader. + * Classes and resources defined by any of this loader's parents in the + * delegation hierarchy are still accessible. Also, any classes or resources + * that are already loaded, are still accessible. + *

+ * In the case of jar: and file: URLs, it also closes any class files, + * or JAR files that were opened by it. If another thread is loading a + * class when the {@code close} method is invoked, then the result of + * that load is undefined. + *

+ * The method makes a best effort attempt to close all opened files, + * by catching {@link IOException}s internally. Unchecked exceptions + * and errors are not caught. Calling close on an already closed + * loader has no effect. + *

+ * @throws IOException if closing any file opened by this class loader + * resulted in an IOException. Any such exceptions are caught, and a + * single IOException is thrown after the last file has been closed. + * If only one exception was thrown, it will be set as the cause + * of this IOException. + * + * @throws SecurityException if a security manager is set, and it denies + * {@link RuntimePermission}("closeClassLoader") + * + * @since 1.7 + */ + public void close() throws IOException { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkPermission(new RuntimePermission("closeClassLoader")); + } + List errors = ucp.closeLoaders(); + if (errors.isEmpty()) { + return; + } + if (errors.size() == 1) { + throw new IOException ( + "Error closing URLClassLoader resource", + errors.get(0) + ); + } + // Several exceptions. So, just combine the error messages + String errormsg = "Error closing resources: "; + for (IOException error: errors) { + errormsg = errormsg + "[" + error.toString() + "] "; + } + throw new IOException (errormsg); + } + /** * Appends the specified URL to the list of URLs to search for * classes and resources. *

* If the URL specified is null or is already in the - * list of URLs, then invoking this method has no effect. + * list of URLs, or if this loader is closed, then invoking this + * method has no effect. * * @param url the URL to be added to the search path of URLs */ @@ -199,7 +254,8 @@ public class URLClassLoader extends SecureClassLoader { * * @param name the name of the class * @return the resulting class - * @exception ClassNotFoundException if the class could not be found + * @exception ClassNotFoundException if the class could not be found, + * or if the loader is closed. */ protected Class findClass(final String name) throws ClassNotFoundException @@ -370,7 +426,7 @@ public class URLClassLoader extends SecureClassLoader { * * @param name the name of the resource * @return a URL for the resource, or null - * if the resource could not be found. + * if the resource could not be found, or if the loader is closed. */ public URL findResource(final String name) { /* @@ -393,6 +449,7 @@ public class URLClassLoader extends SecureClassLoader { * @param name the resource name * @exception IOException if an I/O exception occurs * @return an Enumeration of URLs + * If the loader is closed, the Enumeration will be empty. */ public Enumeration findResources(final String name) throws IOException diff --git a/src/share/classes/sun/misc/URLClassPath.java b/src/share/classes/sun/misc/URLClassPath.java index 5e38c7671..28b4441d3 100644 --- a/src/share/classes/sun/misc/URLClassPath.java +++ b/src/share/classes/sun/misc/URLClassPath.java @@ -25,17 +25,7 @@ package sun.misc; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Hashtable; -import java.util.NoSuchElementException; -import java.util.Stack; -import java.util.Set; -import java.util.HashSet; -import java.util.StringTokenizer; -import java.util.ArrayList; -import java.util.Iterator; +import java.util.*; import java.util.jar.JarFile; import sun.misc.JarIndex; import sun.misc.InvalidJarIndexException; @@ -52,12 +42,7 @@ import java.net.URLConnection; import java.net.HttpURLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import java.io.*; import java.security.AccessController; import java.security.AccessControlException; import java.security.CodeSigner; @@ -100,6 +85,9 @@ public class URLClassPath { /* The jar protocol handler to use when creating new URLs */ private URLStreamHandler jarHandler; + /* Whether this URLClassLoader has been closed yet */ + private boolean closed = false; + /** * Creates a new URLClassPath for the given URLs. The URLs will be * searched in the order specified for classes and resources. A URL @@ -124,6 +112,22 @@ public class URLClassPath { this(urls, null); } + public synchronized List closeLoaders() { + if (closed) { + return Collections.emptyList(); + } + List result = new LinkedList(); + for (Loader loader : loaders) { + try { + loader.close(); + } catch (IOException e) { + result.add (e); + } + } + closed = true; + return result; + } + /** * Appends the specified URL to the search path of directory and JAR * file URLs from which to load classes and resources. @@ -293,6 +297,9 @@ public class URLClassPath { * if the specified index is out of range. */ private synchronized Loader getLoader(int index) { + if (closed) { + return null; + } // Expand URL search path until the request can be satisfied // or the URL stack is empty. while (loaders.size() < index + 1) { @@ -453,7 +460,7 @@ public class URLClassPath { * Inner class used to represent a loader of resources and classes * from a base URL. */ - private static class Loader { + private static class Loader implements Closeable { private final URL base; /* @@ -544,6 +551,12 @@ public class URLClassPath { return getResource(name, true); } + /* + * close this loader and release all resources + * method overridden in sub-classes + */ + public void close () throws IOException {} + /* * Returns the local class path for this loader, or null if none. */ @@ -562,6 +575,7 @@ public class URLClassPath { private MetaIndex metaIndex; private URLStreamHandler handler; private HashMap lmap; + private boolean closed = false; /* * Creates a new JarLoader for the specified URL referring to @@ -604,6 +618,17 @@ public class URLClassPath { } } + @Override + public void close () throws IOException { + // closing is synchronized at higher level + if (!closed) { + closed = true; + // in case not already open. + ensureOpen(); + jar.close(); + } + } + JarFile getJarFile () { return jar; } diff --git a/test/java/net/URLClassLoader/closetest/CloseTest.java b/test/java/net/URLClassLoader/closetest/CloseTest.java new file mode 100644 index 000000000..b0fdd577b --- /dev/null +++ b/test/java/net/URLClassLoader/closetest/CloseTest.java @@ -0,0 +1,246 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/** + * @test + * @bug 4167874 + * @library ../../../../com/sun/net/httpserver + * @build FileServerHandler + * @run shell build.sh + * @run main/othervm CloseTest + * @summary URL-downloaded jar files can consume all available file descriptors + */ + +import java.io.*; +import java.net.*; +import java.lang.reflect.*; +import java.util.concurrent.*; +import com.sun.net.httpserver.*; + +public class CloseTest { + + static void copyFile (String src, String dst) { + copyFile (new File(src), new File(dst)); + } + + static void copyDir (String src, String dst) { + copyDir (new File(src), new File(dst)); + } + + static void copyFile (File src, File dst) { + try { + if (!src.isFile()) { + throw new RuntimeException ("File not found: " + src.toString()); + } + dst.delete(); + dst.createNewFile(); + FileInputStream i = new FileInputStream (src); + FileOutputStream o = new FileOutputStream (dst); + byte[] buf = new byte [1024]; + int count; + while ((count=i.read(buf)) >= 0) { + o.write (buf, 0, count); + } + i.close(); + o.close(); + } catch (IOException e) { + throw new RuntimeException (e); + } + } + + static void rm_minus_rf (File path) { + if (!path.exists()) { + return; + } + if (path.isFile()) { + if (!path.delete()) { + throw new RuntimeException ("Could not delete " + path); + } + } else if (path.isDirectory ()) { + String[] names = path.list(); + File[] files = path.listFiles(); + for (int i=0; i