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 * @build jdk.test.lib.Utils 45 * @run testng OnExitTest 46 * @summary Functions of Process.onExit and ProcessHandle.onExit 47 * @author Roger Riggs 48 */ 49 50 public class OnExitTest extends ProcessUtil { 51 52 @SuppressWarnings("raw_types") 53 public static void main(String[] args) { 54 Class<?>[] testclass = { OnExitTest.class}; 55 TestNG testng = new TestNG(); 56 testng.setTestClasses(testclass); 57 testng.run(); 58 } 59 60 /** 61 * Basic test of exitValue and onExit. 62 */ 63 @Test 64 public static void test1() { 65 try { 66 int[] exitValues = {0, 1, 10}; 67 for (int value : exitValues) { 68 Process p = JavaChild.spawn("exit", Integer.toString(value)); 69 CompletableFuture<Process> future = p.onExit(); 70 future.thenAccept( (ph) -> { 71 int actualExitValue = ph.exitValue(); 72 printf(" javaChild done: %s, exitStatus: %d%n", 73 ph, actualExitValue); 74 Assert.assertEquals(actualExitValue, value, "actualExitValue incorrect"); 75 Assert.assertEquals(ph, p, "Different Process passed to thenAccept"); 76 }); 77 78 Process h = future.get(); 79 Assert.assertEquals(h, p); 80 Assert.assertEquals(p.exitValue(), value); 81 Assert.assertFalse(p.isAlive(), "Process should not be alive"); 82 p.waitFor(); 83 } 84 } catch (IOException | InterruptedException | ExecutionException ex) { 85 Assert.fail(ex.getMessage(), ex); 86 } finally { 87 destroyProcessTree(ProcessHandle.current()); 88 } 89 } 90 91 /** 92 * Test of Completion handler when parent is killed. 93 * Spawn 1 child to spawn 3 children each with 2 children. 94 */ 95 @Test 96 public static void test2() { 97 ProcessHandle procHandle = null; 98 try { 99 ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>(); 100 List<ProcessHandle> children = getChildren(ProcessHandle.current()); 101 children.forEach(ProcessUtil::printProcess); 102 103 JavaChild proc = JavaChild.spawnJavaChild("stdin"); 104 procHandle = proc.toHandle(); 105 printf(" spawned: %d%n", proc.pid()); 106 107 proc.forEachOutputLine((s) -> { 108 String[] split = s.trim().split(" "); 109 if (split.length == 3 && split[1].equals("spawn")) { 110 Long child = Long.valueOf(split[2]); 111 Long parent = Long.valueOf(split[0].split(":")[0]); 112 processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get()); 113 } 114 }); 115 116 proc.sendAction("spawn", "3", "stdin"); 117 118 proc.sendAction("child", "spawn", "2", "stdin"); 119 120 // Poll until all 9 child processes exist or the timeout is reached 121 int expected = 9; 122 long timeout = Utils.adjustTimeout(10L); 123 Instant endTimeout = Instant.now().plusSeconds(timeout); 124 do { 125 Thread.sleep(200L); 126 printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected); 127 } while (processes.size() < expected && 128 Instant.now().isBefore(endTimeout)); 129 130 if (processes.size() < expected) { 131 printf("WARNING: not all children have been started. Can't complete test.%n"); 132 printf(" You can try to increase the timeout or%n"); 133 printf(" you can try to use a faster VM (i.e. not a debug version).%n"); 134 } 135 children = getDescendants(procHandle); 136 137 ConcurrentHashMap<ProcessHandle, CompletableFuture<ProcessHandle>> completions = 138 new ConcurrentHashMap<>(); 139 Instant startTime = Instant.now(); 140 // Create a future for each of the 9 children 141 processes.forEach( (p, parent) -> { 142 CompletableFuture<ProcessHandle> cf = p.onExit().whenComplete((ph, ex) -> { 143 Duration elapsed = Duration.between(startTime, Instant.now()); 144 printf("whenComplete: pid: %s, exception: %s, thread: %s, elapsed: %s%n", 145 ph, ex, Thread.currentThread(), elapsed); 146 }); 147 completions.put(p, cf); 148 }); 149 150 // Check that each of the spawned processes is included in the children 151 List<ProcessHandle> remaining = new ArrayList<>(children); 152 processes.forEach((p, parent) -> { 153 Assert.assertTrue(remaining.remove(p), "spawned process should have been in children"); 154 }); 155 156 // Remove Win32 system spawned conhost.exe processes 157 remaining.removeIf(ProcessUtil::isWindowsConsole); 158 159 remaining.forEach(p -> printProcess(p, "unexpected: ")); 160 if (remaining.size() > 0) { 161 // Show full list for debugging 162 ProcessUtil.logTaskList(); 163 } 164 165 proc.destroy(); // kill off the parent 166 proc.waitFor(); 167 168 // Wait for all the processes and corresponding onExit CF to be completed 169 processes.forEach((p, parent) -> { 170 try { 171 p.onExit().get(); 172 completions.get(p).join(); 173 } catch (InterruptedException | ExecutionException ex) { 174 // ignore 175 } 176 }); 177 178 // Verify that all 9 exit handlers were called with the correct ProcessHandle 179 processes.forEach((p, parent) -> { 180 ProcessHandle value = completions.get(p).getNow(null); 181 Assert.assertEquals(p, value, "onExit.get value expected: " + p 182 + ", actual: " + value 183 + ": " + p.info()); 184 }); 185 186 // Show the status of the original children 187 children.forEach(p -> printProcess(p, "after onExit:")); 188 189 Assert.assertEquals(proc.isAlive(), false, "destroyed process is alive:: %s%n" + proc); 190 } catch (IOException | InterruptedException ex) { 191 Assert.fail(ex.getMessage()); 192 } finally { 193 if (procHandle != null) { 194 destroyProcessTree(procHandle); 195 } 196 } 197 } 198 199 /** 200 * Verify that onExit completes for a non-child process only when 201 * the process has exited. 202 * Spawn a child (A) waiting to be commanded to exit. 203 * Spawn a child (B) to wait for that process to exit. 204 * Command (A) to exit. 205 * Check that (B) does not complete until (A) has exited. 206 */ 207 @Test 208 public static void peerOnExitTest() { 209 String line = null; 210 ArrayBlockingQueue<String> alines = new ArrayBlockingQueue<>(100); 211 ArrayBlockingQueue<String> blines = new ArrayBlockingQueue<>(100); 212 JavaChild A = null; 213 try { 214 String[] split; 215 A = JavaChild.spawnJavaChild("stdin"); 216 A.forEachOutputLine(l -> alines.add(l)); 217 218 // Verify A is running 219 A.sendAction("pid"); 220 do { 221 split = getSplitLine(alines); 222 } while (!"pid".equals(split[1])); 223 224 JavaChild B = null; 225 try { 226 B = JavaChild.spawnJavaChild("stdin"); 227 B.forEachOutputLine(l -> blines.add(l)); 228 229 // Verify B is running 230 B.sendAction("pid"); 231 do { 232 split = getSplitLine(blines); 233 } while (!"pid".equals(split[1])); 234 235 // Tell B to wait for A's pid 236 B.sendAction("waitpid", A.pid()); 237 238 // Wait a bit to see if B will prematurely report the termination of A 239 try { 240 line = blines.poll(5L, TimeUnit.SECONDS); 241 } catch (InterruptedException ie) { 242 Assert.fail("interrupted", ie); 243 } 244 Assert.assertNull(line, "waitpid didn't wait"); 245 246 A.toHandle().onExit().thenAccept(p -> { 247 System.out.printf(" A.toHandle().onExit().A info: %s, now: %s%n", 248 p.info(), Instant.now()); 249 }); 250 251 A.onExit().thenAccept(p -> { 252 System.out.printf(" A.onExit().A info: %s, now: %s%n", 253 p.info(), Instant.now()); 254 }); 255 256 ProcessHandle.Info A_info = A.info(); 257 258 A.sendAction("exit", 0L); 259 260 // Look for B to report that A has exited 261 do { 262 Instant start = Instant.now(); 263 while (blines.isEmpty() && A.isAlive()) { 264 A_info = A.info(); // Spin 265 } 266 Instant end = Instant.now(); 267 System.out.printf(" a.isAlive: %s, a.info: %s, @%s%n", A.isAlive(), A.info(), 268 Duration.between(start, end)); 269 270 split = getSplitLine(blines); 271 } while (!"waitpid".equals(split[1])); 272 273 Assert.assertEquals(split[2], "false", "Process A should not be alive"); 274 275 B.sendAction("exit", 0L); 276 } catch (IOException ioe) { 277 Assert.fail("unable to start JavaChild B", ioe); 278 } finally { 279 B.destroyForcibly(); 280 } 281 } catch (IOException ioe2) { 282 Assert.fail("unable to start JavaChild A", ioe2); 283 } finally { 284 A.destroyForcibly(); 285 } 286 } 287 288 private static boolean DEBUG = true; 289 290 /** 291 * Get a line from the queue and split into words on whitespace. 292 * Log to stdout if requested. 293 * @param queue a queue of strings 294 * @return the words split from the line. 295 */ 296 private static String[] getSplitLine(ArrayBlockingQueue<String> queue) { 297 try { 298 String line = queue.take(); 299 String[] split = line.split("\\s"); 300 if (DEBUG) { 301 System.out.printf(" Child Output: %s%n", line); 302 } 303 return split; 304 } catch (InterruptedException ie) { 305 Assert.fail("interrupted", ie); 306 return null; 307 } 308 } 309 310 }