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

URLClassLoader.close() can cause other class loader’s resource loading to fail

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P4 P4
    • None
    • 8, 11, 17, 22, 23, 24, 25
    • core-libs
    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      I’m on macOS 14.7.2, but this originally occurred on our Ubuntu Linux x64 servers.
      Impacts all Java versions I’ve tried. (11.0.14.1, 22.0.1, 23.0.2).

      A DESCRIPTION OF THE PROBLEM :
      It’s possible for multiple URLClassLoader instances to read the same jar files. If you close a URLClassLoader, it closes all of its jars, even those still in use by other URLClassLoader instances. Those other uses may throw, and be unable to access resources that should be accessible.

      This will usually be experienced as a rare race condition. It can be reproduced deterministically by interleaving calls to two different URLClassLoader instances. I believe the default class loader may be adversely impacted by this bug.

      We experienced an outage because we failed to load a native library from a resource at service start-up. I haven’t been able to isolate exactly where a URLClassLoader was closed, but it might have been in Guava’s
      FinalizableReferenceQueue. That code closes a URLClassLoader when its initialized.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Create two URLClassLoader instances that both read the same .jar file.
      Start reading a resource in one class loader by calling getResourceAsStream().
      Close the other class loader.
      Continue reading the resource.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      $ javac Main.java && java Main skip
      READ A SUCCESS
      READ B SUCCESS
      ACTUAL -
      $ javac Main.java && java Main
      READ A SUCCESS
      Exception in thread "main" java.io.IOException: Stream closed
      at java.base/java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:68)
      at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:154)
      at java.base/java.io.FilterInputStream.read(FilterInputStream.java:119)
      at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:350)
      at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:393)
      at java.base/sun.nio.cs.StreamDecoder.lockedRead(StreamDecoder.java:217)
      at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:171)
      at java.base/java.io.InputStreamReader.read(InputStreamReader.java:186)
      at java.base/java.io.BufferedReader.fill(BufferedReader.java:160)
      at java.base/java.io.BufferedReader.implReadLine(BufferedReader.java:370)
      at java.base/java.io.BufferedReader.readLine(BufferedReader.java:347)
      at java.base/java.io.BufferedReader.readLine(BufferedReader.java:436)
      at Main.main(Main.java:48)

      ---------- BEGIN SOURCE ----------
      import java.io.BufferedReader;
      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.net.URL;
      import java.net.URLClassLoader;
      import java.util.jar.JarOutputStream;
      import java.util.zip.ZipEntry;
      import static java.nio.charset.StandardCharsets.UTF_8;

      public class Main {
        public static void main(String[] args) throws Exception {
          boolean skipClose = args.length > 0 && args[0].equals("skip");

          // Build resources.jar, whose file 'hello.txt' contains 'hello world'.
          File jarFile = new File("resources.jar");
          String resourceName = "hello.txt";
          String resourceContent = "hello world";
          try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jarFile))) {
            out.putNextEntry(new ZipEntry(resourceName));
            out.write(resourceContent.getBytes(UTF_8));
          }
          URL[] jarFileUrls = {
            jarFile.toURI().toURL()
          };

          // Create a class loader B and start reading a resource with it.
          URLClassLoader loaderB = URLClassLoader.newInstance(jarFileUrls);
          InputStream inB = loaderB.getResourceAsStream(resourceName);

          // Create class loader A that reads the same jar file.
          URLClassLoader loaderA = URLClassLoader.newInstance(jarFileUrls);
          try (
            InputStream inA = loaderA.getResourceAsStream(resourceName);
            BufferedReader readerA = new BufferedReader(new InputStreamReader(inA))
          ) {
            if (readerA.readLine().equals(resourceContent)) {
              System.out.println("READ A SUCCESS");
            }
          }
          if (!skipClose) {
            loaderA.close();
          }

          // Finish reading the file with class loader B.
          try (BufferedReader readerB = new BufferedReader(new InputStreamReader(inB))) {
            if (readerB.readLine().equals(resourceContent)) {
              System.out.println("READ B SUCCESS");
            }
          }
        }
      }

      ---------- END SOURCE ----------

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: