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