source: main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java@ 33403

Last change on this file since 33403 was 33403, checked in by ak19, 5 years ago

Mistake to do with launchdir in SafeProcess: if the environment for the process to be executed is null, it does not follow that the launchdir for that process would be null. Ought to handle the params separately.

File size: 66.8 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Author: Greenstone Digital Library, University of Waikato, except where indicated
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer.util;
28
29import java.io.BufferedReader;
30import java.io.BufferedWriter;
31import java.io.Closeable;
32import java.io.File;
33import java.io.InputStream;
34import java.io.InputStreamReader;
35import java.io.IOException;
36import java.io.OutputStream;
37import java.io.OutputStreamWriter;
38import java.net.Socket;
39import java.util.Arrays;
40import java.util.Scanner;
41import java.util.Stack;
42import javax.swing.SwingUtilities;
43
44
45import com.sun.jna.*;
46import com.sun.jna.platform.win32.Kernel32;
47import com.sun.jna.platform.win32.WinNT;
48
49import java.lang.reflect.Field;
50
51import org.apache.log4j.*;
52
53import org.greenstone.gatherer.DebugStream;
54
55// Use this class to run a Java Process. It follows the good and safe practices at
56// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
57// to avoid blocking problems that can arise from a Process' input and output streams.
58
59// On Windows, Perl could launch processes as proper ProcessTrees: http://search.cpan.org/~gsar/libwin32-0.191/
60// Then killing the root process will kill child processes naturally.
61
62public class SafeProcess {
63 public static int DEBUG = 0;
64
65 public static final int STDERR = 0;
66 public static final int STDOUT = 1;
67 public static final int STDIN = 2;
68 // can't make this variable final and init in a static block, because it needs to use other SafeProcess static methods which rely on this in turn:
69 public static String WIN_KILL_CMD;
70
71 /**
72 * Boolean interruptible is used to mark any sections of blocking code that should not be interrupted
73 * with an InterruptedExceptions. At present only the cancelRunningProcess() attempts to do such a thing
74 * and avoids doing so when interruptible is false.
75 * Note that interruptible is also used as a lock, so remember to synchronize on it when using it!
76 */
77 public Boolean interruptible = Boolean.TRUE;
78
79 // charset for reading process stderr and stdout streams
80 //public static final String UTF8 = "UTF-8";
81
82 ///static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
83
84 // input to SafeProcess and initialising it
85 private String command = null;
86 private String[] command_args = null;
87 private String[] envp = null;
88 private File dir = null;
89 private String inputStr = null;
90 private Process process = null;
91 private boolean forciblyTerminateProcess = false;
92
93 /** a ref to the thread in which the Process is being executed (the thread wherein Runtime.exec() is called) */
94 private Thread theProcessThread = null;
95
96 // output from running SafeProcess.runProcess()
97 private String outputStr = "";
98 private String errorStr = "";
99 private int exitValue = -1;
100 //private String charset = null;
101
102 // allow callers to process exceptions of the main process thread if they want
103 private ExceptionHandler exceptionHandler = null;
104 /** allow callers to implement hooks that get called during the main phases of the internal
105 * process' life cycle, such as before and after process.destroy() gets called
106 */
107 private MainProcessHandler mainHandler = null;
108
109 // whether std/err output should be split at new lines
110 private boolean splitStdOutputNewLines = false;
111 private boolean splitStdErrorNewLines = false;
112
113 // call one of these constructors
114
115 // cmd args version
116 public SafeProcess(String[] cmd_args)
117 {
118 command_args = cmd_args;
119 }
120
121 // cmd string version
122 public SafeProcess(String cmdStr)
123 {
124 command = cmdStr;
125 }
126
127 // cmd args with env version, launchDir can be null.
128 public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
129 {
130 command_args = cmd_args;
131 envp = envparams;
132 dir = launchDir;
133 }
134
135 // The important methods:
136 // to get the output from std err and std out streams after the process has been run
137 public String getStdOutput() { return outputStr; }
138 public String getStdError() { return errorStr; }
139 public int getExitValue() { return exitValue; }
140
141 //public void setStreamCharSet(String charset) { this.charset = charset; }
142
143 // set any string to send as input to the process spawned by SafeProcess
144 public void setInputString(String sendStr) {
145 inputStr = sendStr;
146 }
147
148 // register a SafeProcess ExceptionHandler whose gotException() method will
149 // get called for each exception encountered
150 public void setExceptionHandler(ExceptionHandler exception_handler) {
151 exceptionHandler = exception_handler;
152 }
153
154 /** to set a handler that will handle the main (SafeProcess) thread,
155 * implementing the hooks that will get called during the internal process' life cycle,
156 * such as before and after process.destroy() is called */
157 public void setMainHandler(MainProcessHandler handler) {
158 this.mainHandler = handler;
159 }
160
161 // set if you want the std output or err output to have \n at each newline read from the stream
162 public void setSplitStdOutputNewLines(boolean split) {
163 splitStdOutputNewLines = split;
164 }
165 public void setSplitStdErrorNewLines(boolean split) {
166 splitStdErrorNewLines = split;
167 }
168
169
170 /*
171 public boolean canInterrupt() {
172 boolean canInterrupt;
173 synchronized(interruptible) {
174 canInterrupt = interruptible.booleanValue();
175 }
176 return canInterrupt;
177 }
178 */
179
180 /**
181 * If calling this method from a GUI thread when the SafeProcess is in the uninterruptible
182 * phase of natural termination, then this method will return immediately before that phase
183 * has ended. To force the caller to wait until the natural termination phase has ended,
184 * call the other variant of this method with param forceWaitUntilInterruptible set to true:
185 * cancelRunningProcess(true).
186 * @return false if process has already terminated or if it was already terminating when
187 * cancel was called. In such cases no interrupt is sent.
188 * This method returns a boolean that you can call sentInterrupt.
189 */
190 public boolean cancelRunningProcess() {
191
192 boolean forceWaitUntilInterruptible = true;
193 // by default, event dispatch threads may not want to wait for any joins() taking
194 // place at the time of cancel to be completed.
195 // So don't wait until the SafeProcess becomes interruptible
196 return this.cancelRunningProcess(!forceWaitUntilInterruptible);
197 }
198
199 /**
200 * Call this method when you want to prematurely and safely terminate any process
201 * that SafeProcess may be running.
202 * You may want to implement the SafeProcess.MainHandler interface to write code
203 * for any hooks that will get called during the process' life cycle.
204 * @param forceWaitUntilInterruptible if set to true by a calling GUI thread, then this method
205 * won't return until the running process is interruptible, even if SafeProcess is in the phase
206 * of naturally terminating, upon which no interrupts will be sent to the SafeProcess
207 * thread anyway. The join() calls within SafeProcess.runProcess() are blocking calls and are
208 * therefore sensitive to InterruptedExceptions. But the join() calls are part of the cleanup
209 * phase and shouldn't be interrupted, and nothing thereafter can be interrupted anyway.
210 * This method tends to be called with the param set to false. In that case, if the SafeProcess
211 * is in an uninterruptible phase (as can then only happen during clean up of natural
212 * termination) then a calling GUI thread will just return immediately. Meaning, the GUI thread
213 * won't wait for the SafeProcess thread to finish cleaning up.
214 * @return false if process has already terminated or if it was already terminating when
215 * cancel was called. In such cases no interrupt is sent.
216 * This method returns a boolean that you can call sentInterrupt.
217 */
218 public synchronized boolean cancelRunningProcess(boolean forceWaitUntilInterruptible) {
219 // on interrupt:
220 // - forciblyTerminate will be changed to true if the interrupt came in when the process was
221 // still running (and so before the process' streams were being joined)
222 // - and forciblyTerminate will still remain false if the interrupt happens when the process'
223 // streams are being/about to be joined (hence after the process naturally terminated).
224 // So we don't touch the value of this.forciblyTerminate here.
225 // The value of forciblyTerminate determines whether Process.destroy() and its associated before
226 // and after handlers are called or not: we don't bother destroying the process if it came to
227 // a natural end.
228
229 // no process to interrupt, so we're done
230 if(this.process == null) {
231 log("@@@ No Java process to interrupt.");
232 return false;
233 }
234
235 boolean sentInterrupt = false;
236
237 // can't interrupt when SafeProcess is joining (cleanly terminating) worker threads
238 // have to wait until afterward
239 if (interruptible) {
240 // either way, we can now interrupt the thread that SafeProcess.runProcess() is running in
241 if(this.theProcessThread != null) { // we stored a ref to the main thread that's to be interrupted
242 this.theProcessThread.interrupt();
243 log("@@@ Successfully sent interrupt to process.");
244 sentInterrupt = true;
245 }
246 }
247 else { // wait for join()s to finish, if we've been asked to wait
248
249 // During and after joining(), there's no need to interrupt any more anyway: no calls
250 // subsequent to joins() block, so everything thereafter is insensitive to InterruptedExceptions
251 // and everything from the joins() onward are cleanup on natural process termination, so no
252 // interrupt is needed after the joins().
253 // Still, even if the caller is a GUI thread, they can decide if they want to wait until this
254 // method's end: until the SafeProcess becomes interruptible again
255
256 if(!forceWaitUntilInterruptible && SwingUtilities.isEventDispatchThread()) {
257 log("#### Event Dispatch thread, returning");
258 return false;
259 }
260
261 while(!interruptible) {
262
263 log("######### Waiting for process to become interruptible...");
264
265 // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
266 // wait will release lock on this object, and regain it when loop condition interruptible is true
267 try {
268 this.wait(); // can't interrupt when SafeProcess is joining (cleanly terminating) worker threads, so wait
269 } catch(Exception e) {
270 log("@@@ Interrupted exception while waiting for SafeProcess' worker threads to finish joining on cancelling process");
271 }
272 }
273
274 // now the process is sure to have ended as the worker threads would have been joined
275 }
276
277 return sentInterrupt;
278 }
279
280
281 // In future, think of changing the method doRuntimeExec() over to using ProcessBuilder
282 // instead of Runtime.exec(). ProcessBuilder seems to have been introduced from Java 5.
283 // https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html
284 // See also https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
285 // which suggests using Apache Common Exec to launch processes and says what will be forthcoming in Java 9
286
287 private Process doRuntimeExec() throws IOException {
288 Process prcs = null;
289 Runtime rt = Runtime.getRuntime();
290
291 if(this.command != null) {
292 log("SafeProcess running: " + command);
293 prcs = rt.exec(this.command);
294 }
295 else { // at least command_args must be set now
296
297 // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
298 //log("SafeProcess running:" + Arrays.toString(command_args));
299 StringBuffer cmdDisplay = new StringBuffer();
300 for(int i = 0; i < command_args.length; i++) {
301 cmdDisplay.append(" ").append(command_args[i]);
302 }
303 log("SafeProcess running: [" + cmdDisplay + "]");
304 cmdDisplay = null; // let the GC have it
305
306
307 if(this.envp == null && this.dir == null) {
308 prcs = rt.exec(this.command_args);
309 } else { // launch process using cmd str with env params and/or exec-dir
310
311 //if(this.envp != null) {
312 //log("Environment: ");
313 //for(int i = 0; i < envp.length; i++) {
314 // log("\t" + envp[i]);
315 //}
316 //}
317
318 if(this.dir == null) {
319 //log("\twith: " + Arrays.toString(this.envp));
320 prcs = rt.exec(this.command_args, this.envp);
321 } else {
322 //log("\tfrom directory: " + this.dir);
323 //log("\twith: " + Arrays.toString(this.envp));
324 prcs = rt.exec(this.command_args, this.envp, this.dir);
325 }
326 }
327 }
328
329 this.theProcessThread = Thread.currentThread(); // store a ref to the thread wherein the Process is being run
330 return prcs;
331 }
332
333 // Copied from gli's gui/FormatConversionDialog.java
334 private int waitForWithStreams(SafeProcess.OutputStreamGobbler inputGobbler,
335 SafeProcess.InputStreamGobbler outputGobbler,
336 SafeProcess.InputStreamGobbler errorGobbler)
337 throws IOException, InterruptedException
338 {
339 // kick off the stream gobblers
340 inputGobbler.start();
341 errorGobbler.start();
342 outputGobbler.start();
343
344 try {
345 this.exitValue = process.waitFor(); // can throw an InterruptedException if process was cancelled/prematurely terminated
346 } catch(InterruptedException ie) {
347 log("*** Process interrupted (InterruptedException). Expected to be a Cancel operation.");
348 // don't print stacktrace: an interrupt here is not an error, it's expected to be a cancel action
349 if(exceptionHandler != null) {
350 exceptionHandler.gotException(ie);
351 }
352
353 // propagate interrupts to worker threads here
354 // unless the interrupt emanated from any of them in any join(),
355 // which will be caught by the calling method's own catch on InterruptedException.
356 // Only if the thread that SafeProcess runs in was interrupted
357 // should we propagate the interrupt to the worker threads.
358 // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
359 // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
360 // But SafeProcess owns the worker threads, so it has every right to interrupt them
361 // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
362
363 // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
364 // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
365 // "Only code that implements a thread's interruption policy may swallow an interruption request. General-purpose task and library code should never swallow interruption requests."
366 // Does that mean that since this code implements this thread's interruption policy, it's ok
367 // to swallow the interrupt this time and not let it propagate by commenting out the next line?
368 //Thread.currentThread().interrupt(); // re-interrupt the thread
369
370 inputGobbler.interrupt();
371 errorGobbler.interrupt();
372 outputGobbler.interrupt();
373
374 // Since we have been cancelled (InterruptedException), or on any Exception, we need
375 // to forcibly terminate process eventually after the finally code first waits for each worker thread
376 // to die off. Don't set process=null until after we've forcibly terminated it if needs be.
377 this.forciblyTerminateProcess = true;
378
379 // even after the interrupts, we want to proceed to calling join() on all the worker threads
380 // in order to wait for each of them to die before attempting to destroy the process if it
381 // still hasn't terminated after all that.
382 } finally {
383
384 //log("Process exitValue: " + exitValue);
385 ///log("@@@@ Before join phase. Forcibly terminating: " + this.forciblyTerminateProcess);
386
387 // From the comments of
388 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
389 // To avoid running into nondeterministic failures to get the process output
390 // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
391 // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
392
393 // Wait for each of the threads to die, before attempting to destroy the process
394 // Any of these can throw InterruptedExceptions too
395 // and will be processed by the calling function's catch on InterruptedException.
396
397
398 // Thread.joins() below are blocking calls, same as Process.waitFor(), and a cancel action could
399 // send an interrupt during any Join: the InterruptedException ensuing will then break out of the
400 // joins() section. We don't want that to happen: by the time the joins() start happening, the
401 // actual process has finished in some way (naturally terminated or interrupted), and nothing
402 // should interrupt the joins() (nor ideally any potential p.destroy after that).
403 // So we mark the join() section as an un-interruptible section, and make anyone who's seeking
404 // to interrupt just then first wait for this Thread (in which SafeProcess runs) to become
405 // interruptible again. Thos actually assumes anything interruptible can still happen thereafter
406 // when in reality, none of the subsequent actions after the joins() block. So they nothing
407 // thereafter, which is the cleanup phase, will actually respond to an InterruptedException.
408
409
410 if(this.mainHandler != null) {
411 // this method can unset forcible termination flag
412 // if the process had already naturally terminated by this stage:
413 this.forciblyTerminateProcess = mainHandler.beforeWaitingForStreamsToEnd(this.forciblyTerminateProcess);
414 }
415
416 ///log("@@@@ After beforeJoin Handler. Forcibly terminating: " + this.forciblyTerminateProcess);
417
418 // Anyone could interrupt/cancel during waitFor() above,
419 // but no one should interrupt while the worker threads come to a clean close,
420 // so make anyone wanting to cancel the process at this stage wait()
421 // until we're done with the join()s:
422 synchronized(interruptible) {
423 interruptible = Boolean.FALSE;
424 }
425 //Thread.sleep(5000); // Uncomment to test this uninterruptible section, also comment out block checking for
426 // EventDispatchThread in cancelRunningProcess() and 2 calls to progress.enableCancelJob() in DownloadJob.java
427 outputGobbler.join();
428 errorGobbler.join();
429 inputGobbler.join();
430
431 synchronized(interruptible) {
432 interruptible = Boolean.TRUE;
433 }
434
435 ///log("@@@@ Join phase done...");
436
437 // notify any of those waiting to interrupt this thread, that they may feel free to do so again
438 // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
439 synchronized(this) {
440 this.notify();
441 }
442
443 // set the variables that the code which created a SafeProcess object may want to inspect
444 this.outputStr = outputGobbler.getOutput();
445 this.errorStr = errorGobbler.getOutput();
446
447 // call the after join()s hook
448 if(this.mainHandler != null) {
449 this.forciblyTerminateProcess = mainHandler.afterStreamsEnded(this.forciblyTerminateProcess);
450 }
451 }
452
453 // Don't return from finally, it's considered an abrupt completion and exceptions are lost, see
454 // http://stackoverflow.com/questions/18205493/can-we-use-return-in-finally-block
455 return this.exitValue;
456 }
457
458
459 public synchronized boolean processRunning() {
460 if(process == null) return false;
461 return SafeProcess.processRunning(this.process);
462 }
463
464 // Run a very basic process: with no reading from or writing to the Process' iostreams,
465 // this just execs the process and waits for it to return.
466 // Don't call this method but the zero-argument runProcess() instead if your process will
467 // output stuff to its stderr and stdout streams but you don't need to monitory these.
468 // Because, as per a comment in GLI's GS3ServerThread.java,
469 // in Java 6, it wil block if you don't handle a process' streams when the process is
470 // outputting something. (Java 7+ won't block if you don't bother to handle the output streams)
471 public int runBasicProcess() {
472 try {
473 this.forciblyTerminateProcess = true;
474
475 // 1. create the process
476 process = doRuntimeExec();
477 // 2. basic waitFor the process to finish
478 this.exitValue = process.waitFor();
479
480 // 3. if we managed to get here, the process naturally terminated (wasn't interrupted):
481 this.forciblyTerminateProcess = false;
482 } catch(IOException ioe) {
483
484 if(exceptionHandler != null) {
485 exceptionHandler.gotException(ioe);
486 } else {
487 log("IOException: " + ioe.getMessage(), ioe);
488 }
489 } catch(InterruptedException ie) {
490
491 if(exceptionHandler != null) {
492 exceptionHandler.gotException(ie);
493 } else { // Unexpected InterruptedException, so printstacktrace
494 log("Process InterruptedException: " + ie.getMessage(), ie);
495 }
496
497 Thread.currentThread().interrupt();
498 } finally {
499
500 cleanUp("SafeProcess.runBasicProcess");
501 }
502 return this.exitValue;
503 }
504
505 // Runs a process with default stream processing. Returns the exitValue
506 public int runProcess() {
507 return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
508 }
509
510 // Run a process with custom stream processing (any custom handlers passed in that are null
511 // will use the default stream processing).
512 // Returns the exitValue from running the Process
513 public int runProcess(CustomProcessHandler procInHandler,
514 CustomProcessHandler procOutHandler,
515 CustomProcessHandler procErrHandler)
516 {
517 SafeProcess.OutputStreamGobbler inputGobbler = null;
518 SafeProcess.InputStreamGobbler errorGobbler = null;
519 SafeProcess.InputStreamGobbler outputGobbler = null;
520
521 try {
522 this.forciblyTerminateProcess = false;
523
524 // 1. get the Process object
525 process = doRuntimeExec();
526
527
528 // 2. create the streamgobblers and set any specified handlers on them
529
530 // PROC INPUT STREAM
531 if(procInHandler == null) {
532 // send inputStr to process. The following constructor can handle inputStr being null
533 inputGobbler = // WriterToProcessInputStream
534 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
535 } else { // user will do custom handling of process' InputStream
536 inputGobbler = new SafeProcess.OutputStreamGobbler(process.getOutputStream(), procInHandler);
537 }
538
539 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
540 if(procErrHandler == null) {
541 errorGobbler // ReaderFromProcessOutputStream
542 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines);
543 } else {
544 errorGobbler
545 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), procErrHandler);
546 }
547
548 // PROC OUT STREAM to monitor for the expected std output line(s)
549 if(procOutHandler == null) {
550 outputGobbler
551 = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines);
552 } else {
553 outputGobbler
554 = new SafeProcess.InputStreamGobbler(process.getInputStream(), procOutHandler);
555 }
556
557
558 // 3. kick off the stream gobblers
559 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
560
561 } catch(IOException ioe) {
562 this.forciblyTerminateProcess = true;
563
564 if(exceptionHandler != null) {
565 exceptionHandler.gotException(ioe);
566 } else {
567 log("IOexception: " + ioe.getMessage(), ioe);
568 }
569 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so print stack trace
570 this.forciblyTerminateProcess = true;
571
572 if(exceptionHandler != null) {
573 exceptionHandler.gotException(ie);
574 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
575 } else {
576 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
577 }
578
579 // see comments in other runProcess()
580 Thread.currentThread().interrupt();
581
582 } finally {
583
584 cleanUp("SafeProcess.runProcess(3 params)");
585 }
586
587 return this.exitValue;
588 }
589
590 public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
591 {
592 SafeProcess.OutputStreamGobbler inputGobbler = null;
593 SafeProcess.InputStreamGobbler errorGobbler = null;
594 SafeProcess.InputStreamGobbler outputGobbler = null;
595
596 try {
597 this.forciblyTerminateProcess = false;
598
599 // 1. get the Process object
600 process = doRuntimeExec();
601
602
603 // 2. create the streamgobblers and set any specified handlers on them
604
605 // PROC INPUT STREAM
606 // send inputStr to process. The following constructor can handle inputStr being null
607 inputGobbler = // WriterToProcessInputStream
608 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
609
610 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
611 errorGobbler // ReaderFromProcessOutputStream
612 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
613 // PROC OUT STREAM to monitor for the expected std output line(s)
614 outputGobbler
615 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
616
617
618 // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
619 if(outLineByLineHandler != null) {
620 outputGobbler.setLineByLineHandler(outLineByLineHandler);
621 }
622 if(errLineByLineHandler != null) {
623 errorGobbler.setLineByLineHandler(errLineByLineHandler);
624 }
625
626
627 // 3. kick off the stream gobblers
628 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
629
630 } catch(IOException ioe) {
631 this.forciblyTerminateProcess = true;
632
633 if(exceptionHandler != null) {
634 exceptionHandler.gotException(ioe);
635 } else {
636 log("IOexception: " + ioe.getMessage(), ioe);
637 }
638 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so log it
639 this.forciblyTerminateProcess = true;
640
641 if(exceptionHandler != null) {
642 exceptionHandler.gotException(ie);
643 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
644 } else {
645 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
646 }
647 // We're not causing any interruptions that may occur when trying to stop the worker threads
648 // So resort to default behaviour in this catch?
649 // "On catching InterruptedException, re-interrupt the thread."
650 // This is just how InterruptedExceptions tend to be handled
651 // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
652 // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
653 // http://michaelscharf.blogspot.co.nz/2006/09/dont-swallow-interruptedexception-call.html
654 Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
655
656 } finally {
657
658 cleanUp("SafeProcess.runProcess(2 params)");
659 }
660
661 return this.exitValue;
662 }
663
664 private void cleanUp(String callingMethod) {
665
666 // Moved into here from GS2PerlConstructor and GShell.runLocal() which said
667 // "I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine (no it doesn't!)"
668 // http://steveliles.github.io/invoking_processes_from_java.html
669 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
670 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
671
672 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
673 //log("*** In finally of " + callingMethod + ": " + cmd);
674
675 // if we're forcibly terminating the process, call the before- and afterDestroy hooks
676 // besides actually destroying the process
677 if( this.forciblyTerminateProcess ) {
678 log("*** Going to call process.destroy from " + callingMethod);
679
680 if(mainHandler != null) mainHandler.beforeProcessDestroy();
681 boolean noNeedToDestroyIfOnLinux = true; // Interrupt handling suffices to cleanup process and subprocesses on Linux
682 SafeProcess.destroyProcess(process, noNeedToDestroyIfOnLinux); // see runProcess(2 args/3 args)
683 if(mainHandler != null) mainHandler.afterProcessDestroy();
684
685 log("*** Have called process.destroy from " + callingMethod);
686 }
687
688 process = null;
689 this.theProcessThread = null; // let the process thread ref go too
690 boolean wasForciblyTerminated = this.forciblyTerminateProcess;
691 this.forciblyTerminateProcess = false; // reset
692
693 if(mainHandler != null) mainHandler.doneCleanup(wasForciblyTerminated);
694 }
695
696/*
697
698 On Windows, p.destroy() terminates process p that Java launched,
699 but does not terminate any processes that p may have launched. Presumably since they didn't form a proper process tree.
700 https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/e3cb7532-87f6-4ae3-9d80-a3afc8b9d437/how-to-kill-a-process-tree-in-cc-on-windows-platform?forum=vclanguage
701 https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161(v=vs.85).aspx
702
703 Searching for: "forcibly terminate external process launched by Java on Windows"
704 Not possible: stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
705 But can use taskkill or tskill or wmic commands to terminate a process by processID
706 stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
707 Taskkill command can kill by Image Name, such as all running perl, e.g. taskkill /f /im perl.exe
708 But what if we kill perl instances not launched by GS?
709 /f Specifies to forcefully terminate the process(es). We need this flag switched on to kill childprocesses.
710 /t Terminates the specified process and any child processes which were started by it.
711 /t didn't work to terminate subprocesses. Maybe since the process wasn't launched as
712 a properly constructed processtree.
713 /im is the image name (the name of the program), see Image Name column in Win Task Manager.
714
715 We don't want to kill all perl running processes.
716 Another option is to use wmic, available since Windows XP, to kill a process based on its command
717 which we sort of know (SafeProcess.command) and which can be seen in TaskManager under the
718 "Command Line" column of the Processes tab.
719 https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line
720 The following works kill any Command Line that matches -site localsite lucene-jdbm-demo
721 C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate
722 "WMIC Wildcard Search using 'like' and %"
723 https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/
724 However, we're not even guaranteed that every perl command GS launches will contain the collection name
725 Nor do we want to kill all perl processes that GS launches with bin\windows\perl\bin\perl, though this works:
726 wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate
727 The above could kill GS perl processes we don't intend to terminate, as they're not spawned by the particular
728 Process we're trying to terminate from the root down.
729
730 Solution: We can use taskkill or the longstanding tskill or wmic to kill a process by ID. Since we can
731 kill an external process that SafeProcess launched OK, and only have trouble killing any child processes
732 it launched, we need to know the pids of the child processes.
733
734 We can use Windows' wmic to discover the childpids of a process whose id we know.
735 And we can use JNA to get the process ID of the external process that SafeProcess launched.
736
737 To find the processID of the process launched by SafeProcess,
738 need to use Java Native Access (JNA) jars, available jna.jar and jna-platform.jar.
739 http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
740 http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
741 http://www.golesny.de/p/code/javagetpid
742 https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
743 We're using JNA v 4.1.0, https://mvnrepository.com/artifact/net.java.dev.jna/jna
744
745 WMIC can show us a list of parent process id and process id of running processes, and then we can
746 kill those child processes with a specific process id.
747 https://superuser.com/questions/851692/track-which-program-launches-a-certain-process
748 http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows
749 WMIC can get us the pids of all childprocesses launched by parent process denoted by parent pid.
750 And vice versa:
751 if you know the parent pid and want to know all the pids of the child processes spawned:
752 wmic process where (parentprocessid=596) get processid
753 if you know a child process id and want to know the parent's id:
754 wmic process where (processid=180) get parentprocessid
755
756 The above is the current solution.
757
758 Eventually, instead of running a windows command to kill the process ourselves, consider changing over to use
759 https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java
760 (works with Apache license, http://www.apache.org/licenses/LICENSE-2.0)
761 This is a Java class that uses JNA to terminate processes. It also has the getProcessID() method.
762
763 Linux ps equivalent on Windows is "tasklist", see
764 http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
765
766*/
767
768// http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
769// Uses Java Native Access, JNA
770public static long getProcessID(Process p)
771{
772 long pid = -1;
773 try {
774 //for windows
775 if (p.getClass().getName().equals("java.lang.Win32Process") ||
776 p.getClass().getName().equals("java.lang.ProcessImpl"))
777 {
778 Field f = p.getClass().getDeclaredField("handle");
779 f.setAccessible(true);
780 long handl = f.getLong(p);
781 Kernel32 kernel = Kernel32.INSTANCE;
782 WinNT.HANDLE hand = new WinNT.HANDLE();
783 hand.setPointer(Pointer.createConstant(handl));
784 pid = kernel.GetProcessId(hand);
785 f.setAccessible(false);
786 }
787 //for unix based operating systems
788 else if (p.getClass().getName().equals("java.lang.UNIXProcess"))
789 {
790 Field f = p.getClass().getDeclaredField("pid");
791 f.setAccessible(true);
792 pid = f.getLong(p);
793 f.setAccessible(false);
794 }
795
796 } catch(Exception ex) {
797 log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex);
798 pid = -1;
799 }
800 return pid;
801}
802
803
804// Can't artificially send Ctrl-C: stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
805// (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
806// stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
807// Searching for: "forcibly terminate external process launched by Java on Windows"
808static void killWinProcessWithID(long processID) {
809
810 String cmd = SafeProcess.getWinProcessKillCmd(processID);
811 if (cmd == null) return;
812
813 try {
814 log("\tAttempting to terminate Win subprocess with pid: " + processID);
815 SafeProcess proc = new SafeProcess(cmd);
816 int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete"
817 // there is output that needs flushing, so don't use runBasicProcess()
818
819 } catch(Exception e) {
820 log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
821 }
822}
823
824// Kill signals, their names and numerical equivalents: http://www.faqs.org/qa/qa-831.html
825// https://stackoverflow.com/questions/8533377/why-child-process-still-alive-after-parent-process-was-killed-in-linux
826// Works on Linux but not Mac when build scripts run from GLI: kill -TERM -pid
827// Works on Macs but not Linux: pkill -TERM -P pid
828// More reading:
829// https://superuser.com/questions/343031/sigterm-with-a-keyboard-shortcut
830// Ctrl-C sends a SIGNINT, not SIGTERM or SIGKILL. And on Ctrl-C, "the signal is sent to the foreground *process group*."
831// https://linux.die.net/man/1/kill (manual)
832// https://unix.stackexchange.com/questions/117227/why-pidof-and-pgrep-are-behaving-differently
833// https://unix.stackexchange.com/questions/67635/elegantly-get-list-of-children-processes
834// https://stackoverflow.com/questions/994033/mac-os-x-quickest-way-to-kill-quit-an-entire-process-tree-from-within-a-cocoa-a
835// https://unix.stackexchange.com/questions/132224/is-it-possible-to-get-process-group-id-from-proc
836// https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated
837
838/**
839 * On Unix, will kill the process denoted by processID and any subprocesses this launched. Tested on a Mac, where this is used.
840 * @param force if true will send the -KILL (-9) signal, which may result in abrupt termination without cleanup
841 * if false, will send the -TERM (-15) signal, which will allow cleanup before termination. Sending a SIGTERM is preferred.
842 * @param killEntireTree if false, will terminate only the process denoted by processID, otherwise all descendants/subprocesses too.
843 * @return true if running the kill process returned an exit value of 0 or if it had already been terminated
844*/
845static boolean killUnixProcessWithID(long processID, boolean force, boolean killEntireTree) {
846
847 String signal = force ? "KILL" : "TERM"; // kill -KILL (kill -9) vs preferred kill -TERM (kill -15)
848 String cmd;
849 if(killEntireTree) { // kill the process denoted by processID and any subprocesses this launched
850
851 if(Utility.isMac()) {
852 // this cmd works on Mac (tested Snow Leopard), but on Linux this cmd only terminates toplevel process
853 // when doing full-import, and doesn't always terminate subprocesses when doing full-buildcol.pl
854 cmd = "pkill -"+signal + " -P " + processID; // e.g. "pkill -TERM -P pid"
855 }
856 else { // other unix
857 // this cmd works on linux, not recognised on Mac (tested Snow Leopard):
858 cmd = "kill -"+signal + " -"+processID; // e.g. "kill -TERM -pid"
859 // note the hyphen before pid to terminate subprocesses too
860 }
861
862 } else { // kill only the process represented by the processID.
863 cmd = "kill -"+signal + " " + processID; // e.g. "kill -TERM pid"
864 }
865
866 SafeProcess proc = new SafeProcess(cmd);
867 int exitValue = proc.runProcess();
868
869
870 if(exitValue == 0) {
871 if(force) {
872 log("@@@ Successfully sent SIGKILL to unix process tree rooted at " + processID);
873 } else {
874 log("@@@ Successfully sent SIGTERM to unix process tree rooted at " + processID);
875 }
876 return true;
877 } else if(exitValue == 1 && proc.getStdOutput().trim().equals("") && proc.getStdError().trim().equals("")) {
878 // https://stackoverflow.com/questions/28332888/return-value-of-kill
879 // "kill returns an exit code of 0 (true) if the process still existed it and was killed.
880 // kill returns an exit code of 1 (false) if the kill failed, probably because the process was no longer running."
881 // On Linux, interrupting the process and its worker threads and closing resources already successfully terminates
882 // the process and its subprocesses (don't need to call this method at all to terminate the processes: the processes
883 // aren't running when we get to this method)
884 log("@@@ Sending termination signal returned exit value 1. On unix this can happen when the process has already been terminated.");
885 return true;
886 } else {
887 log("@@@ Not able to successfully terminate process. Got exitvalue: " + exitValue);
888 log("@@@ Got output: |" + proc.getStdOutput() + "|");
889 log("@@@ Got err output: |" + proc.getStdError() + "|");
890 // caller can try again with kill -KILL, by setting force parameter to true
891 return false;
892 }
893}
894
895public static void destroyProcess(Process p) {
896 // A cancel action results in an interruption to the process thread, which in turn interrupts
897 // the SafeProcess' worker threads, all which clean up after themselves.
898 // On linux, this suffices to cleanly terminate a Process and any subprocesses that may have launched
899 // so we don't need to do extra work in such a case. But the interrupts happen only when SafeProcess calls
900 // destroyProcess() on the Process it was running internally, and not if anyone else tries to end a
901 // Process by calling SafeProcess.destroyProcess(p). In such cases, the Process needs to be actively terminated:
902 boolean canSkipExtraWorkIfLinux = true;
903 SafeProcess.destroyProcess(p, !canSkipExtraWorkIfLinux);
904}
905
906// On linux, the SafeProcess code handling an Interruption suffices to successfully and cleanly terminate
907// the process and any subprocesses launched by p as well (and not even an extra p.destroy() is needed).
908// On Windows, and Mac too, we need to do more work, since otherwise processes launched by p remain
909// around executing until they naturally terminate.
910// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until it naturally terminates.
911private static void destroyProcess(Process p, boolean canSkipExtraWorkIfLinux) {
912 log("### in SafeProcess.destroyProcess(Process p)");
913
914 // If it isn't windows, process.destroy() terminates any child processes too
915 if(Utility.isWindows()) {
916
917 if(!SafeProcess.isAvailable("wmic")) {
918 log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses...");
919 log("Kill them manually from the TaskManager or they will proceed to run to termination");
920
921 // At least we can get rid of the top level process we launched
922 p.destroy();
923 return;
924 }
925
926 // get the process id of the process we launched,
927 // so we can use it to find the pids of any subprocesses it launched in order to terminate those too.
928
929 long processID = SafeProcess.getProcessID(p);
930 if(processID == -1) { // the process doesn't exist or no longer exists (terminated naturally?)
931 p.destroy(); // minimum step, do this anyway, at worst there's no process and this won't have any effect
932 } else {
933 log("Attempting to terminate sub processes of Windows process with pid " + processID);
934 terminateSubProcessesRecursively(processID, p);
935 }
936 return;
937
938 }
939 else { // linux or mac
940
941 // if we're on linux and would have already terminated by now (in which case canSkipExtraWorkForLinux would be true),
942 // then there's nothing much left to do. This would only be the case if SafeProcess is calling this method on its
943 // internal process, since it would have successfully cleaned up on Interruption and there would be no process left running
944 if(!Utility.isMac() && canSkipExtraWorkIfLinux) {
945 log("@@@ Linux: Cancelling a SafeProcess instance does not require any complicated system destroy operation");
946 p.destroy(); // vestigial: this will have no effect if the process had already terminated, which is the case in this block
947 return;
948 }
949 // else we're on a Mac or an external caller (not SafeProcess) has requested explicit termination on Linux
950
951 long pid = SafeProcess.getProcessID(p);
952 /*
953 // On Macs (all Unix?) can't get the child processes of a process once it's been destroyed
954 macTerminateSubProcessesRecursively(pid, p);
955 */
956
957 if(pid == -1) { // if the process has already terminated, or we can't get the pid for any reason:
958 p.destroy(); // at minimum. Will have no effect if the process had already terminated
959 } else {
960 boolean forceKill = true;
961 boolean killEntireProcessTree = true;
962
963 if(!killUnixProcessWithID(pid, !forceKill, killEntireProcessTree)) { // send sig TERM (kill -15 or kill -TERM)
964 killUnixProcessWithID(pid, forceKill, killEntireProcessTree); // send sig KILL (kill -9 or kill -KILL)
965 }
966 // if both kill commands failed for whatever reason, can still at least end the top level process:
967 p.destroy(); // no effect if the process has already terminated.
968 }
969
970 return;
971 }
972}
973
974
975// UNUSED and INCOMPLETE METHOD
976// But if this method is needed, then need to parse childpids printed by "pgrep -P pid" and write recursive step
977// The childpids are probably listed one per line, see https://unix.stackexchange.com/questions/117227/why-pidof-and-pgrep-are-behaving-differently
978private static void macTerminateSubProcessesRecursively(long parent_pid, Process p) { //boolean isTopLevelProcess) {
979 log("@@@ Attempting to terminate mac process recursively");
980
981 // https://unix.stackexchange.com/questions/67635/elegantly-get-list-of-children-processes
982 SafeProcess proc = new SafeProcess("pgrep -P "+parent_pid);
983 int exitValue = proc.runProcess();
984 String stdOutput = proc.getStdOutput();
985 String stdErrOutput = proc.getStdError();
986
987 // now we have the child processes, can terminate the parent process
988 if(p != null) { // top level process, can just be terminated the java way with p.destroy()
989 p.destroy();
990 } else {
991 boolean forceKill = true;
992 boolean killSubprocesses = true;
993 // get rid of process denoted by the current pid (but not killing subprocesses it may have launched,
994 // since we'll deal with them recursively)
995 if(!SafeProcess.killUnixProcessWithID(parent_pid, !forceKill, !killSubprocesses)) { // send kill -TERM, kill -15
996 SafeProcess.killUnixProcessWithID(parent_pid, forceKill, !killSubprocesses); // send kill -9, kill -KILL
997 }
998 }
999
1000 /*
1001 // get rid of any process with current pid
1002 if(!isTopLevelProcess && !SafeProcess.killUnixProcessWithID(parent_pid, false)) { // send kill -TERM, kill -15
1003 SafeProcess.killUnixProcessWithID(parent_pid, true); // send kill -9, kill -KILL
1004 }
1005 */
1006
1007 if(stdOutput.trim().equals("") && stdErrOutput.trim().equals("") && exitValue == 1) {
1008 log("No child processes");
1009 // we're done
1010 return;
1011 } else {
1012 log("Got childpids on STDOUT: " + stdOutput);
1013 log("Got childpids on STDERR: " + stdErrOutput);
1014 }
1015}
1016
1017// Helper function. Only for Windows.
1018// Counterintuitively, we're be killing all parent processess and then all child procs and all their descendants
1019// as soon as we discover any further process each (sub)process has launched. The parent processes are killed
1020// first in each case for 2 reasons:
1021// 1. on Windows, killing the parent process leaves the child running as an orphan anyway, so killing the
1022// parent is an independent action, the child process is not dependent on the parent;
1023// 2. Killing a parent process prevents it from launching further processes while we're killing off each child process
1024private static void terminateSubProcessesRecursively(long parent_pid, Process p) {
1025
1026 // Use Windows wmic to find the pids of any sub processes launched by the process denoted by parent_pid
1027 SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+parent_pid+") get processid");
1028 proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n
1029 int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way.
1030 //log("@@@@ Return value from proc: " + exitValue);
1031
1032 // need output from both stdout and stderr: stderr will say there are no pids, stdout will contain pids
1033 String stdOutput = proc.getStdOutput();
1034 String stdErrOutput = proc.getStdError();
1035
1036
1037 // Now we know the pids of the immediate subprocesses, we can get rid of the parent process
1038 // We know the children remain running: since the whole problem on Windows is that these
1039 // child processes remain running as orphans after the parent is forcibly terminated.
1040 if(p != null) { // we're the top level process, terminate the java way
1041 p.destroy();
1042 } else { // terminate windows way
1043 SafeProcess.killWinProcessWithID(parent_pid); // get rid of process with current pid
1044 }
1045
1046 // parse the output to get the sub processes' pids
1047 // Output looks like:
1048 // ProcessId
1049 // 6040
1050 // 180
1051 // 4948
1052 // 1084
1053 // 6384
1054 // If no children, then STDERR output starts with the following, possibly succeeded by empty lines:
1055 // No Instance(s) Available.
1056
1057 // base step of the recursion
1058 if(stdErrOutput.indexOf("No Instance(s) Available.") != -1) {
1059 //log("@@@@ Got output on stderr: " + stdErrOutput);
1060 // No further child processes. And we already terminated the parent process, so we're done
1061 return;
1062 } else {
1063 //log("@@@@ Got output on stdout:\n" + stdOutput);
1064
1065 // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split
1066
1067 // find all childprocesses for that pid and terminate them too:
1068 Stack<Long> subprocs = new Stack<Long>();
1069 Scanner sc = new Scanner(stdOutput);
1070 while (sc.hasNext()) {
1071 if(!sc.hasNextLong()) {
1072 sc.next(); // discard the current token since it's not a Long
1073 } else {
1074 long child_pid = sc.nextLong();
1075 subprocs.push(new Long(child_pid));
1076 }
1077 }
1078 sc.close();
1079
1080 // recursion step if subprocs is not empty (but if it is empty, then it's another base step)
1081 if(!subprocs.empty()) {
1082 long child_pid = subprocs.pop().longValue();
1083 terminateSubProcessesRecursively(child_pid, null);
1084 }
1085 }
1086}
1087
1088// This method should only be called on a Windows OS
1089private static String getWinProcessKillCmd(Long processID) {
1090 // check if we first need to init WIN_KILL_CMD. We do this only once, but can't do it in a static codeblock
1091 // because of a cyclical dependency regarding this during static initialization
1092
1093 if(WIN_KILL_CMD == null) {
1094 if(SafeProcess.isAvailable("wmic")) {
1095 // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229
1096 WIN_KILL_CMD = "wmic process _PROCID_ delete"; // like "kill -9" on Windows
1097 }
1098 else if(SafeProcess.isAvailable("taskkill")) { // check if we have taskkill or else use the longstanding tskill
1099
1100 WIN_KILL_CMD = "taskkill /f /t /PID _PROCID_"; // need to forcefully /f terminate the process
1101 // /t "Terminates the specified process and any child processes which were started by it."
1102 // But despite the /T flag, the above doesn't kill subprocesses.
1103 }
1104 else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed
1105 WIN_KILL_CMD = "tskill _PROCID_"; // https://ss64.com/nt/tskill.html
1106 }
1107 }
1108
1109 if(WIN_KILL_CMD == null) { // can happen if none of the above cmds were available
1110 return null;
1111 }
1112 return WIN_KILL_CMD.replace( "_PROCID_", Long.toString(processID) );
1113}
1114
1115
1116// Run `which` on a program to find out if it is available. which.exe is included in winbin.
1117// On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same.
1118// where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html
1119// There is no `where` on Linux/Mac, must use which for them.
1120// On windows, "which tskill" fails (and "where tskill" works), but "which" succeeds on taskkill|wmic|browser names.
1121public static boolean isAvailable(String program) {
1122 try {
1123 // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in"
1124 // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed
1125 SafeProcess prcs = new SafeProcess("which " + program);
1126 prcs.runProcess();
1127 String output = prcs.getStdOutput().trim();
1128 ///System.err.println("*** 'which " + program + "' returned: |" + output + "|");
1129 if(output.equals("")) {
1130 return false;
1131 } else if(output.indexOf("no "+program) !=-1) { // from GS3's org.greenstone.util.BrowserLauncher.java's isAvailable(program)
1132 log("@@@ SafeProcess.isAvailable(): " + program + "is not available");
1133 return false;
1134 }
1135 //System.err.println("*** 'which " + program + "' returned: " + output);
1136 return true;
1137 } catch (Exception exc) {
1138 return false;
1139 }
1140}
1141
1142// Google Java external process destroy kill subprocesses
1143// https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
1144
1145//******************** Inner class and interface definitions ********************//
1146// Static inner classes can be instantiated without having to instantiate an object of the outer class first
1147
1148// Can have public static interfaces too,
1149// see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
1150// Implementors need to take care that the implementations are thread safe
1151// http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
1152public static interface ExceptionHandler {
1153
1154 /**
1155 * Called whenever an exception occurs during the execution of the main thread of SafeProcess
1156 * (the thread in which the Process is run).
1157 * Since this method can't be declared as synchronized in this interface method declaration,
1158 * when implementing ExceptionHandler.gotException(), if it manipulates anything that's
1159 * not threadsafe, declare gotException() as a synchronized method to ensure thread safety
1160 */
1161 public void gotException(Exception e);
1162}
1163
1164/** On interrupting (cancelling) a process,
1165 * if the class that uses SafeProcess wants to do special handling
1166 * either before and after join() is called on all the worker threads,
1167 * or, only on forcible termination, before and after process.destroy() is to be called,
1168 * then that class can implement this MainProcessHandler interface
1169 */
1170public static interface MainProcessHandler {
1171 /**
1172 * Called before the streamgobbler join()s.
1173 * If not overriding, the default implementation should be:
1174 * public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating) { return forciblyTerminating; }
1175 * When overriding:
1176 * @param forciblyTerminating is true if currently it's been decided that the process needs to be
1177 * forcibly terminated. Return false if you don't want it to be. For a basic implementation,
1178 * return the parameter.
1179 * @return true if the process is still running and therefore still needs to be destroyed, or if
1180 * you can't determine whether it's still running or not. Process.destroy() will then be called.
1181 * @return false if the process has already naturally terminated by this stage. Process.destroy()
1182 * won't be called, and neither will the before- and after- processDestroy methods of this class.
1183 */
1184 public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating);
1185 /**
1186 * Called after the streamgobbler join()s have finished.
1187 * If not overriding, the default implementation should be:
1188 * public boolean afterStreamsEnded(boolean forciblyTerminating) { return forciblyTerminating; }
1189 * When overriding:
1190 * @param forciblyTerminating is true if currently it's been decided that the process needs to be
1191 * forcibly terminated. Return false if you don't want it to be. For a basic implementation,
1192 * return the parameter (usual case).
1193 * @return true if the process is still running and therefore still needs to be destroyed, or if
1194 * can't determine whether it's still running or not. Process.destroy() will then be called.
1195 * @return false if the process has already naturally terminated by this stage. Process.destroy()
1196 * won't be called, and neither will the before- and after- processDestroy methods of this class.
1197 */
1198 public boolean afterStreamsEnded(boolean forciblyTerminating);
1199 /**
1200 * called after join()s and before process.destroy()/destroyProcess(Process), iff forciblyTerminating
1201 */
1202 public void beforeProcessDestroy();
1203 /**
1204 * Called after process.destroy()/destroyProcess(Process), iff forciblyTerminating
1205 */
1206 public void afterProcessDestroy();
1207
1208 /**
1209 * Always called after process ended: whether it got destroyed or not
1210 */
1211 public void doneCleanup(boolean wasForciblyTerminated);
1212}
1213
1214// Write your own run() body for any StreamGobbler. You need to create an instance of a class
1215// extending CustomProcessHandler for EACH IOSTREAM of the process that you want to handle.
1216// Do not create a single CustomProcessHandler instance and reuse it for all three streams,
1217// i.e. don't call SafeProcess' runProcess(x, x, x); It should be runProcess(x, y, z).
1218// Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams
1219// example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class.
1220// CustomProcessHandler is made an abstract class instead of an interface to force classes that want
1221// to use a CustomProcessHandler to create a separate class that extends CustomProcessHandler, rather than
1222// that the classes that wish to use it "implementing" the CustomProcessHandler interface itself: the
1223// CustomProcessHandler.run() method may then be called in the major thread from which the Process is being
1224// executed, rather than from the individual threads that deal with each iostream of the Process.
1225public static abstract class CustomProcessHandler {
1226
1227 protected final int source;
1228
1229 protected CustomProcessHandler(int src) {
1230 this.source = src; // STDERR or STDOUT or STDIN
1231 }
1232
1233 public String getThreadNamePrefix() {
1234 return SafeProcess.streamToString(this.source);
1235 }
1236
1237 public abstract void run(Closeable stream); //InputStream or OutputStream
1238}
1239
1240// When using the default stream processing to read from a process' stdout or stderr stream, you can
1241// create a class extending LineByLineHandler for the process' err stream and one for its output stream
1242// to do something on a line by line basis, such as sending the line to a log
1243public static abstract class LineByLineHandler {
1244 protected final int source;
1245
1246 protected LineByLineHandler(int src) {
1247 this.source = src; // STDERR or STDOUT
1248 }
1249
1250 public String getThreadNamePrefix() {
1251 return SafeProcess.streamToString(this.source);
1252 }
1253
1254 public abstract void gotLine(String line); // first non-null line
1255 public abstract void gotException(Exception e); // for when an exception occurs instead of getting a line
1256}
1257
1258
1259//**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
1260
1261// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
1262// This class is used in FormatConversionDialog to properly read from the stdout and stderr
1263// streams of a Process, Process.getInputStream() and Process.getErrorSream()
1264public static class InputStreamGobbler extends Thread
1265{
1266 private InputStream is = null;
1267 private StringBuffer outputstr = new StringBuffer();
1268 private boolean split_newlines = false;
1269 private CustomProcessHandler customHandler = null;
1270 private LineByLineHandler lineByLineHandler = null;
1271
1272 protected InputStreamGobbler() {
1273 super("InputStreamGobbler");
1274 }
1275
1276 public InputStreamGobbler(InputStream is)
1277 {
1278 this(); // sets thread name
1279 this.is = is;
1280 this.split_newlines = false;
1281 }
1282
1283 public InputStreamGobbler(InputStream is, boolean split_newlines)
1284 {
1285 this(); // sets thread name
1286 this.is = is;
1287 this.split_newlines = split_newlines;
1288
1289 }
1290
1291 public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler)
1292 {
1293 this(); // thread name
1294 this.is = is;
1295 this.customHandler = customHandler;
1296 this.adjustThreadName(customHandler.getThreadNamePrefix());
1297 }
1298
1299
1300 private void adjustThreadName(String prefix) {
1301 this.setName(prefix + this.getName());
1302 }
1303
1304 public void setLineByLineHandler(LineByLineHandler lblHandler) {
1305 this.lineByLineHandler = lblHandler;
1306 this.adjustThreadName(lblHandler.getThreadNamePrefix());
1307 }
1308
1309 // default run() behaviour
1310 public void runDefault()
1311 {
1312 BufferedReader br = null;
1313 try {
1314 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
1315 String line=null;
1316 while ( !this.isInterrupted() && (line = br.readLine()) != null ) {
1317
1318 //log("@@@ GOT LINE: " + line);
1319 outputstr.append(line);
1320 if(split_newlines) {
1321 outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
1322 }
1323
1324 if(lineByLineHandler != null) { // let handler deal with newlines
1325 lineByLineHandler.gotLine(line);
1326 }
1327 }
1328
1329 } catch (IOException ioe) {
1330 if(lineByLineHandler != null) {
1331 lineByLineHandler.gotException(ioe);
1332 } else {
1333 log("Exception when reading process stream with " + this.getName() + ": ", ioe);
1334 }
1335 } finally {
1336 if(this.isInterrupted()) {
1337 log("@@@ Successfully interrupted " + this.getName() + ".");
1338 }
1339 SafeProcess.closeResource(br);
1340 }
1341 }
1342
1343 public void runCustom() {
1344 this.customHandler.run(is);
1345 }
1346
1347 public void run() {
1348 if(this.customHandler == null) {
1349 runDefault();
1350 } else {
1351 runCustom();
1352 }
1353 }
1354
1355 public String getOutput() {
1356 return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
1357 }
1358} // end static inner class InnerStreamGobbler
1359
1360
1361// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
1362// This class is used in FormatConversionDialog to properly write to the inputstream of a Process
1363// Process.getOutputStream()
1364public static class OutputStreamGobbler extends Thread
1365{
1366 private OutputStream os = null;
1367 private String inputstr = "";
1368 private CustomProcessHandler customHandler = null;
1369
1370 protected OutputStreamGobbler() {
1371 super("stdinOutputStreamGobbler"); // thread name
1372 }
1373
1374 public OutputStreamGobbler(OutputStream os) {
1375 this(); // set thread name
1376 this.os = os;
1377 }
1378
1379 public OutputStreamGobbler(OutputStream os, String inputstr)
1380 {
1381 this(); // set thread name
1382 this.os = os;
1383 this.inputstr = inputstr;
1384 }
1385
1386 public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) {
1387 this(); // set thread name
1388 this.os = os;
1389 this.customHandler = customHandler;
1390 }
1391
1392 // default run() behaviour
1393 public void runDefault() {
1394
1395 if (inputstr == null) {
1396 return;
1397 }
1398
1399 // also quit if the process was interrupted before we could send anything to its stdin
1400 if(this.isInterrupted()) {
1401 log(this.getName() + " thread was interrupted.");
1402 return;
1403 }
1404
1405 BufferedWriter osw = null;
1406 try {
1407 osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
1408 //System.out.println("@@@ SENDING LINE: " + inputstr);
1409 osw.write(inputstr, 0, inputstr.length());
1410 osw.newLine();//osw.write("\n");
1411 osw.flush();
1412
1413 // Don't explicitly send EOF when using StreamGobblers as below,
1414 // as the EOF char is echoed to output.
1415 // Flushing the write handle and/or closing the resource seems
1416 // to already send EOF silently.
1417
1418 /*if(Utility.isWindows()) {
1419 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
1420 } else { // EOF on Linux/Mac is Ctrl-D
1421 osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
1422 }
1423 osw.flush();
1424 */
1425 } catch (IOException ioe) {
1426 log("Exception writing to SafeProcess' inputstream: ", ioe);
1427 } finally {
1428 SafeProcess.closeResource(osw);
1429 }
1430 }
1431
1432 // call the user's custom handler for the run() method
1433 public void runCustom() {
1434 this.customHandler.run(os);
1435 }
1436
1437 public void run()
1438 {
1439 if(this.customHandler == null) {
1440 runDefault();
1441 } else {
1442 runCustom();
1443 }
1444 }
1445} // end static inner class OutputStreamGobbler
1446
1447//**************** Static methods **************//
1448
1449
1450 // logger and DebugStream print commands are synchronized, therefore thread safe.
1451 public static void log(String msg) {
1452 if(DEBUG == 0) return;
1453 //logger.info(msg);
1454
1455 System.err.println(msg);
1456
1457 //DebugStream.println(msg);
1458 }
1459
1460 public static void log(String msg, Exception e) { // Print stack trace on the exception
1461 //logger.error(msg, e);
1462
1463 System.err.println(msg);
1464 e.printStackTrace();
1465
1466 //DebugStream.println(msg);
1467 //DebugStream.printStackTrace(e);
1468 }
1469
1470 public static void log(Exception e) {
1471 //logger.error(e);
1472
1473 e.printStackTrace();
1474
1475 //DebugStream.printStackTrace(e);
1476 }
1477
1478 public static void log(String msg, Exception e, boolean printStackTrace) {
1479 if(printStackTrace) {
1480 log(msg, e);
1481 } else {
1482 log(msg);
1483 }
1484 }
1485
1486 public static String streamToString(int src) {
1487 String stream;
1488 switch(src) {
1489 case STDERR:
1490 stream = "stderr";
1491 break;
1492 case STDOUT:
1493 stream = "stdout";
1494 break;
1495 default:
1496 stream = "stdin";
1497 }
1498 return stream;
1499 }
1500
1501//**************** Useful static methods. Copied from GLI's Utility.java ******************
1502 // For safely closing streams/handles/resources.
1503 // For examples of use look in the Input- and OutputStreamGobbler classes.
1504 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
1505 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
1506 public static boolean closeResource(Closeable resourceHandle) {
1507 boolean success = false;
1508 try {
1509 if(resourceHandle != null) {
1510 resourceHandle.close();
1511 resourceHandle = null;
1512 success = true;
1513 }
1514 } catch(Exception e) {
1515 log("Exception closing resource: " + e.getMessage(), e);
1516 resourceHandle = null;
1517 success = false;
1518 } finally {
1519 return success;
1520 }
1521 }
1522
1523 // in Java 6, Sockets don't yet implement Closeable
1524 public static boolean closeSocket(Socket resourceHandle) {
1525 boolean success = false;
1526 try {
1527 if(resourceHandle != null) {
1528 resourceHandle.close();
1529 resourceHandle = null;
1530 success = true;
1531 }
1532 } catch(Exception e) {
1533 log("Exception closing resource: " + e.getMessage(), e);
1534 resourceHandle = null;
1535 success = false;
1536 } finally {
1537 return success;
1538 }
1539 }
1540
1541 public static boolean closeProcess(Process prcs) {
1542 boolean success = true;
1543 if( prcs != null ) {
1544 success = success && closeResource(prcs.getErrorStream());
1545 success = success && closeResource(prcs.getOutputStream());
1546 success = success && closeResource(prcs.getInputStream());
1547 prcs.destroy();
1548 }
1549 return success;
1550 }
1551
1552// Moved from GShell.java
1553 /** Determine if the given process is still executing. It does this by attempting to throw an exception - not the most efficient way, but the only one as far as I know
1554 * @param process the Process to test
1555 * @return true if it is still executing, false otherwise
1556 */
1557 static public boolean processRunning(Process process) {
1558 boolean process_running = false;
1559
1560 try {
1561 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
1562 }
1563 catch(IllegalThreadStateException itse) {
1564 process_running = true;
1565 }
1566 catch(Exception exception) {
1567 log(exception); // DebugStream.printStackTrace(exception);
1568 }
1569 return process_running;
1570 }
1571
1572} // end class SafeProcess
Note: See TracBrowser for help on using the repository browser.