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

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

When an instance of SafeProcess calls its static destroyProcess method, this happens on cancel action which would have caused an Interrupt to the SafeProcess thread that interrupts the worker threads, all of which cleanly tidy up after themselves. On Linux, this has the effect that the Process internal to SafeProcess, and any subprocesses that the process may have launched, are all neatly terminated. So on Linux, when destroyProcess is called by a SafeProcess instance on itself, there's no need to send a SIGTERM (else SIGKILL) to terminate the Process, as it's already cleanly terminated. Reducing the overkill step in such a scenario. The public static SafeProcess.destroyProcess(Process p) now forces the step of sending a termination signal, since any caller outside a SafeProcess instance won't neatly tidy up their Process object with interrupts.

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