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