Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8246714

URLClassLoader/JAR protocol handler poisons the global JarFile cache under concurrent load

    XMLWordPrintable

Details

    • Bug
    • Resolution: Unresolved
    • P3
    • tbd
    • 8u45, 14
    • core-libs
    • None
    • 7

    Description

      Jar files opened via URL, URLConnection, or URLClassLoader utilize an on-by-default cache of open JarFile instances. This map is maintained by JarFileFactory and maps jar URLs to already-opened JarFiles, presumably to reduce the number of open files when the same jar is used by multiple consumers.

      https://github.com/openjdk/jdk/blob/master/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java#L80-L97

      In the cases of URL and URLConnection, the "connection" to the jar resource is made via this cached JarFile, which is left in the cache after accessing the resource. The closing of an InputStream from URL.openStream or closing the connection acquired from URL.openConnection does not damage the cached JarFile.

      URLClassLoader, on the other hand, stores in its list of "closeables" any jar files used to acquire resources.

      https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/net/URLClassLoader.java#L294-L300

      Unless the cache use is disabled via URLConnection.setUseCaches(false), closing the URLClassLoader will also close these JarFile instances.

      https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/net/URLClassLoader.java#L349-L359

      Closing the JarFile instance is supposed to also remove it from the global cache.

      https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/sun/net/www/protocol/jar/URLJarFile.java#L167-L169

      Unfortunately, the removal and close is not performed as an atomic operation, which means there's a window of time where another thread could acquire a reference to a doomed JarFile. Basically, if two isolated URLClassLoader happen to access the same jar file concurrently, and one of them is closed, the other may error out.

      https://github.com/openjdk/jdk/blob/master/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java#L112-L118

      The result is that other threads may attempt to use JarFile instances that are about to be closed.

      These errors have been reported many times in many forms but usually without a clear reproduction or analysis. The following example fails quickly:

      https://gist.github.com/headius/32416a79faf14f63d660c40d83021bcf

      Example errors:

      {noformat}
      java.lang.IllegalStateException: zip file closed
      at java.base/java.util.zip.ZipFile.ensureOpen(ZipFile.java:915)
      at java.base/java.util.zip.ZipFile.getInputStream(ZipFile.java:378)
      at java.base/java.util.jar.JarFile.getInputStream(JarFile.java:835)
      at java.base/sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java:167)
      at java.base/java.net.URLClassLoader.getResourceAsStream(URLClassLoader.java:328)
      at BrokenURLClassLoader.lambda$main$0(BrokenURLClassLoader.java:27)

      java.lang.NullPointerException
      at java.base/sun.net.www.protocol.jar.JarURLConnection.connect(JarURLConnection.java:133)
      at java.base/sun.net.www.protocol.jar.JarURLConnection.getInputStream(JarURLConnection.java:155)
      at java.base/java.net.URLClassLoader.getResourceAsStream(URLClassLoader.java:328)
      at BrokenURLClassLoader.lambda$main$0(BrokenURLClassLoader.java:27)
      {noformat}

      We have also seen occasional "inflater closed" errors within resource/classloading logic while working on JRuby. They may be related but this case does not appear to reproduce them.

      I know of two possible workarounds for user code:

      * Disable the use of these caches by always acquiring the intermediate URLConnection before proceeding to access resources. It is not possible to set an entire URLClassLoader to not use the cache, however, so this does not help classloading requests from elsewhere.
      * Do not close URLClassLoader. This will leave open JarFile instances in the cache, which may be benign (static jar files from the filesystem) or malignant (temporary jar files created or unpacked by library or app code).

      To summarize: URLClassLoader's usage of the global JarFile cache leads to improperly closed JarFile instances and subsequent errors under concurrent load. All uses of URLClassLoader with jar files will potentially fail under concurrent load. This affects OpenJDK versions at least back to Java 7, when URLClassLoader.close was added.

      Attachments

        Issue Links

          Activity

            People

              aefimov Aleksej Efimov
              headius Charles Nutter
              Votes:
              1 Vote for this issue
              Watchers:
              15 Start watching this issue

              Dates

                Created:
                Updated: