1 import static java.io.File.createTempFile; 2 import static java.lang.Long.parseLong; 3 import static java.lang.System.getProperty; 4 import static java.lang.management.ManagementFactory.getOperatingSystemMXBean; 5 import static java.nio.file.Files.readAllBytes; 6 import static java.nio.file.Files.readSymbolicLink; 7 import static java.util.Arrays.stream; 8 import static java.util.stream.Collectors.joining; 9 import static jdk.test.lib.process.ProcessTools.createJavaProcessBuilder; 10 11 import java.io.File; 12 import java.io.IOException; 13 14 import com.sun.management.UnixOperatingSystemMXBean; 15 16 /* 17 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 18 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 19 * 20 * This code is free software; you can redistribute it and/or modify it 21 * under the terms of the GNU General Public License version 2 only, as 22 * published by the Free Software Foundation. 23 * 24 * This code is distributed in the hope that it will be useful, but WITHOUT 25 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 26 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 27 * version 2 for more details (a copy is included in the LICENSE file that 28 * accompanied this code). 29 * 30 * You should have received a copy of the GNU General Public License version 31 * 2 along with this work; if not, write to the Free Software Foundation, 32 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 33 * 34 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 35 * or visit www.oracle.com if you need additional information or have any 36 * questions. 37 */ 38 39 /* 40 * @test TestInheritFD 41 * @bug 8176717 8176809 42 * @summary a new process should not inherit open file descriptors 43 * @requires (os.family != "mac") 44 * @library /test/lib 45 * @modules java.base/jdk.internal.misc 46 * java.management 47 */ 48 49 /** 50 * Test that HotSpot does not leak logging file descriptors. 51 * 52 * This test is performed in three steps. The first VM starts a second VM with 53 * gc logging enabled. The second VM starts a third VM and redirects the third 54 * VMs output to the first VM, it then exits and hopefully closes its log file. 55 * 56 * The third VM waits for the second to exit and close its log file. After that, 57 * the third VM tries to rename the log file of the second VM. If it succeeds in 58 * doing so it means that the third VM did not inherit the open log file 59 * (windows can not rename opened files easily) 60 * 61 * The third VM communicates the success to rename the file by printing "CLOSED 62 * FD". The first VM checks that the string was printed by the third VM. 63 * 64 * On unix like systems, UnixOperatingSystemMXBean is used to check open file 65 * descriptors. 66 */ 67 68 public class TestInheritFD { 69 70 public static final String LEAKS_FD = "VM RESULT => LEAKS FD"; 71 public static final String RETAINS_FD = "VM RESULT => RETAINS FD"; 72 public static final String EXIT = "VM RESULT => VM EXIT"; 73 public static final String LOG_SUFFIX = ".strangelogsuffixthatcanbecheckedfor"; 74 75 // first VM 76 public static void main(String[] args) throws Exception { 77 String logPath = createTempFile("logging", LOG_SUFFIX).getName(); 78 File commFile = createTempFile("communication", ".txt"); 79 80 ProcessBuilder pb = createJavaProcessBuilder( 81 "-Xlog:gc:\"" + logPath + "\"", 82 "-Dtest.jdk=" + getProperty("test.jdk"), 83 VMStartedWithLogging.class.getName(), 84 logPath); 85 86 pb.redirectOutput(commFile); // use temp file to communicate between processes 87 pb.start(); 88 89 String out = ""; 90 do { 91 out = new String(readAllBytes(commFile.toPath())); 92 Thread.sleep(100); 93 System.out.println("SLEEP 100 millis"); 94 } while (!out.contains(EXIT)); 95 96 System.out.println(out); 97 if (out.contains(RETAINS_FD)) { 98 System.out.println("Log file was not inherited by third VM"); 99 } else { 100 throw new RuntimeException("could not match: " + RETAINS_FD); 101 } 102 } 103 104 static class VMStartedWithLogging { 105 // second VM 106 public static void main(String[] args) throws IOException, InterruptedException { 107 ProcessBuilder pb = createJavaProcessBuilder( 108 "-Dtest.jdk=" + getProperty("test.jdk"), 109 VMShouldNotInheritFileDescriptors.class.getName(), 110 args[0], 111 "" + ProcessHandle.current().pid(), 112 "" + (supportsUnixMXBean()?+unixNrFD():-1)); 113 pb.inheritIO(); // in future, redirect information from third VM to first VM 114 pb.start(); 115 116 findOpenLogFile(); 117 } 118 } 119 120 static class VMShouldNotInheritFileDescriptors { 121 // third VM 122 public static void main(String[] args) throws InterruptedException { 123 File logFile = new File(args[0]); 124 long parentPid = parseLong(args[1]); 125 long parentFDCount = parseLong(args[2]); 126 127 if(supportsUnixMXBean()){ 128 long thisFDCount = unixNrFD(); 129 System.out.println("This VM FD-count (" 130 + thisFDCount 131 + ") should be strictly less than parent VM FD-count (" 132 + parentFDCount 133 + ") as log file should have been closed, HOWEVER, THIS CAN NOT BE RELIED" 134 + " ON as files in /proc and /sys is opened by the JVM"); 135 System.out.println(findOpenLogFile()?LEAKS_FD:RETAINS_FD); 136 } else if (getProperty("os.name").toLowerCase().contains("win")) { 137 windows(logFile, parentPid); 138 } else { 139 System.out.println(LEAKS_FD); // default fail on unknown configuration 140 } 141 System.out.println(EXIT); 142 } 143 } 144 145 static boolean supportsUnixMXBean() { 146 return getOperatingSystemMXBean() instanceof UnixOperatingSystemMXBean; 147 } 148 149 static long unixNrFD() { 150 UnixOperatingSystemMXBean osBean = (UnixOperatingSystemMXBean) getOperatingSystemMXBean(); 151 return osBean.getOpenFileDescriptorCount(); 152 } 153 154 static File linkTarget(File f) { 155 try { 156 return readSymbolicLink(f.toPath()).toFile(); 157 } catch (IOException e) { 158 return new File("Error: could not read symbolic link (last link can not be read as it points to the /proc/self/fd directory that has been closed): " + e.toString()); 159 } 160 } 161 162 static boolean findOpenLogFile() { 163 File dir = new File("/proc/self/fd"); 164 165 System.out.println("Open file descriptors:\n" + stream(dir.listFiles()) 166 .map(f -> "###" + f.getAbsolutePath() + " -> " + linkTarget(f)) 167 .collect(joining("\n"))); 168 169 return stream(dir.listFiles()) 170 .map(TestInheritFD::linkTarget) 171 .filter(f -> f.getName().endsWith(LOG_SUFFIX)) 172 .findAny() 173 .isPresent(); 174 } 175 176 static void windows(File f, long parentPid) throws InterruptedException { 177 System.out.println("waiting for pid: " + parentPid); 178 ProcessHandle.of(parentPid).ifPresent(handle -> handle.onExit().join()); 179 System.out.println("trying to rename file to the same name: " + f); 180 System.out.println(f.renameTo(f)?RETAINS_FD:LEAKS_FD); // this parts communicates a closed file descriptor by printing "VM RESULT => RETAINS FD" 181 }