changes after first review

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