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

Last change on this file since 31712 was 31712, checked in by ak19, 7 years ago

Logging on exception should happen regardless of whether the DEBUG flag is on or not. Exceptions are not debugging information. Also made some minor changes to a few comments.

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