| /* | |
| * Copyright (c) 2022, 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. | |
| */ | |
| import org.testng.Assert; | |
| import org.testng.annotations.DataProvider; | |
| import org.testng.annotations.Test; | |
| import java.io.BufferedReader; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.io.Writer; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| /* | |
| * @test | |
| * @bug 8289643 8291760 | |
| * @requires (os.family == "linux" & !vm.musl) | |
| * @summary File descriptor leak detection with ProcessBuilder.startPipeline | |
| * @run testng/othervm PipelineLeaksFD | |
| */ | |
| @Test | |
| public class PipelineLeaksFD { | |
| @DataProvider | |
| public Object[][] builders() { | |
| return new Object[][]{ | |
| {List.of(new ProcessBuilder("cat"))}, | |
| {List.of(new ProcessBuilder("cat"), | |
| new ProcessBuilder("cat"))}, | |
| {List.of(new ProcessBuilder("cat"), | |
| new ProcessBuilder("cat"), | |
| new ProcessBuilder("cat"), | |
| new ProcessBuilder("cat"), | |
| new ProcessBuilder("cat"))}, | |
| }; | |
| } | |
| @Test(dataProvider = "builders") | |
| void checkForLeaks(List<ProcessBuilder> builders) throws IOException { | |
| Set<PipeRecord> pipesBefore = myPipes(); | |
| if (pipesBefore.size() < 3) { | |
| System.out.println(pipesBefore); | |
| Assert.fail("There should be at least 3 pipes before, (0, 1, 2)"); | |
| } | |
| List<Process> processes = ProcessBuilder.startPipeline(builders); | |
| // Write something through the pipeline | |
| final String text = "xyz"; | |
| try (Writer out = processes.get(0).outputWriter()) { | |
| out.write(text); | |
| } | |
| // Read, check, and close all streams | |
| for (int i = 0; i < processes.size(); i++) { | |
| final Process p = processes.get(i); | |
| String expectedOut = (i == processes.size() - 1) ? text : null; | |
| String expectedErr = null; // EOF | |
| try (BufferedReader inputStream = p.inputReader(); | |
| BufferedReader errorStream = p.errorReader()) { | |
| String outActual = inputStream.readLine(); | |
| Assert.assertEquals(outActual, expectedOut, "stdout, process[ " + i + "]: " + p); | |
| String errActual = errorStream.readLine(); | |
| Assert.assertEquals(errActual, expectedErr, "stderr, process[ " + i + "]: " + p); | |
| } | |
| } | |
| processes.forEach(p -> waitForQuiet(p)); | |
| Set<PipeRecord> pipesAfter = myPipes(); | |
| if (!pipesBefore.equals(pipesAfter)) { | |
| Set<PipeRecord> missing = new HashSet<>(pipesBefore); | |
| missing.removeAll(pipesAfter); | |
| printPipes(missing, "Missing from pipesAfter"); | |
| Set<PipeRecord> extra = new HashSet<>(pipesAfter); | |
| extra.removeAll(pipesBefore); | |
| printPipes(extra, "Extra pipes in pipesAfter"); | |
| Assert.fail("More or fewer pipes than expected"); | |
| } | |
| } | |
| static void printPipes(Set<PipeRecord> pipes, String label) { | |
| System.out.printf("%s: [%d]%n", label, pipes.size()); | |
| pipes.forEach(r -> System.out.printf("%-20s: %s%n", r.fd(), r.link())); | |
| } | |
| static void waitForQuiet(Process p) { | |
| try { | |
| int st = p.waitFor(); | |
| if (st != 0) { | |
| System.out.println("non-zero exit status: " + p); | |
| } | |
| } catch (InterruptedException ie) { | |
| } | |
| } | |
| /** | |
| * Collect a Set of pairs of /proc fd paths and the symbol links that are pipes. | |
| * @return A set of PipeRecords, possibly empty | |
| */ | |
| static Set<PipeRecord> myPipes() { | |
| Path path = Path.of("/proc/" + ProcessHandle.current().pid() + "/fd"); | |
| Set<PipeRecord> pipes = new HashSet<>(); | |
| File[] files = path.toFile().listFiles(f -> Files.isSymbolicLink(f.toPath())); | |
| if (files != null) { | |
| for (File file : files) { | |
| try { | |
| Path link = Files.readSymbolicLink(file.toPath()); | |
| if (link.toString().startsWith("pipe:")) { | |
| pipes.add(new PipeRecord(file.toPath(), link)); | |
| } | |
| } catch (IOException ioe) { | |
| } | |
| } | |
| } | |
| return pipes; | |
| } | |
| record PipeRecord(Path fd, Path link) { }; | |
| } |