diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java index e3071c27064..7957834e960 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -29,6 +29,7 @@ import static com.sun.tools.jdeps.Module.trace; import static java.util.stream.Collectors.*; import java.io.BufferedInputStream; +import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -41,6 +42,7 @@ import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.net.URI; +import java.net.URLClassLoader; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; @@ -282,6 +284,7 @@ public class JdepsConfiguration implements AutoCloseable { archive.close(); for (Module module : nameToModule.values()) module.close(); + system.close(); } static class SystemModuleFinder implements ModuleFinder { @@ -290,6 +293,7 @@ public class JdepsConfiguration implements AutoCloseable { private final FileSystem fileSystem; private final Path root; private final Map systemModules; + private final List closeables = new ArrayList<>(); SystemModuleFinder() { if (Files.isRegularFile(Paths.get(JAVA_HOME, "lib", "modules"))) { @@ -321,6 +325,11 @@ public class JdepsConfiguration implements AutoCloseable { env.put("java.home", javaHome); // a remote run-time image this.fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env); + closeables.add(fileSystem); + ClassLoader cl = fileSystem.provider().getClass().getClassLoader(); + if (cl instanceof URLClassLoader urlcl) { + closeables.add(urlcl); + } this.root = fileSystem.getPath("/modules"); this.systemModules = walk(root); } @@ -420,6 +429,24 @@ public class JdepsConfiguration implements AutoCloseable { .map(ModuleDescriptor::name) .collect(Collectors.toSet()); } + + public void close() throws IOException { + IOException ioe = null; + for (Closeable closeable : closeables) { + try { + closeable.close(); + } catch (IOException ex) { + if (ioe == null) { + ioe = ex; + } else { + ioe.addSuppressed(ex); + } + } + } + if (ioe != null) { + throw ioe; + } + } } public static class Builder { diff --git a/test/langtools/tools/jdeps/SystemFilesClosed.java b/test/langtools/tools/jdeps/SystemFilesClosed.java new file mode 100644 index 00000000000..f9585a10f62 --- /dev/null +++ b/test/langtools/tools/jdeps/SystemFilesClosed.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2026, 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 8386334 + * @summary Check that `lib/jrt-fs.jar` and `lib/modules` are properly closed while + * jdeps is invoked with `--system` option. + * @requires os.family == "mac" | os.family == "linux" + * @modules jdk.jdeps + * @library lib + * @build JdepsRunner + * @run junit ${test.main.class} + */ + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class SystemFilesClosed { + + private Path base; + + @Test + void testSystemFilesClosed() throws Exception { + // Probe lsof availability before doing the jlink/jdeps work + if (!lsofCommand().isPresent()) { + Assumptions.abort("lsof command is not available on this system"); + } + + String targetSystem = base.toString(); + int ret = java.util.spi.ToolProvider.findFirst("jlink") + .orElseThrow() + .run(System.out, System.err, "--add-modules", "java.base", "--output", targetSystem); + if (ret != 0) { + System.out.println("It is most probably an exploded build. Skip testing."); + return; + } + + JdepsRunner jdeps = new JdepsRunner("--check", "java.base", "--system", targetSystem); + Assertions.assertEquals(0, jdeps.run(true), "Jdeps task failed"); + + Process process = new ProcessBuilder() + .command(lsofCommand().orElseThrow(() -> new RuntimeException("lsof command is not available on this system")), + "-p", String.valueOf(ProcessHandle.current().pid())) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start(); + List lines; + String realPath = base.toRealPath().toString(); + try (InputStream stdout = process.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(stdout))) { + lines = reader.lines().filter(line -> line.contains(realPath)).toList(); + } + process.waitFor(); + Assertions.assertEquals(0, lines.size(), "File(s) remain opened: " + lines); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } + + static Optional lsofCommandCache = Arrays.stream(new String[] { + "/usr/bin/lsof", + "/usr/sbin/lsof", + "/bin/lsof", + "/sbin/lsof", + "/usr/local/bin/lsof"}) + .filter(args -> new File(args).exists()) + .findFirst(); + + static Optional lsofCommand() { + return lsofCommandCache; + } +}