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 : (4 * 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 }