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