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

Last change on this file was 35665, checked in by davidb, 3 years ago

Calling things like new Boolean(false) and new Integer(100) has been depreated in favour of Boolean.valueOf(false) and Integer.valueOf(100). This set of code commits reflects the necessary coding changes to the valueOf() form, which the JDK developers specify is a more efficient way to achieve the same thing

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