1 /* 2 * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.IOException; 25 import java.time.Duration; 26 import java.time.Instant; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.concurrent.ArrayBlockingQueue; 30 import java.util.concurrent.CompletableFuture; 31 import java.util.concurrent.ConcurrentHashMap; 32 import java.util.concurrent.ExecutionException; 33 import java.util.concurrent.TimeUnit; 34 35 import jdk.test.lib.Utils; 36 37 import org.testng.annotations.Test; 38 import org.testng.Assert; 39 import org.testng.TestNG; 40 41 /* 42 * @test 43 * @library /test/lib 44 * @modules java.base/jdk.internal.misc 45 * jdk.management 46 * @build jdk.test.lib.Platform jdk.test.lib.Utils 47 * @run testng OnExitTest 48 * @summary Functions of Process.onExit and ProcessHandle.onExit 49 * @author Roger Riggs 50 */ 51 52 public class OnExitTest extends ProcessUtil { 53 54 @SuppressWarnings("raw_types") 55 public static void main(String[] args) { 56 Class<?>[] testclass = { OnExitTest.class}; 57 TestNG testng = new TestNG(); 58 testng.setTestClasses(testclass); 59 testng.run(); 60 } 61 62 /** 63 * Basic test of exitValue and onExit. 64 */ 65 @Test 66 public static void test1() { 67 try { 68 int[] exitValues = {0, 1, 10}; 69 for (int value : exitValues) { 70 Process p = JavaChild.spawn("exit", Integer.toString(value)); 71 CompletableFuture<Process> future = p.onExit(); 72 future.thenAccept( (ph) -> { 73 int actualExitValue = ph.exitValue(); 74 printf(" javaChild done: %s, exitStatus: %d%n", 75 ph, actualExitValue); 76 Assert.assertEquals(actualExitValue, value, "actualExitValue incorrect"); 77 Assert.assertEquals(ph, p, "Different Process passed to thenAccept"); 78 }); 79 80 Process h = future.get(); 81 Assert.assertEquals(h, p); 82 Assert.assertEquals(p.exitValue(), value); 83 Assert.assertFalse(p.isAlive(), "Process should not be alive"); 84 p.waitFor(); 85 } 86 } catch (IOException | InterruptedException | ExecutionException ex) { 87 Assert.fail(ex.getMessage(), ex); 88 } finally { 89 destroyProcessTree(ProcessHandle.current()); 90 } 91 } 92 93 /** 94 * Test of Completion handler when parent is killed. 95 * Spawn 1 child to spawn 3 children each with 2 children. 96 */ 97 @Test 98 public static void test2() { 99 ProcessHandle procHandle = null; 100 try { 101 ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>(); 102 List<ProcessHandle> children = getChildren(ProcessHandle.current()); 103 children.forEach(ProcessUtil::printProcess); 104 105 JavaChild proc = JavaChild.spawnJavaChild("stdin"); 106 procHandle = proc.toHandle(); 107 printf(" spawned: %d%n", proc.pid()); 108 109 proc.forEachOutputLine((s) -> { 110 String[] split = s.trim().split(" "); 111 if (split.length == 3 && split[1].equals("spawn")) { 112 Long child = Long.valueOf(split[2]); 113 Long parent = Long.valueOf(split[0].split(":")[0]); 114 processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get()); 115 } 116 }); 117 118 proc.sendAction("spawn", "3", "stdin"); 119 120 proc.sendAction("child", "spawn", "2", "stdin"); 121 122 // Poll until all 9 child processes exist or the timeout is reached 123 int expected = 9; 124 long timeout = Utils.adjustTimeout(10L); 125 Instant endTimeout = Instant.now().plusSeconds(timeout); 126 do { 127 Thread.sleep(200L); 128 printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected); 129 } while (processes.size() < expected && 130 Instant.now().isBefore(endTimeout)); 131 132 if (processes.size() < expected) { 133 printf("WARNING: not all children have been started. Can't complete test.%n"); 134 printf(" You can try to increase the timeout or%n"); 135 printf(" you can try to use a faster VM (i.e. not a debug version).%n"); 136 } 137 children = getDescendants(procHandle); 138 139 ConcurrentHashMap<ProcessHandle, CompletableFuture<ProcessHandle>> completions = 140 new ConcurrentHashMap<>(); 141 Instant startTime = Instant.now(); 142 // Create a future for each of the 9 children 143 processes.forEach( (p, parent) -> { 144 CompletableFuture<ProcessHandle> cf = p.onExit().whenComplete((ph, ex) -> { 145 Duration elapsed = Duration.between(startTime, Instant.now()); 146 printf("whenComplete: pid: %s, exception: %s, thread: %s, elapsed: %s%n", 147 ph, ex, Thread.currentThread(), elapsed); 148 }); 149 completions.put(p, cf); 150 }); 151 152 // Check that each of the spawned processes is included in the children 153 List<ProcessHandle> remaining = new ArrayList<>(children); 154 processes.forEach((p, parent) -> { 155 Assert.assertTrue(remaining.remove(p), "spawned process should have been in children"); 156 }); 157 158 // Remove Win32 system spawned conhost.exe processes 159 remaining.removeIf(ProcessUtil::isWindowsConsole); 160 161 remaining.forEach(p -> printProcess(p, "unexpected: ")); 162 if (remaining.size() > 0) { 163 // Show full list for debugging 164 ProcessUtil.logTaskList(); 165 } 166 167 proc.destroy(); // kill off the parent 168 proc.waitFor(); 169 170 // Wait for all the processes and corresponding onExit CF to be completed 171 processes.forEach((p, parent) -> { 172 try { 173 p.onExit().get(); 174 completions.get(p).join(); 175 } catch (InterruptedException | ExecutionException ex) { 176 // ignore 177 } 178 }); 179 180 // Verify that all 9 exit handlers were called with the correct ProcessHandle 181 processes.forEach((p, parent) -> { 182 ProcessHandle value = completions.get(p).getNow(null); 183 Assert.assertEquals(p, value, "onExit.get value expected: " + p 184 + ", actual: " + value 185 + ": " + p.info()); 186 }); 187 188 // Show the status of the original children 189 children.forEach(p -> printProcess(p, "after onExit:")); 190 191 Assert.assertEquals(proc.isAlive(), false, "destroyed process is alive:: %s%n" + proc); 192 } catch (IOException | InterruptedException ex) { 193 Assert.fail(ex.getMessage()); 194 } finally { 195 if (procHandle != null) { 196 destroyProcessTree(procHandle); 197 } 198 } 199 } 200 201 /** 202 * Verify that onExit completes for a non-child process only when 203 * the process has exited. 204 * Spawn a child (A) waiting to be commanded to exit. 205 * Spawn a child (B) to wait for that process to exit. 206 * Command (A) to exit. 207 * Check that (B) does not complete until (A) has exited. 208 */ 209 @Test 210 public static void peerOnExitTest() { 211 String line = null; 212 ArrayBlockingQueue<String> alines = new ArrayBlockingQueue<>(100); 213 ArrayBlockingQueue<String> blines = new ArrayBlockingQueue<>(100); 214 JavaChild A = null; 215 try { 216 String[] split; 217 A = JavaChild.spawnJavaChild("stdin"); 218 A.forEachOutputLine(l -> alines.add(l)); 219 220 // Verify A is running 221 A.sendAction("pid"); 222 do { 223 split = getSplitLine(alines); 224 } while (!"pid".equals(split[1])); 225 226 JavaChild B = null; 227 try { 228 B = JavaChild.spawnJavaChild("stdin"); 229 B.forEachOutputLine(l -> blines.add(l)); 230 231 // Verify B is running 232 B.sendAction("pid"); 233 do { 234 split = getSplitLine(blines); 235 } while (!"pid".equals(split[1])); 236 237 // Tell B to wait for A's pid 238 B.sendAction("waitpid", A.pid()); 239 240 // Wait a bit to see if B will prematurely report the termination of A 241 try { 242 line = blines.poll(5L, TimeUnit.SECONDS); 243 } catch (InterruptedException ie) { 244 Assert.fail("interrupted", ie); 245 } 246 Assert.assertNull(line, "waitpid didn't wait"); 247 248 A.sendAction("exit", 0L); 249 250 // Look for B to report that A has exited 251 do { 252 split = getSplitLine(blines); 253 } while (!"waitpid".equals(split[1])); 254 255 Assert.assertEquals(split[2], "false", "Process A should not be alive"); 256 257 B.sendAction("exit", 0L); 258 } catch (IOException ioe) { 259 Assert.fail("unable to start JavaChild B", ioe); 260 } finally { 261 B.destroyForcibly(); 262 } 263 } catch (IOException ioe2) { 264 Assert.fail("unable to start JavaChild A", ioe2); 265 } finally { 266 A.destroyForcibly(); 267 } 268 } 269 270 private static boolean DEBUG = true; 271 272 /** 273 * Get a line from the queue and split into words on whitespace. 274 * Log to stdout if requested. 275 * @param queue a queue of strings 276 * @return the words split from the line. 277 */ 278 private static String[] getSplitLine(ArrayBlockingQueue<String> queue) { 279 try { 280 String line = queue.take(); 281 String[] split = line.split("\\s"); 282 if (DEBUG) { 283 System.out.printf(" Child Output: %s%n", line); 284 } 285 return split; 286 } catch (InterruptedException ie) { 287 Assert.fail("interrupted", ie); 288 return null; 289 } 290 } 291 292 }