60 * @author Martin Buchholz
61 * @author Volker Simonis (ported to AIX)
62 * @since 1.5
63 */
64 final class ProcessImpl extends Process {
65 private static final JavaIOFileDescriptorAccess fdAccess
66 = SharedSecrets.getJavaIOFileDescriptorAccess();
67
68 // Linux platforms support a normal (non-forcible) kill signal.
69 static final boolean SUPPORTS_NORMAL_TERMINATION = true;
70
71 private final int pid;
72 private final ProcessHandleImpl processHandle;
73 private int exitcode;
74 private boolean hasExited;
75
76 private /* final */ OutputStream stdin;
77 private /* final */ InputStream stdout;
78 private /* final */ InputStream stderr;
79
80 // only used on Solaris
81 private /* final */ DeferredCloseInputStream stdout_inner_stream;
82
83 private static enum LaunchMechanism {
84 // order IS important!
85 FORK,
86 POSIX_SPAWN,
87 VFORK
88 }
89
90 private static enum Platform {
91
92 LINUX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.VFORK, LaunchMechanism.FORK),
93
94 BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK),
95
96 SOLARIS(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK),
97
98 AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK);
99
100 final LaunchMechanism defaultLaunchMechanism;
101 final Set<LaunchMechanism> validLaunchMechanisms;
102
103 Platform(LaunchMechanism ... launchMechanisms) {
104 this.defaultLaunchMechanism = launchMechanisms[0];
105 this.validLaunchMechanisms =
106 EnumSet.copyOf(Arrays.asList(launchMechanisms));
107 }
108
109 LaunchMechanism launchMechanism() {
110 return AccessController.doPrivileged(
111 (PrivilegedAction<LaunchMechanism>) () -> {
112 String s = System.getProperty(
113 "jdk.lang.Process.launchMechanism");
114 LaunchMechanism lm;
115 if (s == null) {
116 lm = defaultLaunchMechanism;
117 s = lm.name().toLowerCase(Locale.ENGLISH);
122 } catch (IllegalArgumentException e) {
123 lm = null;
124 }
125 }
126 if (lm == null || !validLaunchMechanisms.contains(lm)) {
127 throw new Error(
128 s + " is not a supported " +
129 "process launch mechanism on this platform."
130 );
131 }
132 return lm;
133 }
134 );
135 }
136
137 static Platform get() {
138 String osName = GetPropertyAction.privilegedGetProperty("os.name");
139
140 if (osName.equals("Linux")) { return LINUX; }
141 if (osName.contains("OS X")) { return BSD; }
142 if (osName.equals("SunOS")) { return SOLARIS; }
143 if (osName.equals("AIX")) { return AIX; }
144
145 throw new Error(osName + " is not a supported OS platform.");
146 }
147 }
148
149 private static final Platform platform = Platform.get();
150 private static final LaunchMechanism launchMechanism = platform.launchMechanism();
151 private static final byte[] helperpath = toCString(StaticProperty.javaHome() + "/lib/jspawnhelper");
152
153 private static byte[] toCString(String s) {
154 if (s == null)
155 return null;
156 byte[] bytes = s.getBytes();
157 byte[] result = new byte[bytes.length + 1];
158 System.arraycopy(bytes, 0,
159 result, 0,
160 bytes.length);
161 result[result.length-1] = (byte)0;
162 return result;
368 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
369 synchronized (this) {
370 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
371 this.hasExited = true;
372 this.notifyAll();
373 }
374
375 if (stdout instanceof ProcessPipeInputStream)
376 ((ProcessPipeInputStream) stdout).processExited();
377
378 if (stderr instanceof ProcessPipeInputStream)
379 ((ProcessPipeInputStream) stderr).processExited();
380
381 if (stdin instanceof ProcessPipeOutputStream)
382 ((ProcessPipeOutputStream) stdin).processExited();
383
384 return null;
385 });
386 break;
387
388 case SOLARIS:
389 stdin = (fds[0] == -1) ?
390 ProcessBuilder.NullOutputStream.INSTANCE :
391 new BufferedOutputStream(
392 new FileOutputStream(newFileDescriptor(fds[0])));
393
394 stdout = (fds[1] == -1 || forceNullOutputStream) ?
395 ProcessBuilder.NullInputStream.INSTANCE :
396 new BufferedInputStream(
397 stdout_inner_stream =
398 new DeferredCloseInputStream(
399 newFileDescriptor(fds[1])));
400
401 stderr = (fds[2] == -1) ?
402 ProcessBuilder.NullInputStream.INSTANCE :
403 new DeferredCloseInputStream(newFileDescriptor(fds[2]));
404
405 /*
406 * For each subprocess forked a corresponding reaper task
407 * is submitted. That task is the only thread which waits
408 * for the subprocess to terminate and it doesn't hold any
409 * locks while doing so. This design allows waitFor() and
410 * exitStatus() to be safely executed in parallel (and they
411 * need no native code).
412 */
413 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
414 synchronized (this) {
415 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
416 this.hasExited = true;
417 this.notifyAll();
418 }
419 return null;
420 });
421 break;
422
423 case AIX:
424 stdin = (fds[0] == -1) ?
425 ProcessBuilder.NullOutputStream.INSTANCE :
426 new ProcessPipeOutputStream(fds[0]);
427
428 stdout = (fds[1] == -1 || forceNullOutputStream) ?
429 ProcessBuilder.NullInputStream.INSTANCE :
430 new DeferredCloseProcessPipeInputStream(fds[1]);
431
432 stderr = (fds[2] == -1) ?
433 ProcessBuilder.NullInputStream.INSTANCE :
434 new DeferredCloseProcessPipeInputStream(fds[2]);
435
436 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
437 synchronized (this) {
438 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
439 this.hasExited = true;
440 this.notifyAll();
441 }
442
503 }
504
505 private void destroy(boolean force) {
506 switch (platform) {
507 case LINUX:
508 case BSD:
509 case AIX:
510 // There is a risk that pid will be recycled, causing us to
511 // kill the wrong process! So we only terminate processes
512 // that appear to still be running. Even with this check,
513 // there is an unavoidable race condition here, but the window
514 // is very small, and OSes try hard to not recycle pids too
515 // soon, so this is quite safe.
516 synchronized (this) {
517 if (!hasExited)
518 processHandle.destroyProcess(force);
519 }
520 try { stdin.close(); } catch (IOException ignored) {}
521 try { stdout.close(); } catch (IOException ignored) {}
522 try { stderr.close(); } catch (IOException ignored) {}
523 break;
524
525 case SOLARIS:
526 // There is a risk that pid will be recycled, causing us to
527 // kill the wrong process! So we only terminate processes
528 // that appear to still be running. Even with this check,
529 // there is an unavoidable race condition here, but the window
530 // is very small, and OSes try hard to not recycle pids too
531 // soon, so this is quite safe.
532 synchronized (this) {
533 if (!hasExited)
534 processHandle.destroyProcess(force);
535 try {
536 stdin.close();
537 if (stdout_inner_stream != null)
538 stdout_inner_stream.closeDeferred(stdout);
539 if (stderr instanceof DeferredCloseInputStream)
540 ((DeferredCloseInputStream) stderr)
541 .closeDeferred(stderr);
542 } catch (IOException e) {
543 // ignore
544 }
545 }
546 break;
547
548 default: throw new AssertionError("Unsupported platform: " + platform);
549 }
550 }
551
552 @Override
553 public CompletableFuture<Process> onExit() {
554 return ProcessHandleImpl.completion(pid, false)
555 .handleAsync((unusedExitStatus, unusedThrowable) -> {
556 boolean interrupted = false;
557 while (true) {
558 // Ensure that the concurrent task setting the exit status has completed
559 try {
560 waitFor();
561 break;
562 } catch (InterruptedException ie) {
563 interrupted = true;
564 }
565 }
|
60 * @author Martin Buchholz
61 * @author Volker Simonis (ported to AIX)
62 * @since 1.5
63 */
64 final class ProcessImpl extends Process {
65 private static final JavaIOFileDescriptorAccess fdAccess
66 = SharedSecrets.getJavaIOFileDescriptorAccess();
67
68 // Linux platforms support a normal (non-forcible) kill signal.
69 static final boolean SUPPORTS_NORMAL_TERMINATION = true;
70
71 private final int pid;
72 private final ProcessHandleImpl processHandle;
73 private int exitcode;
74 private boolean hasExited;
75
76 private /* final */ OutputStream stdin;
77 private /* final */ InputStream stdout;
78 private /* final */ InputStream stderr;
79
80 private static enum LaunchMechanism {
81 // order IS important!
82 FORK,
83 POSIX_SPAWN,
84 VFORK
85 }
86
87 private static enum Platform {
88
89 LINUX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.VFORK, LaunchMechanism.FORK),
90
91 BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK),
92
93 AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK);
94
95 final LaunchMechanism defaultLaunchMechanism;
96 final Set<LaunchMechanism> validLaunchMechanisms;
97
98 Platform(LaunchMechanism ... launchMechanisms) {
99 this.defaultLaunchMechanism = launchMechanisms[0];
100 this.validLaunchMechanisms =
101 EnumSet.copyOf(Arrays.asList(launchMechanisms));
102 }
103
104 LaunchMechanism launchMechanism() {
105 return AccessController.doPrivileged(
106 (PrivilegedAction<LaunchMechanism>) () -> {
107 String s = System.getProperty(
108 "jdk.lang.Process.launchMechanism");
109 LaunchMechanism lm;
110 if (s == null) {
111 lm = defaultLaunchMechanism;
112 s = lm.name().toLowerCase(Locale.ENGLISH);
117 } catch (IllegalArgumentException e) {
118 lm = null;
119 }
120 }
121 if (lm == null || !validLaunchMechanisms.contains(lm)) {
122 throw new Error(
123 s + " is not a supported " +
124 "process launch mechanism on this platform."
125 );
126 }
127 return lm;
128 }
129 );
130 }
131
132 static Platform get() {
133 String osName = GetPropertyAction.privilegedGetProperty("os.name");
134
135 if (osName.equals("Linux")) { return LINUX; }
136 if (osName.contains("OS X")) { return BSD; }
137 if (osName.equals("AIX")) { return AIX; }
138
139 throw new Error(osName + " is not a supported OS platform.");
140 }
141 }
142
143 private static final Platform platform = Platform.get();
144 private static final LaunchMechanism launchMechanism = platform.launchMechanism();
145 private static final byte[] helperpath = toCString(StaticProperty.javaHome() + "/lib/jspawnhelper");
146
147 private static byte[] toCString(String s) {
148 if (s == null)
149 return null;
150 byte[] bytes = s.getBytes();
151 byte[] result = new byte[bytes.length + 1];
152 System.arraycopy(bytes, 0,
153 result, 0,
154 bytes.length);
155 result[result.length-1] = (byte)0;
156 return result;
362 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
363 synchronized (this) {
364 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
365 this.hasExited = true;
366 this.notifyAll();
367 }
368
369 if (stdout instanceof ProcessPipeInputStream)
370 ((ProcessPipeInputStream) stdout).processExited();
371
372 if (stderr instanceof ProcessPipeInputStream)
373 ((ProcessPipeInputStream) stderr).processExited();
374
375 if (stdin instanceof ProcessPipeOutputStream)
376 ((ProcessPipeOutputStream) stdin).processExited();
377
378 return null;
379 });
380 break;
381
382 case AIX:
383 stdin = (fds[0] == -1) ?
384 ProcessBuilder.NullOutputStream.INSTANCE :
385 new ProcessPipeOutputStream(fds[0]);
386
387 stdout = (fds[1] == -1 || forceNullOutputStream) ?
388 ProcessBuilder.NullInputStream.INSTANCE :
389 new DeferredCloseProcessPipeInputStream(fds[1]);
390
391 stderr = (fds[2] == -1) ?
392 ProcessBuilder.NullInputStream.INSTANCE :
393 new DeferredCloseProcessPipeInputStream(fds[2]);
394
395 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
396 synchronized (this) {
397 this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
398 this.hasExited = true;
399 this.notifyAll();
400 }
401
462 }
463
464 private void destroy(boolean force) {
465 switch (platform) {
466 case LINUX:
467 case BSD:
468 case AIX:
469 // There is a risk that pid will be recycled, causing us to
470 // kill the wrong process! So we only terminate processes
471 // that appear to still be running. Even with this check,
472 // there is an unavoidable race condition here, but the window
473 // is very small, and OSes try hard to not recycle pids too
474 // soon, so this is quite safe.
475 synchronized (this) {
476 if (!hasExited)
477 processHandle.destroyProcess(force);
478 }
479 try { stdin.close(); } catch (IOException ignored) {}
480 try { stdout.close(); } catch (IOException ignored) {}
481 try { stderr.close(); } catch (IOException ignored) {}
482 break;
483
484 default: throw new AssertionError("Unsupported platform: " + platform);
485 }
486 }
487
488 @Override
489 public CompletableFuture<Process> onExit() {
490 return ProcessHandleImpl.completion(pid, false)
491 .handleAsync((unusedExitStatus, unusedThrowable) -> {
492 boolean interrupted = false;
493 while (true) {
494 // Ensure that the concurrent task setting the exit status has completed
495 try {
496 waitFor();
497 break;
498 } catch (InterruptedException ie) {
499 interrupted = true;
500 }
501 }
|