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