1 /* 2 * Copyright (c) 2014, 2024, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.lang; 26 27 import jdk.internal.misc.InnocuousThread; 28 29 import java.lang.annotation.Native; 30 import java.security.AccessController; 31 import java.security.PrivilegedAction; 32 import java.time.Duration; 33 import java.time.Instant; 34 import java.util.Arrays; 35 import java.util.Optional; 36 import java.util.concurrent.CompletableFuture; 37 import java.util.concurrent.ConcurrentHashMap; 38 import java.util.concurrent.ConcurrentMap; 39 import java.util.concurrent.Executor; 40 import java.util.concurrent.Executors; 41 import java.util.concurrent.ThreadFactory; 42 import java.util.concurrent.ThreadLocalRandom; 43 import java.util.stream.IntStream; 44 import java.util.stream.Stream; 45 46 /** 47 * ProcessHandleImpl is the implementation of ProcessHandle. 48 * 49 * @see Process 50 * @since 9 51 */ 52 @jdk.internal.ValueBased 53 final class ProcessHandleImpl implements ProcessHandle { 54 /** 55 * Default size of stack for reaper processes. 56 */ 57 private static final long REAPER_DEFAULT_STACKSIZE = 128 * 1024; 58 59 /** 60 * Return value from waitForProcessExit0 indicating the process is not a child. 61 */ 62 @Native 63 private static final int NOT_A_CHILD = -2; 64 65 /** 66 * Cache the ProcessHandle of this process. 67 */ 68 private static final ProcessHandleImpl current; 69 70 /** 71 * Map of pids to ExitCompletions. 72 */ 73 private static final ConcurrentMap<Long, ExitCompletion> 74 completions = new ConcurrentHashMap<>(); 75 76 static { 77 initNative(); 78 long pid = getCurrentPid0(); 79 current = new ProcessHandleImpl(pid, isAlive0(pid)); 80 } 81 82 private static native void initNative(); 83 84 /** 85 * The thread pool of "process reaper" daemon threads. 86 */ 87 private static final Executor processReaperExecutor = initReaper(); 88 89 private static Executor initReaper() { 90 // Initialize ThreadLocalRandom now to avoid using the smaller stack 91 // of the processReaper threads. 92 ThreadLocalRandom.current(); 93 94 // For a debug build, the stack shadow zone is larger; 95 // Increase the total stack size to avoid potential stack overflow. 96 int debugDelta = "release".equals(System.getProperty("jdk.debug")) ? 0 : (16 * 4096); 97 final long stackSize = Boolean.getBoolean("jdk.lang.processReaperUseDefaultStackSize") 98 ? 0 : REAPER_DEFAULT_STACKSIZE + debugDelta; 99 100 ThreadFactory threadFactory = grimReaper -> { 101 Thread t = InnocuousThread.newSystemThread("process reaper", grimReaper, 102 stackSize, Thread.MAX_PRIORITY); 103 t.setDaemon(true); 104 return t; 105 }; 106 107 return Executors.newCachedThreadPool(threadFactory); 108 } 109 110 private static class ExitCompletion extends CompletableFuture<Integer> { 111 final boolean isReaping; 112 113 ExitCompletion(boolean isReaping) { 114 this.isReaping = isReaping; 115 } 116 } 117 118 /** 119 * Returns a CompletableFuture that completes with process exit status when 120 * the process completes. 121 * 122 * @param shouldReap true if the exit value should be reaped 123 */ 124 static CompletableFuture<Integer> completion(long pid, boolean shouldReap) { 125 // check canonicalizing cache 1st 126 ExitCompletion completion = completions.get(pid); 127 // re-try until we get a completion that shouldReap => isReaping 128 while (completion == null || (shouldReap && !completion.isReaping)) { 129 ExitCompletion newCompletion = new ExitCompletion(shouldReap); 130 if (completion == null) { 131 completion = completions.putIfAbsent(pid, newCompletion); 132 } else { 133 completion = completions.replace(pid, completion, newCompletion) 134 ? null : completions.get(pid); 135 } 136 if (completion == null) { 137 // newCompletion has just been installed successfully 138 completion = newCompletion; 139 // spawn a thread to wait for and deliver the exit value 140 processReaperExecutor.execute(new Runnable() { 141 // Use inner class to avoid lambda stack overhead 142 public void run() { 143 Thread t = Thread.currentThread(); 144 String threadName = t.getName(); 145 t.setName("process reaper (pid " + pid + ")"); 146 try { 147 int exitValue = waitForProcessExit0(pid, shouldReap); 148 if (exitValue == NOT_A_CHILD) { 149 // pid not alive or not a child of this process 150 // If it is alive wait for it to terminate 151 long sleep = 300; // initial milliseconds to sleep 152 int incr = 30; // increment to the sleep time 153 154 long startTime = isAlive0(pid); 155 long origStart = startTime; 156 while (startTime >= 0) { 157 try { 158 Thread.sleep(Math.min(sleep, 5000L)); // no more than 5 sec 159 sleep += incr; 160 } catch (InterruptedException ie) { 161 // ignore and retry 162 } 163 startTime = isAlive0(pid); // recheck if it is alive 164 if (startTime > 0 && origStart > 0 && startTime != origStart) { 165 // start time changed (and is not zero), pid is not the same process 166 break; 167 } 168 } 169 exitValue = 0; 170 } 171 newCompletion.complete(exitValue); 172 // remove from cache afterwards 173 completions.remove(pid, newCompletion); 174 } finally { 175 // Restore thread name 176 t.setName(threadName); 177 } 178 } 179 }); 180 } 181 } 182 return completion; 183 } 184 185 @Override 186 public CompletableFuture<ProcessHandle> onExit() { 187 if (this.equals(current)) { 188 throw new IllegalStateException("onExit for current process not allowed"); 189 } 190 191 return ProcessHandleImpl.completion(pid(), false) 192 .handleAsync((exitStatus, unusedThrowable) -> this); 193 } 194 195 /** 196 * Wait for the process to exit, return the value. 197 * Conditionally reap the value if requested 198 * @param pid the processId 199 * @param reapvalue if true, the value is retrieved, 200 * else return the value and leave the process waitable 201 * 202 * @return the value or -1 if an error occurs 203 */ 204 private static native int waitForProcessExit0(long pid, boolean reapvalue); 205 206 /** 207 * The pid of this ProcessHandle. 208 */ 209 private final long pid; 210 211 /** 212 * The start time of this process. 213 * If STARTTIME_ANY, the start time of the process is not available from the os. 214 * If greater than zero, the start time of the process. 215 */ 216 private final long startTime; 217 218 /* The start time should match any value. 219 * Typically, this is because the OS can not supply it. 220 * The process is known to exist but not the exact start time. 221 */ 222 private static final long STARTTIME_ANY = 0L; 223 224 /* The start time of a Process that does not exist. */ 225 private static final long STARTTIME_PROCESS_UNKNOWN = -1; 226 227 /** 228 * Private constructor. Instances are created by the {@code get(long)} factory. 229 * @param pid the pid for this instance 230 */ 231 private ProcessHandleImpl(long pid, long startTime) { 232 this.pid = pid; 233 this.startTime = startTime; 234 } 235 236 /** 237 * Returns a ProcessHandle for an existing native process. 238 * 239 * @param pid the native process identifier 240 * @return The ProcessHandle for the pid if the process is alive; 241 * or {@code null} if the process ID does not exist in the native system. 242 */ 243 static Optional<ProcessHandle> get(long pid) { 244 long start = isAlive0(pid); 245 return (start >= 0) 246 ? Optional.of(new ProcessHandleImpl(pid, start)) 247 : Optional.empty(); 248 } 249 250 /** 251 * Returns a ProcessHandle for an existing native process known to be alive. 252 * The startTime of the process is retrieved and stored in the ProcessHandle. 253 * It does not perform a security check since it is called from ProcessImpl. 254 * @param pid of the known to exist process 255 * @return a ProcessHandle corresponding to an existing Process instance 256 */ 257 static ProcessHandleImpl getInternal(long pid) { 258 return new ProcessHandleImpl(pid, isAlive0(pid)); 259 } 260 261 /** 262 * Returns the native process ID. 263 * A {@code long} is used to be able to fit the system specific binary values 264 * for the process. 265 * 266 * @return the native process ID 267 */ 268 @Override 269 public long pid() { 270 return pid; 271 } 272 273 /** 274 * Returns the ProcessHandle for the current native process. 275 * 276 * @return The ProcessHandle for the OS process. 277 */ 278 public static ProcessHandleImpl current() { 279 return current; 280 } 281 282 /** 283 * Return the pid of the current process. 284 * 285 * @return the pid of the current process 286 */ 287 private static native long getCurrentPid0(); 288 289 /** 290 * Returns a ProcessHandle for the parent process. 291 * 292 * @return a ProcessHandle of the parent process; {@code null} is returned 293 * if the child process does not have a parent 294 */ 295 public Optional<ProcessHandle> parent() { 296 long ppid = parent0(pid, startTime); 297 if (ppid <= 0) { 298 return Optional.empty(); 299 } 300 return get(ppid); 301 } 302 303 /** 304 * Returns the parent of the native pid argument. 305 * 306 * @param pid the process id 307 * @param startTime the startTime of the process 308 * @return the parent of the native pid; if any, otherwise -1 309 */ 310 private static native long parent0(long pid, long startTime); 311 312 /** 313 * Returns the number of pids filled in to the array. 314 * @param pid if {@code pid} equals zero, then all known processes are returned; 315 * otherwise only direct child process pids are returned 316 * @param pids an allocated long array to receive the pids 317 * @param ppids an allocated long array to receive the parent pids; may be null 318 * @param starttimes an allocated long array to receive the child start times; may be null 319 * @return if greater than or equal to zero is the number of pids in the array; 320 * if greater than the length of the arrays, the arrays are too small 321 */ 322 private static native int getProcessPids0(long pid, long[] pids, 323 long[] ppids, long[] starttimes); 324 325 /** 326 * Destroy the process for this ProcessHandle. 327 * The native code checks the start time before sending the termination request. 328 * 329 * @param force {@code true} if the process should be terminated forcibly; 330 * else {@code false} for a normal termination 331 */ 332 boolean destroyProcess(boolean force) { 333 if (this.equals(current)) { 334 throw new IllegalStateException("destroy of current process not allowed"); 335 } 336 return destroy0(pid, startTime, force); 337 } 338 339 /** 340 * Signal the process to terminate. 341 * The process is signaled only if its start time matches the known start time. 342 * 343 * @param pid process id to kill 344 * @param startTime the start time of the process 345 * @param forcibly true to forcibly terminate (SIGKILL vs SIGTERM) 346 * @return true if the process was signaled without error; false otherwise 347 */ 348 private static native boolean destroy0(long pid, long startTime, boolean forcibly); 349 350 @Override 351 public boolean destroy() { 352 return destroyProcess(false); 353 } 354 355 @Override 356 public boolean destroyForcibly() { 357 return destroyProcess(true); 358 } 359 360 361 @Override 362 public boolean supportsNormalTermination() { 363 return ProcessImpl.SUPPORTS_NORMAL_TERMINATION; 364 } 365 366 /** 367 * Tests whether the process represented by this {@code ProcessHandle} is alive. 368 * 369 * @return {@code true} if the process represented by this 370 * {@code ProcessHandle} object has not yet terminated. 371 * @since 9 372 */ 373 @Override 374 public boolean isAlive() { 375 long start = isAlive0(pid); 376 return (start >= 0 && (start == startTime || start == 0 || startTime == 0)); 377 } 378 379 /** 380 * Returns the process start time depending on whether the pid is alive. 381 * This must not reap the exitValue. 382 * 383 * @param pid the pid to check 384 * @return the start time in milliseconds since 1970, 385 * 0 if the start time cannot be determined, 386 * -1 if the pid does not exist. 387 */ 388 private static native long isAlive0(long pid); 389 390 @Override 391 public Stream<ProcessHandle> children() { 392 // The native OS code selects based on matching the requested parent pid. 393 // If the original parent exits, the pid may have been re-used for 394 // this newer process. 395 // Processes started by the original parent (now dead) will all have 396 // start times less than the start of this newer parent. 397 // Processes started by this newer parent will have start times equal 398 // or after this parent. 399 return children(pid).filter(ph -> startTime <= ((ProcessHandleImpl)ph).startTime); 400 } 401 402 /** 403 * Returns a Stream of the children of a process or all processes. 404 * 405 * @param pid the pid of the process for which to find the children; 406 * 0 for all processes 407 * @return a stream of ProcessHandles 408 */ 409 static Stream<ProcessHandle> children(long pid) { 410 int size = 100; 411 long[] childpids = null; 412 long[] starttimes = null; 413 while (childpids == null || size > childpids.length) { 414 childpids = new long[size]; 415 starttimes = new long[size]; 416 size = getProcessPids0(pid, childpids, null, starttimes); 417 } 418 419 final long[] cpids = childpids; 420 final long[] stimes = starttimes; 421 return IntStream.range(0, size).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i])); 422 } 423 424 @Override 425 public Stream<ProcessHandle> descendants() { 426 int size = 100; 427 long[] pids = null; 428 long[] ppids = null; 429 long[] starttimes = null; 430 while (pids == null || size > pids.length) { 431 pids = new long[size]; 432 ppids = new long[size]; 433 starttimes = new long[size]; 434 size = getProcessPids0(0, pids, ppids, starttimes); 435 } 436 437 int next = 0; // index of next process to check 438 int count = -1; // count of subprocesses scanned 439 long ppid = pid; // start looking for this parent 440 long ppStart = 0; 441 // Find the start time of the parent 442 for (int i = 0; i < size; i++) { 443 if (pids[i] == ppid) { 444 ppStart = starttimes[i]; 445 break; 446 } 447 } 448 do { 449 // Scan from next to size looking for ppid with child start time 450 // the same or later than the parent. 451 // If found, exchange it with index next 452 for (int i = next; i < size; i++) { 453 if (ppids[i] == ppid && 454 ppStart <= starttimes[i]) { 455 swap(pids, i, next); 456 swap(ppids, i, next); 457 swap(starttimes, i, next); 458 next++; 459 } 460 } 461 ppid = pids[++count]; // pick up the next pid to scan for 462 ppStart = starttimes[count]; // and its start time 463 } while (count < next); 464 465 final long[] cpids = pids; 466 final long[] stimes = starttimes; 467 return IntStream.range(0, count).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i])); 468 } 469 470 // Swap two elements in an array 471 private static void swap(long[] array, int x, int y) { 472 long v = array[x]; 473 array[x] = array[y]; 474 array[y] = v; 475 } 476 477 @Override 478 public ProcessHandle.Info info() { 479 return ProcessHandleImpl.Info.info(pid, startTime); 480 } 481 482 @Override 483 public int compareTo(ProcessHandle other) { 484 return Long.compare(pid, ((ProcessHandleImpl) other).pid); 485 } 486 487 @Override 488 public String toString() { 489 return Long.toString(pid); 490 } 491 492 @Override 493 public int hashCode() { 494 return Long.hashCode(pid); 495 } 496 497 @Override 498 public boolean equals(Object obj) { 499 if (this == obj) { 500 return true; 501 } 502 return (obj instanceof ProcessHandleImpl other) 503 && (pid == other.pid) 504 && (startTime == other.startTime || startTime == 0 || other.startTime == 0); 505 } 506 507 /** 508 * Implementation of ProcessHandle.Info. 509 * Information snapshot about a process. 510 * The attributes of a process vary by operating system and are not available 511 * in all implementations. Additionally, information about other processes 512 * is limited by the operating system privileges of the process making the request. 513 * If a value is not available, either a {@code null} or {@code -1} is stored. 514 * The accessor methods return {@code null} if the value is not available. 515 */ 516 static class Info implements ProcessHandle.Info { 517 static { 518 initIDs(); 519 } 520 521 /** 522 * Initialization of JNI fieldIDs. 523 */ 524 private static native void initIDs(); 525 526 /** 527 * Fill in this Info instance with information about the native process. 528 * If values are not available the native code does not modify the field. 529 * @param pid of the native process 530 */ 531 private native void info0(long pid); 532 533 String command; 534 String commandLine; 535 String[] arguments; 536 long startTime; 537 long totalTime; 538 String user; 539 540 Info() { 541 command = null; 542 commandLine = null; 543 arguments = null; 544 startTime = -1L; 545 totalTime = -1L; 546 user = null; 547 } 548 549 /** 550 * Returns the Info object with the fields from the process. 551 * Whatever fields are provided by native are returned. 552 * If the startTime of the process does not match the provided 553 * startTime then an empty Info is returned. 554 * 555 * @param pid the native process identifier 556 * @param startTime the startTime of the process being queried 557 * @return ProcessHandle.Info non-null; individual fields may be null 558 * or -1 if not available. 559 */ 560 public static ProcessHandle.Info info(long pid, long startTime) { 561 Info info = new Info(); 562 info.info0(pid); 563 if (startTime != info.startTime) { 564 info.command = null; 565 info.arguments = null; 566 info.startTime = -1L; 567 info.totalTime = -1L; 568 info.user = null; 569 } 570 return info; 571 } 572 573 @Override 574 public Optional<String> command() { 575 return Optional.ofNullable(command); 576 } 577 578 @Override 579 public Optional<String> commandLine() { 580 if (command != null && arguments != null) { 581 return Optional.of(command + " " + String.join(" ", arguments)); 582 } else { 583 return Optional.ofNullable(commandLine); 584 } 585 } 586 587 @Override 588 public Optional<String[]> arguments() { 589 return Optional.ofNullable(arguments); 590 } 591 592 @Override 593 public Optional<Instant> startInstant() { 594 return (startTime > 0) 595 ? Optional.of(Instant.ofEpochMilli(startTime)) 596 : Optional.empty(); 597 } 598 599 @Override 600 public Optional<Duration> totalCpuDuration() { 601 return (totalTime != -1) 602 ? Optional.of(Duration.ofNanos(totalTime)) 603 : Optional.empty(); 604 } 605 606 @Override 607 public Optional<String> user() { 608 return Optional.ofNullable(user); 609 } 610 611 @Override 612 public String toString() { 613 StringBuilder sb = new StringBuilder(60); 614 sb.append('['); 615 if (user != null) { 616 sb.append("user: "); 617 sb.append(user()); 618 } 619 if (command != null) { 620 if (sb.length() > 1) sb.append(", "); 621 sb.append("cmd: "); 622 sb.append(command); 623 } 624 if (arguments != null && arguments.length > 0) { 625 if (sb.length() > 1) sb.append(", "); 626 sb.append("args: "); 627 sb.append(Arrays.toString(arguments)); 628 } 629 if (commandLine != null) { 630 if (sb.length() > 1) sb.append(", "); 631 sb.append("cmdLine: "); 632 sb.append(commandLine); 633 } 634 if (startTime > 0) { 635 if (sb.length() > 1) sb.append(", "); 636 sb.append("startTime: "); 637 sb.append(startInstant()); 638 } 639 if (totalTime != -1) { 640 if (sb.length() > 1) sb.append(", "); 641 sb.append("totalTime: "); 642 sb.append(totalCpuDuration().toString()); 643 } 644 sb.append(']'); 645 return sb.toString(); 646 } 647 } 648 }