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