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

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

Unused, but possibly useful, commented out code

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