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