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

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

Major changes to SafeProcess and classes of the download package, by bringing the final GLI (and final Greenstone) class DownloadJob over to use SafeProcess. Significant changes to SafeProcess: 1. Introduction of cancelRunningProcess as a new method, so that callers don't need to know how cancelling a process is implemented (as an interrupt) nor do they need to know what thread they ought to interrupt (which should be the thread that launched SafeProcess), nor do they need to know of the complexity surrounding the join() blocking call which should not be interrupted. 2. Introduction of the SafeProcess.MainHandler interface that provides methods that allow implementers to write hooks to various stages of the SafeProcess' internal process' life cycle. 3. moved process cleanUp() code into a reusable method within SafeProcess. Significant changes to DownloadJob and its associated DownloadProgressBar and DownloadScrollPane classes: 1. Unused member vars removed or commented out and those that can be declared final are now declared so, as a programming pricinple for thread safety, since DownloadJob and the other download classes will have to interact with multiple threads and could be called by different threads. 2. Replaced existing actionPerformed() and callDownload() of DownloadJob with SafeProcess and implemented the necessary Hooks in the SafeProcess.MainHandler() interface to ensure that perl is still told to terminate wget on a cancel action. 3. Replaced DownloadScrollPane's deleteDownloadJob() with a new version that now more responsively removes its DownloadProgressBar (with controls) for a DownloadJob. It's called by the SafeProcess.MainHandler interface hooks implemented by DownloadJob, so DownloadScrollPane no longer needs to wait() to be notify()ed when a process has cleaned up on premature termination by a cancel action. 4. Made the necessary methods in DownloadProgressBar synchronized for thread safety. 5. GShell now uses SafeProcess' new cancelRunningProcess() method in place of directly calling interrupt on the (GShell) thread that launched SafeProcess.

File size: 53.8 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//import java.lang.reflect.Method;
25
26import org.apache.log4j.*;
27
28import org.greenstone.gatherer.DebugStream;
29
30// Use this class to run a Java Process. It follows the good and safe practices at
31// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
32// to avoid blocking problems that can arise from a Process' input and output streams.
33
34// On Windows, Perl could launch processes as proper ProcessTrees: http://search.cpan.org/~gsar/libwin32-0.191/
35// Then killing the root process will kill child processes naturally.
36
37public class SafeProcess {
38 public static int DEBUG = 0;
39
40 public static final int STDERR = 0;
41 public static final int STDOUT = 1;
42 public static final int STDIN = 2;
43 // 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:
44 public static String WIN_KILL_CMD;
45
46 /**
47 * Boolean interruptible is used to mark any sections of blocking code that should not be interrupted
48 * with an InterruptedExceptions. At present only the cancelRunningProcess() attempts to do such a thing
49 * and avoids doing so when interruptible is false.
50 * Note that interruptible is also used as a lock, so remember to synchronize on it when using it!
51 */
52 public Boolean interruptible = Boolean.TRUE;
53
54 // charset for reading process stderr and stdout streams
55 //public static final String UTF8 = "UTF-8";
56
57 ///static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
58
59 // input to SafeProcess and initialising it
60 private String command = null;
61 private String[] command_args = null;
62 private String[] envp = null;
63 private File dir = null;
64 private String inputStr = null;
65 private Process process = null;
66 private boolean forciblyTerminateProcess = false;
67
68 /** a ref to the thread in which the Process is being executed (the thread wherein Runtime.exec() is called) */
69 private Thread theProcessThread = null;
70
71 // output from running SafeProcess.runProcess()
72 private String outputStr = "";
73 private String errorStr = "";
74 private int exitValue = -1;
75 //private String charset = null;
76
77 // allow callers to process exceptions of the main process thread if they want
78 private ExceptionHandler exceptionHandler = null;
79 /** allow callers to implement hooks that get called during the main phases of the internal
80 * process' life cycle, such as before and after process.destroy() gets called
81 */
82 private MainProcessHandler mainHandler = null;
83
84 // whether std/err output should be split at new lines
85 private boolean splitStdOutputNewLines = false;
86 private boolean splitStdErrorNewLines = false;
87
88 // call one of these constructors
89
90 // cmd args version
91 public SafeProcess(String[] cmd_args)
92 {
93 command_args = cmd_args;
94 }
95
96 // cmd string version
97 public SafeProcess(String cmdStr)
98 {
99 command = cmdStr;
100 }
101
102 // cmd args with env version, launchDir can be null.
103 public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
104 {
105 command_args = cmd_args;
106 envp = envparams;
107 dir = launchDir;
108 }
109
110 // The important methods:
111 // to get the output from std err and std out streams after the process has been run
112 public String getStdOutput() { return outputStr; }
113 public String getStdError() { return errorStr; }
114 public int getExitValue() { return exitValue; }
115
116 //public void setStreamCharSet(String charset) { this.charset = charset; }
117
118 // set any string to send as input to the process spawned by SafeProcess
119 public void setInputString(String sendStr) {
120 inputStr = sendStr;
121 }
122
123 // register a SafeProcess ExceptionHandler whose gotException() method will
124 // get called for each exception encountered
125 public void setExceptionHandler(ExceptionHandler exception_handler) {
126 exceptionHandler = exception_handler;
127 }
128
129 /** to set a handler that will handle the main (SafeProcess) thread,
130 * implementing the hooks that will get called during the internal process' life cycle,
131 * such as before and after process.destroy() is called */
132 public void setMainHandler(MainProcessHandler handler) {
133 this.mainHandler = handler;
134 }
135
136 // set if you want the std output or err output to have \n at each newline read from the stream
137 public void setSplitStdOutputNewLines(boolean split) {
138 splitStdOutputNewLines = split;
139 }
140 public void setSplitStdErrorNewLines(boolean split) {
141 splitStdErrorNewLines = split;
142 }
143
144
145 /*
146 public boolean canInterrupt() {
147 boolean canInterrupt;
148 synchronized(interruptible) {
149 canInterrupt = interruptible.booleanValue();
150 }
151 return canInterrupt;
152 }
153 */
154
155 /**
156 * Call this method when you want to prematurely and safely terminate any process
157 * that SafeProcess may be running.
158 * You may want to implement the SafeProcess.MainHandler interface to write code
159 * for any hooks that will get called during the process' life cycle.
160 * @return false if process has already terminated or if it was already terminating
161 * when cancel was called. In such cases no interrupt is sent. Returns boolean sentInterrupt.
162 */
163 public synchronized boolean cancelRunningProcess() {
164 // on interrupt:
165 // - forciblyTerminate will be changed to true if the interrupt came in when the process was
166 // still running (and so before the process' streams were being joined)
167 // - and forciblyTerminate will still remain false if the interrupt happens when the process'
168 // streams are being/about to be joined (hence after the process naturally terminated).
169 // So we don't touch the value of this.forciblyTerminate here.
170 // The value of forciblyTerminate determines whether Process.destroy() and its associated before
171 // and after handlers are called or not: we don't bother destroying the process if it came to
172 // a natural end.
173
174 // no process to interrupt, so we're done
175 if(this.process == null) {
176 log("@@@ No Java process to interrupt.");
177 return false;
178 }
179
180 boolean sentInterrupt = false;
181
182 // can't interrupt when SafeProcess is joining (cleanly terminating) worker threads
183 // have to wait until afterward
184 if (interruptible) {
185 // either way, we can now interrupt the thread - if we have one (we should)
186 if(this.theProcessThread != null) { // we're told which thread should be interrupted
187 this.theProcessThread.interrupt();
188 log("@@@ Successfully sent interrupt to process.");
189 sentInterrupt = true;
190 }
191 }
192 else { // wait for join()s to finish.
193 // During and after joining(), there's no need to interrupt any more anyway: no calls
194 // subsequent to joins() block, so everything thereafter is insensitive to InterruptedExceptions.
195
196 if(SwingUtilities.isEventDispatchThread()) {
197 log("#### Event Dispatch thread, returning");
198 return false;
199 }
200
201 while(!interruptible) {
202
203 log("######### Waiting for process to become interruptible...");
204
205 // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
206 // wait will release lock on this object, and regain it when loop condition interruptible is true
207 try {
208 this.wait(); // can't interrupt when SafeProcess is joining (cleanly terminating) worker threads, so wait
209 } catch(Exception e) {
210 log("@@@ Interrupted exception while waiting for SafeProcess' worker threads to finish joining on cancelling process");
211 }
212 }
213
214 // now the process is sure to have ended as the worker threads would have been joined
215 }
216
217 return sentInterrupt;
218 }
219
220
221 // In future, think of changing the method doRuntimeExec() over to using ProcessBuilder
222 // instead of Runtime.exec(). ProcessBuilder seems to have been introduced from Java 5.
223 // https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html
224 // See also https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
225 // which suggests using Apache Common Exec to launch processes and says what will be forthcoming in Java 9
226
227 private Process doRuntimeExec() throws IOException {
228 Process prcs = null;
229 Runtime rt = Runtime.getRuntime();
230
231 if(this.command != null) {
232 log("SafeProcess running: " + command);
233 prcs = rt.exec(this.command);
234 }
235 else { // at least command_args must be set now
236
237 // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
238 //log("SafeProcess running:" + Arrays.toString(command_args));
239 StringBuffer cmdDisplay = new StringBuffer();
240 for(int i = 0; i < command_args.length; i++) {
241 cmdDisplay.append(" ").append(command_args[i]);
242 }
243 log("SafeProcess running: [" + cmdDisplay + "]");
244 cmdDisplay = null; // let the GC have it
245
246
247 if(this.envp == null) {
248 prcs = rt.exec(this.command_args);
249 } else { // launch process using cmd str with env params
250
251 if(this.dir == null) {
252 //log("\twith: " + Arrays.toString(this.envp));
253 prcs = rt.exec(this.command_args, this.envp);
254 } else {
255 //log("\tfrom directory: " + this.dir);
256 //log("\twith: " + Arrays.toString(this.envp));
257 prcs = rt.exec(this.command_args, this.envp, this.dir);
258 }
259 }
260 }
261
262 this.theProcessThread = Thread.currentThread(); // store a ref to the thread wherein the Process is being run
263 return prcs;
264 }
265
266 // Copied from gli's gui/FormatConversionDialog.java
267 private int waitForWithStreams(SafeProcess.OutputStreamGobbler inputGobbler,
268 SafeProcess.InputStreamGobbler outputGobbler,
269 SafeProcess.InputStreamGobbler errorGobbler)
270 throws IOException, InterruptedException
271 {
272 // kick off the stream gobblers
273 inputGobbler.start();
274 errorGobbler.start();
275 outputGobbler.start();
276
277 try {
278 this.exitValue = process.waitFor(); // can throw an InterruptedException if process was cancelled/prematurely terminated
279 } catch(InterruptedException ie) {
280 log("*** Process interrupted (InterruptedException). Expected to be a Cancel operation.");
281 // don't print stacktrace: an interrupt here is not an error, it's expected to be a cancel action
282 if(exceptionHandler != null) {
283 exceptionHandler.gotException(ie);
284 }
285
286 // propagate interrupts to worker threads here
287 // unless the interrupt emanated from any of them in any join(),
288 // which will be caught by the calling method's own catch on InterruptedException.
289 // Only if the thread that SafeProcess runs in was interrupted
290 // should we propagate the interrupt to the worker threads.
291 // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
292 // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
293 // But SafeProcess owns the worker threads, so it has every right to interrupt them
294 // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
295
296 // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
297 // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
298 // "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."
299 // Does that mean that since this code implements this thread's interruption policy, it's ok
300 // to swallow the interrupt this time and not let it propagate by commenting out the next line?
301 //Thread.currentThread().interrupt(); // re-interrupt the thread
302
303 inputGobbler.interrupt();
304 errorGobbler.interrupt();
305 outputGobbler.interrupt();
306
307 // Since we have been cancelled (InterruptedException), or on any Exception, we need
308 // to forcibly terminate process eventually after the finally code first waits for each worker thread
309 // to die off. Don't set process=null until after we've forcibly terminated it if needs be.
310 this.forciblyTerminateProcess = true;
311
312 // even after the interrupts, we want to proceed to calling join() on all the worker threads
313 // in order to wait for each of them to die before attempting to destroy the process if it
314 // still hasn't terminated after all that.
315 } finally {
316
317 //log("Process exitValue: " + exitValue);
318 ///log("@@@@ Before join phase. Forcibly terminating: " + this.forciblyTerminateProcess);
319
320 // From the comments of
321 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
322 // To avoid running into nondeterministic failures to get the process output
323 // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
324 // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
325
326 // Wait for each of the threads to die, before attempting to destroy the process
327 // Any of these can throw InterruptedExceptions too
328 // and will be processed by the calling function's catch on InterruptedException.
329
330
331 // Thread.joins() below are blocking calls, same as Process.waitFor(), and a cancel action could
332 // send an interrupt during any Join: the InterruptedException ensuing will then break out of the
333 // joins() section. We don't want that to happen: by the time the joins() start happening, the
334 // actual process has finished in some way (naturally terminated or interrupted), and nothing
335 // should interrupt the joins() (nor ideally any potential p.destroy after that).
336 // So we mark the join() section as an un-interruptible section, and make anyone who's seeking
337 // to interrupt just then first wait for this Thread (in which SafeProcess runs) to become
338 // interruptible again. Thos actually assumes anything interruptible can still happen thereafter
339 // when in reality, none of the subsequent actions after the joins() block. So they nothing
340 // thereafter, which is the cleanup phase, will actually respond to an InterruptedException.
341
342
343 if(this.mainHandler != null) {
344 // this method can unset forcible termination flag
345 // if the process had already naturally terminated by this stage:
346 this.forciblyTerminateProcess = mainHandler.beforeWaitingForStreamsToEnd(this.forciblyTerminateProcess);
347 }
348
349 ///log("@@@@ After beforeJoin Handler. Forcibly terminating: " + this.forciblyTerminateProcess);
350
351 // Anyone could interrupt/cancel during waitFor() above,
352 // but no one should interrupt while the worker threads come to a clean close,
353 // so make anyone wanting to cancel the process at this stage wait()
354 // until we're done with the join()s:
355 synchronized(interruptible) {
356 interruptible = Boolean.FALSE;
357 }
358 //Thread.sleep(5000); // Uncomment to test this uninterruptible section, also comment out block checking for
359 // EventDispatchThread in cancelRunningProcess() and 2 calls to progress.enableCancelJob() in DownloadJob.java
360 outputGobbler.join();
361 errorGobbler.join();
362 inputGobbler.join();
363
364 synchronized(interruptible) {
365 interruptible = Boolean.TRUE;
366 }
367
368 ///log("@@@@ Join phase done...");
369
370 // notify any of those waiting to interrupt this thread, that they may feel free to do so again
371 // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
372 synchronized(this) {
373 this.notify();
374 }
375
376 // set the variables that the code which created a SafeProcess object may want to inspect
377 this.outputStr = outputGobbler.getOutput();
378 this.errorStr = errorGobbler.getOutput();
379
380 // call the after join()s hook
381 if(this.mainHandler != null) {
382 this.forciblyTerminateProcess = mainHandler.afterStreamsEnded(this.forciblyTerminateProcess);
383 }
384 }
385
386 // Don't return from finally, it's considered an abrupt completion and exceptions are lost, see
387 // http://stackoverflow.com/questions/18205493/can-we-use-return-in-finally-block
388 return this.exitValue;
389 }
390
391
392 public synchronized boolean processRunning() {
393 if(process == null) return false;
394 return SafeProcess.processRunning(this.process);
395 }
396
397 // Run a very basic process: with no reading from or writing to the Process' iostreams,
398 // this just execs the process and waits for it to return.
399 // Don't call this method but the zero-argument runProcess() instead if your process will
400 // output stuff to its stderr and stdout streams but you don't need to monitory these.
401 // Because, as per a comment in GLI's GS3ServerThread.java,
402 // in Java 6, it wil block if you don't handle a process' streams when the process is
403 // outputting something. (Java 7+ won't block if you don't bother to handle the output streams)
404 public int runBasicProcess() {
405 try {
406 this.forciblyTerminateProcess = true;
407
408 // 1. create the process
409 process = doRuntimeExec();
410 // 2. basic waitFor the process to finish
411 this.exitValue = process.waitFor();
412
413 this.forciblyTerminateProcess = false;
414 } catch(IOException ioe) {
415
416 if(exceptionHandler != null) {
417 exceptionHandler.gotException(ioe);
418 } else {
419 log("IOException: " + ioe.getMessage(), ioe);
420 }
421 } catch(InterruptedException ie) {
422
423 if(exceptionHandler != null) {
424 exceptionHandler.gotException(ie);
425 } else { // Unexpected InterruptedException, so printstacktrace
426 log("Process InterruptedException: " + ie.getMessage(), ie);
427 }
428
429 Thread.currentThread().interrupt();
430 } finally {
431
432 cleanUp("SafeProcess.runBasicProcess");
433 }
434 return this.exitValue;
435 }
436
437 // Runs a process with default stream processing. Returns the exitValue
438 public int runProcess() {
439 return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
440 }
441
442 // Run a process with custom stream processing (any custom handlers passed in that are null
443 // will use the default stream processing).
444 // Returns the exitValue from running the Process
445 public int runProcess(CustomProcessHandler procInHandler,
446 CustomProcessHandler procOutHandler,
447 CustomProcessHandler procErrHandler)
448 {
449 SafeProcess.OutputStreamGobbler inputGobbler = null;
450 SafeProcess.InputStreamGobbler errorGobbler = null;
451 SafeProcess.InputStreamGobbler outputGobbler = null;
452
453 try {
454 this.forciblyTerminateProcess = false;
455
456 // 1. get the Process object
457 process = doRuntimeExec();
458
459
460 // 2. create the streamgobblers and set any specified handlers on them
461
462 // PROC INPUT STREAM
463 if(procInHandler == null) {
464 // send inputStr to process. The following constructor can handle inputStr being null
465 inputGobbler = // WriterToProcessInputStream
466 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
467 } else { // user will do custom handling of process' InputStream
468 inputGobbler = new SafeProcess.OutputStreamGobbler(process.getOutputStream(), procInHandler);
469 }
470
471 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
472 if(procErrHandler == null) {
473 errorGobbler // ReaderFromProcessOutputStream
474 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines);
475 } else {
476 errorGobbler
477 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), procErrHandler);
478 }
479
480 // PROC OUT STREAM to monitor for the expected std output line(s)
481 if(procOutHandler == null) {
482 outputGobbler
483 = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines);
484 } else {
485 outputGobbler
486 = new SafeProcess.InputStreamGobbler(process.getInputStream(), procOutHandler);
487 }
488
489
490 // 3. kick off the stream gobblers
491 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
492
493 } catch(IOException ioe) {
494 this.forciblyTerminateProcess = true;
495
496 if(exceptionHandler != null) {
497 exceptionHandler.gotException(ioe);
498 } else {
499 log("IOexception: " + ioe.getMessage(), ioe);
500 }
501 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so print stack trace
502 this.forciblyTerminateProcess = true;
503
504 if(exceptionHandler != null) {
505 exceptionHandler.gotException(ie);
506 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
507 } else {
508 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
509 }
510
511 // see comments in other runProcess()
512 Thread.currentThread().interrupt();
513
514 } finally {
515
516 cleanUp("SafeProcess.runProcess(3 params)");
517 }
518
519 return this.exitValue;
520 }
521
522 public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
523 {
524 SafeProcess.OutputStreamGobbler inputGobbler = null;
525 SafeProcess.InputStreamGobbler errorGobbler = null;
526 SafeProcess.InputStreamGobbler outputGobbler = null;
527
528 try {
529 this.forciblyTerminateProcess = false;
530
531 // 1. get the Process object
532 process = doRuntimeExec();
533
534
535 // 2. create the streamgobblers and set any specified handlers on them
536
537 // PROC INPUT STREAM
538 // send inputStr to process. The following constructor can handle inputStr being null
539 inputGobbler = // WriterToProcessInputStream
540 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
541
542 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
543 errorGobbler // ReaderFromProcessOutputStream
544 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
545 // PROC OUT STREAM to monitor for the expected std output line(s)
546 outputGobbler
547 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
548
549
550 // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
551 if(outLineByLineHandler != null) {
552 outputGobbler.setLineByLineHandler(outLineByLineHandler);
553 }
554 if(errLineByLineHandler != null) {
555 errorGobbler.setLineByLineHandler(errLineByLineHandler);
556 }
557
558
559 // 3. kick off the stream gobblers
560 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
561
562 } catch(IOException ioe) {
563 this.forciblyTerminateProcess = true;
564
565 if(exceptionHandler != null) {
566 exceptionHandler.gotException(ioe);
567 } else {
568 log("IOexception: " + ioe.getMessage(), ioe);
569 }
570 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so log it
571 this.forciblyTerminateProcess = true;
572
573 if(exceptionHandler != null) {
574 exceptionHandler.gotException(ie);
575 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
576 } else {
577 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
578 }
579 // We're not causing any interruptions that may occur when trying to stop the worker threads
580 // So resort to default behaviour in this catch?
581 // "On catching InterruptedException, re-interrupt the thread."
582 // This is just how InterruptedExceptions tend to be handled
583 // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
584 // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
585 Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
586
587 } finally {
588
589 cleanUp("SafeProcess.runProcess(2 params)");
590 }
591
592 return this.exitValue;
593 }
594
595 private void cleanUp(String callingMethod) {
596
597 // Moved into here from GS2PerlConstructor and GShell.runLocal() which said
598 // "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!)"
599 // http://steveliles.github.io/invoking_processes_from_java.html
600 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
601 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
602
603 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
604 //log("*** In finally of " + callingMethod + ": " + cmd);
605
606 // if we're forcibly terminating the process, call the before- and afterDestroy hooks
607 // besides actually destroying the process
608 if( this.forciblyTerminateProcess ) {
609 log("*** Going to call process.destroy from " + callingMethod);
610
611 if(mainHandler != null) mainHandler.beforeProcessDestroy();
612 SafeProcess.destroyProcess(process); // 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// 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
755// On linux and mac, p.destroy() suffices to kill processes launched by p as well.
756// On Windows we need to do more work, since otherwise processes launched by p remain around executing until they naturally terminate.
757// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until it naturally terminates.
758static void destroyProcess(Process p) {
759 log("### in SafeProcess.destroyProcess(Process p)");
760
761 // If it isn't windows, process.destroy() terminates any child processes too
762 if(!Utility.isWindows()) {
763 p.destroy();
764 return;
765 }
766
767 if(!SafeProcess.isAvailable("wmic")) {
768 log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses...");
769 log("Kill them manually from the TaskManager or they will proceed to run to termination");
770
771 // At least we can get rid of the top level process we launched
772 p.destroy();
773 return;
774 }
775
776 // get the process id of the process we launched,
777 // so we can use it to find the pids of any subprocesses it launched in order to terminate those too.
778
779 long processID = SafeProcess.getProcessID(p);
780 if(processID == -1) { // the process doesn't exist or no longer exists (terminated naturally?)
781 p.destroy(); // minimum step, do this anyway, at worst there's no process and this won't have any effect
782 } else {
783 log("Attempting to terminate sub processes of Windows process with pid " + processID);
784 terminateSubProcessesRecursively(processID, p);
785 }
786
787}
788
789// Helper function. Only for Windows.
790// Counterintuitively, we're be killing all parent processess and then all child procs and all their descendants
791// as soon as we discover any further process each (sub)process has launched. The parent processes are killed
792// first in each case for 2 reasons:
793// 1. on Windows, killing the parent process leaves the child running as an orphan anyway, so killing the
794// parent is an independent action, the child process is not dependent on the parent;
795// 2. Killing a parent process prevents it from launching further processes while we're killing off each child process
796private static void terminateSubProcessesRecursively(long parent_pid, Process p) {
797
798 // Use Windows wmic to find the pids of any sub processes launched by the process denoted by parent_pid
799 SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+parent_pid+") get processid");
800 proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n
801 int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way.
802 //log("@@@@ Return value from proc: " + exitValue);
803
804 // need output from both stdout and stderr: stderr will say there are no pids, stdout will contain pids
805 String stdOutput = proc.getStdOutput();
806 String stdErrOutput = proc.getStdError();
807
808
809 // Now we know the pids of the immediate subprocesses, we can get rid of the parent process
810 // We know the children remain running: since the whole problem on Windows is that these
811 // child processes remain running as orphans after the parent is forcibly terminated.
812 if(p != null) { // we're the top level process, terminate the java way
813 p.destroy();
814 } else { // terminate windows way
815 SafeProcess.killWinProcessWithID(parent_pid); // get rid off current pid
816 }
817
818 // parse the output to get the sub processes' pids
819 // Output looks like:
820 // ProcessId
821 // 6040
822 // 180
823 // 4948
824 // 1084
825 // 6384
826 // If no children, then STDERR output starts with the following, possibly succeeded by empty lines:
827 // No Instance(s) Available.
828
829 // base step of the recursion
830 if(stdErrOutput.indexOf("No Instance(s) Available.") != -1) {
831 //log("@@@@ Got output on stderr: " + stdErrOutput);
832 // No further child processes. And we already terminated the parent process, so we're done
833 return;
834 } else {
835 //log("@@@@ Got output on stdout:\n" + stdOutput);
836
837 // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split
838
839 // find all childprocesses for that pid and terminate them too:
840 Stack<Long> subprocs = new Stack<Long>();
841 Scanner sc = new Scanner(stdOutput);
842 while (sc.hasNext()) {
843 if(!sc.hasNextLong()) {
844 sc.next(); // discard the current token since it's not a Long
845 } else {
846 long child_pid = sc.nextLong();
847 subprocs.push(new Long(child_pid));
848 }
849 }
850 sc.close();
851
852 // recursion step if subprocs is not empty (but if it is empty, then it's another base step)
853 if(!subprocs.empty()) {
854 long child_pid = subprocs.pop().longValue();
855 terminateSubProcessesRecursively(child_pid, null);
856 }
857 }
858}
859
860// This method should only be called on a Windows OS
861private static String getWinProcessKillCmd(Long processID) {
862 // check if we first need to init WIN_KILL_CMD. We do this only once, but can't do it in a static codeblock
863 // because of a cyclical dependency regarding this during static initialization
864
865 if(WIN_KILL_CMD == null) {
866 if(SafeProcess.isAvailable("wmic")) {
867 // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229
868 WIN_KILL_CMD = "wmic process _PROCID_ delete"; // like "kill -9" on Windows
869 }
870 else if(SafeProcess.isAvailable("taskkill")) { // check if we have taskkill or else use the longstanding tskill
871
872 WIN_KILL_CMD = "taskkill /f /t /PID _PROCID_"; // need to forcefully /f terminate the process
873 // /t "Terminates the specified process and any child processes which were started by it."
874 // But despite the /T flag, the above doesn't kill subprocesses.
875 }
876 else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed
877 WIN_KILL_CMD = "tskill _PROCID_"; // https://ss64.com/nt/tskill.html
878 }
879 }
880
881 if(WIN_KILL_CMD == null) { // can happen if none of the above cmds were available
882 return null;
883 }
884 return WIN_KILL_CMD.replace( "_PROCID_", Long.toString(processID) );
885}
886
887
888// Run `which` on a program to find out if it is available. which.exe is included in winbin.
889// On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same.
890// where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html
891// There is no `where` on Linux/Mac, must use which for them.
892// On windows, "which tskill" fails (and "where tskill" works), but "which" succeeds on taskkill|wmic|browser names.
893public static boolean isAvailable(String program) {
894 try {
895 // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in"
896 // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed
897 SafeProcess prcs = new SafeProcess("which " + program);
898 prcs.runProcess();
899 String output = prcs.getStdOutput();
900 if(output.equals("")) {
901 return false;
902 }
903 //System.err.println("*** 'which " + program + "' returned: " + output);
904 return true;
905 } catch (Exception exc) {
906 return false;
907 }
908}
909
910// Google Java external process destroy kill subprocesses
911// https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
912
913//******************** Inner class and interface definitions ********************//
914// Static inner classes can be instantiated without having to instantiate an object of the outer class first
915
916// Can have public static interfaces too,
917// see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
918// Implementors need to take care that the implementations are thread safe
919// http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
920public static interface ExceptionHandler {
921
922 /**
923 * Called whenever an exception occurs during the execution of the main thread of SafeProcess
924 * (the thread in which the Process is run).
925 * Since this method can't be declared as synchronized in this interface method declaration,
926 * when implementing ExceptionHandler.gotException(), if it manipulates anything that's
927 * not threadsafe, declare gotException() as a synchronized method to ensure thread safety
928 */
929 public void gotException(Exception e);
930}
931
932/** On interrupting (cancelling) a process,
933 * if the class that uses SafeProcess wants to do special handling
934 * either before and after join() is called on all the worker threads,
935 * or, only on forcible termination, before and after process.destroy() is to be called,
936 * then that class can implement this MainProcessHandler interface
937 */
938public static interface MainProcessHandler {
939 /**
940 * Called before the streamgobbler join()s.
941 * If not overriding, the default implementation should be:
942 * public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating) { return forciblyTerminating; }
943 * When overriding:
944 * @param forciblyTerminating is true if currently it's been decided that the process needs to be
945 * forcibly terminated. Return false if you don't want it to be. For a basic implementation,
946 * return the parameter.
947 * @return true if the process is still running and therefore still needs to be destroyed, or if
948 * you can't determine whether it's still running or not. Process.destroy() will then be called.
949 * @return false if the process has already naturally terminated by this stage. Process.destroy()
950 * won't be called, and neither will the before- and after- processDestroy methods of this class.
951 */
952 public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating);
953 /**
954 * Called after the streamgobbler join()s have finished.
955 * If not overriding, the default implementation should be:
956 * public boolean afterStreamsEnded(boolean forciblyTerminating) { return forciblyTerminating; }
957 * When overriding:
958 * @param forciblyTerminating is true if currently it's been decided that the process needs to be
959 * forcibly terminated. Return false if you don't want it to be. For a basic implementation,
960 * return the parameter (usual case).
961 * @return true if the process is still running and therefore still needs to be destroyed, or if
962 * can't determine whether it's still running or not. Process.destroy() will then be called.
963 * @return false if the process has already naturally terminated by this stage. Process.destroy()
964 * won't be called, and neither will the before- and after- processDestroy methods of this class.
965 */
966 public boolean afterStreamsEnded(boolean forciblyTerminating);
967 /**
968 * called after join()s and before process.destroy()/destroyProcess(Process), iff forciblyTerminating
969 */
970 public void beforeProcessDestroy();
971 /**
972 * Called after process.destroy()/destroyProcess(Process), iff forciblyTerminating
973 */
974 public void afterProcessDestroy();
975
976 /**
977 * Always called after process ended: whether it got destroyed or not
978 */
979 public void doneCleanup(boolean wasForciblyTerminated);
980}
981
982// Write your own run() body for any StreamGobbler. You need to create an instance of a class
983// extending CustomProcessHandler for EACH IOSTREAM of the process that you want to handle.
984// Do not create a single CustomProcessHandler instance and reuse it for all three streams,
985// i.e. don't call SafeProcess' runProcess(x, x, x); It should be runProcess(x, y, z).
986// Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams
987// example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class.
988// CustomProcessHandler is made an abstract class instead of an interface to force classes that want
989// to use a CustomProcessHandler to create a separate class that extends CustomProcessHandler, rather than
990// that the classes that wish to use it "implementing" the CustomProcessHandler interface itself: the
991// CustomProcessHandler.run() method may then be called in the major thread from which the Process is being
992// executed, rather than from the individual threads that deal with each iostream of the Process.
993public static abstract class CustomProcessHandler {
994
995 protected final int source;
996
997 protected CustomProcessHandler(int src) {
998 this.source = src; // STDERR or STDOUT or STDIN
999 }
1000
1001 public String getThreadNamePrefix() {
1002 return SafeProcess.streamToString(this.source);
1003 }
1004
1005 public abstract void run(Closeable stream); //InputStream or OutputStream
1006}
1007
1008// When using the default stream processing to read from a process' stdout or stderr stream, you can
1009// create a class extending LineByLineHandler for the process' err stream and one for its output stream
1010// to do something on a line by line basis, such as sending the line to a log
1011public static abstract class LineByLineHandler {
1012 protected final int source;
1013
1014 protected LineByLineHandler(int src) {
1015 this.source = src; // STDERR or STDOUT
1016 }
1017
1018 public String getThreadNamePrefix() {
1019 return SafeProcess.streamToString(this.source);
1020 }
1021
1022 public abstract void gotLine(String line); // first non-null line
1023 public abstract void gotException(Exception e); // for when an exception occurs instead of getting a line
1024}
1025
1026
1027//**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
1028
1029// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
1030// This class is used in FormatConversionDialog to properly read from the stdout and stderr
1031// streams of a Process, Process.getInputStream() and Process.getErrorSream()
1032public static class InputStreamGobbler extends Thread
1033{
1034 private InputStream is = null;
1035 private StringBuffer outputstr = new StringBuffer();
1036 private boolean split_newlines = false;
1037 private CustomProcessHandler customHandler = null;
1038 private LineByLineHandler lineByLineHandler = null;
1039
1040 protected InputStreamGobbler() {
1041 super("InputStreamGobbler");
1042 }
1043
1044 public InputStreamGobbler(InputStream is)
1045 {
1046 this(); // sets thread name
1047 this.is = is;
1048 this.split_newlines = false;
1049 }
1050
1051 public InputStreamGobbler(InputStream is, boolean split_newlines)
1052 {
1053 this(); // sets thread name
1054 this.is = is;
1055 this.split_newlines = split_newlines;
1056
1057 }
1058
1059 public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler)
1060 {
1061 this(); // thread name
1062 this.is = is;
1063 this.customHandler = customHandler;
1064 this.adjustThreadName(customHandler.getThreadNamePrefix());
1065 }
1066
1067
1068 private void adjustThreadName(String prefix) {
1069 this.setName(prefix + this.getName());
1070 }
1071
1072 public void setLineByLineHandler(LineByLineHandler lblHandler) {
1073 this.lineByLineHandler = lblHandler;
1074 this.adjustThreadName(lblHandler.getThreadNamePrefix());
1075 }
1076
1077 // default run() behaviour
1078 public void runDefault()
1079 {
1080 BufferedReader br = null;
1081 try {
1082 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
1083 String line=null;
1084 while ( !this.isInterrupted() && (line = br.readLine()) != null ) {
1085
1086 //log("@@@ GOT LINE: " + line);
1087 outputstr.append(line);
1088 if(split_newlines) {
1089 outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
1090 }
1091
1092 if(lineByLineHandler != null) { // let handler deal with newlines
1093 lineByLineHandler.gotLine(line);
1094 }
1095 }
1096
1097 } catch (IOException ioe) {
1098 if(lineByLineHandler != null) {
1099 lineByLineHandler.gotException(ioe);
1100 } else {
1101 log("Exception when reading process stream with " + this.getName() + ": ", ioe);
1102 }
1103 } finally {
1104 if(this.isInterrupted()) {
1105 log("@@@ Successfully interrupted " + this.getName() + ".");
1106 }
1107 SafeProcess.closeResource(br);
1108 }
1109 }
1110
1111 public void runCustom() {
1112 this.customHandler.run(is);
1113 }
1114
1115 public void run() {
1116 if(this.customHandler == null) {
1117 runDefault();
1118 } else {
1119 runCustom();
1120 }
1121 }
1122
1123 public String getOutput() {
1124 return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
1125 }
1126} // end static inner class InnerStreamGobbler
1127
1128
1129// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
1130// This class is used in FormatConversionDialog to properly write to the inputstream of a Process
1131// Process.getOutputStream()
1132public static class OutputStreamGobbler extends Thread
1133{
1134 private OutputStream os = null;
1135 private String inputstr = "";
1136 private CustomProcessHandler customHandler = null;
1137
1138 protected OutputStreamGobbler() {
1139 super("stdinOutputStreamGobbler"); // thread name
1140 }
1141
1142 public OutputStreamGobbler(OutputStream os) {
1143 this(); // set thread name
1144 this.os = os;
1145 }
1146
1147 public OutputStreamGobbler(OutputStream os, String inputstr)
1148 {
1149 this(); // set thread name
1150 this.os = os;
1151 this.inputstr = inputstr;
1152 }
1153
1154 public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) {
1155 this(); // set thread name
1156 this.os = os;
1157 this.customHandler = customHandler;
1158 }
1159
1160 // default run() behaviour
1161 public void runDefault() {
1162
1163 if (inputstr == null) {
1164 return;
1165 }
1166
1167 // also quit if the process was interrupted before we could send anything to its stdin
1168 if(this.isInterrupted()) {
1169 log(this.getName() + " thread was interrupted.");
1170 return;
1171 }
1172
1173 BufferedWriter osw = null;
1174 try {
1175 osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
1176 //System.out.println("@@@ SENDING LINE: " + inputstr);
1177 osw.write(inputstr, 0, inputstr.length());
1178 osw.newLine();//osw.write("\n");
1179 osw.flush();
1180
1181 // Don't explicitly send EOF when using StreamGobblers as below,
1182 // as the EOF char is echoed to output.
1183 // Flushing the write handle and/or closing the resource seems
1184 // to already send EOF silently.
1185
1186 /*if(Utility.isWindows()) {
1187 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
1188 } else { // EOF on Linux/Mac is Ctrl-D
1189 osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
1190 }
1191 osw.flush();
1192 */
1193 } catch (IOException ioe) {
1194 log("Exception writing to SafeProcess' inputstream: ", ioe);
1195 } finally {
1196 SafeProcess.closeResource(osw);
1197 }
1198 }
1199
1200 // call the user's custom handler for the run() method
1201 public void runCustom() {
1202 this.customHandler.run(os);
1203 }
1204
1205 public void run()
1206 {
1207 if(this.customHandler == null) {
1208 runDefault();
1209 } else {
1210 runCustom();
1211 }
1212 }
1213} // end static inner class OutputStreamGobbler
1214
1215//**************** Static methods **************//
1216
1217
1218 // logger and DebugStream print commands are synchronized, therefore thread safe.
1219 public static void log(String msg) {
1220 if(DEBUG == 0) return;
1221 //logger.info(msg);
1222
1223 System.err.println(msg);
1224
1225 //DebugStream.println(msg);
1226 }
1227
1228 public static void log(String msg, Exception e) { // Print stack trace on the exception
1229 if(DEBUG == 0) return;
1230 //logger.error(msg, e);
1231
1232 System.err.println(msg);
1233 e.printStackTrace();
1234
1235 //DebugStream.println(msg);
1236 //DebugStream.printStackTrace(e);
1237 }
1238
1239 public static void log(Exception e) {
1240 if(DEBUG == 0) return;
1241 //logger.error(e);
1242
1243 e.printStackTrace();
1244
1245 //DebugStream.printStackTrace(e);
1246 }
1247
1248 public static void log(String msg, Exception e, boolean printStackTrace) {
1249 if(printStackTrace) {
1250 log(msg, e);
1251 } else {
1252 log(msg);
1253 }
1254 }
1255
1256 public static String streamToString(int src) {
1257 String stream;
1258 switch(src) {
1259 case STDERR:
1260 stream = "stderr";
1261 break;
1262 case STDOUT:
1263 stream = "stdout";
1264 break;
1265 default:
1266 stream = "stdin";
1267 }
1268 return stream;
1269 }
1270
1271//**************** Useful static methods. Copied from GLI's Utility.java ******************
1272 // For safely closing streams/handles/resources.
1273 // For examples of use look in the Input- and OutputStreamGobbler classes.
1274 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
1275 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
1276 public static boolean closeResource(Closeable resourceHandle) {
1277 boolean success = false;
1278 try {
1279 if(resourceHandle != null) {
1280 resourceHandle.close();
1281 resourceHandle = null;
1282 success = true;
1283 }
1284 } catch(Exception e) {
1285 log("Exception closing resource: " + e.getMessage(), e);
1286 resourceHandle = null;
1287 success = false;
1288 } finally {
1289 return success;
1290 }
1291 }
1292
1293 // in Java 6, Sockets don't yet implement Closeable
1294 public static boolean closeSocket(Socket resourceHandle) {
1295 boolean success = false;
1296 try {
1297 if(resourceHandle != null) {
1298 resourceHandle.close();
1299 resourceHandle = null;
1300 success = true;
1301 }
1302 } catch(Exception e) {
1303 log("Exception closing resource: " + e.getMessage(), e);
1304 resourceHandle = null;
1305 success = false;
1306 } finally {
1307 return success;
1308 }
1309 }
1310
1311 public static boolean closeProcess(Process prcs) {
1312 boolean success = true;
1313 if( prcs != null ) {
1314 success = success && closeResource(prcs.getErrorStream());
1315 success = success && closeResource(prcs.getOutputStream());
1316 success = success && closeResource(prcs.getInputStream());
1317 prcs.destroy();
1318 }
1319 return success;
1320 }
1321
1322// Moved from GShell.java
1323 /** 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
1324 * @param process the Process to test
1325 * @return true if it is still executing, false otherwise
1326 */
1327 static public boolean processRunning(Process process) {
1328 boolean process_running = false;
1329
1330 try {
1331 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
1332 }
1333 catch(IllegalThreadStateException itse) {
1334 process_running = true;
1335 }
1336 catch(Exception exception) {
1337 log(exception); // DebugStream.printStackTrace(exception);
1338 }
1339 return process_running;
1340 }
1341
1342} // end class SafeProcess
Note: See TracBrowser for help on using the repository browser.