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

Last change on this file since 31646 was 31646, checked in by ak19, 7 years ago
  1. Moved isAvailable(program) into SafeProcess.java from FileAssociationManager.java. 2. SafeProcess.java now successfully terminates any child processes launched on Windows too because of the addition of new methods. But the real solution may need to be in Perl, especially if using mobile windows OS where the system tools currently being used may not be present. 4. Adding JNA jar files, jna.jar and jna-platform.jar of version 4.1.0 (2013), to gli's lib. These are needed to get the PID of a running process on Windows. 5. Need to tidy up code, need to check linux is not affected by platform specific import statements used by Windows, need to make the process termination recursive (not just children, but all descendants need to be killed on destroyProcess(p). Rather than wmic delete or taskkill or tskill, can use the linked Java code Processes.java which is dependent on JNA.
File size: 40.3 KB
Line 
1package org.greenstone.gatherer.util;
2
3import java.io.BufferedReader;
4import java.io.BufferedWriter;
5import java.io.Closeable;
6import java.io.File;
7import java.io.InputStream;
8import java.io.InputStreamReader;
9import java.io.IOException;
10import java.io.OutputStream;
11import java.io.OutputStreamWriter;
12import java.util.Arrays;
13import java.util.Scanner;
14
15import com.sun.jna.*;
16import com.sun.jna.platform.win32.Kernel32;
17import com.sun.jna.platform.win32.WinNT;
18
19import java.lang.reflect.Field;
20//import java.lang.reflect.Method;
21
22import org.apache.log4j.*;
23
24import org.greenstone.gatherer.DebugStream;
25
26// Use this class to run a Java Process. It follows the good and safe practices at
27// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
28// to avoid blocking problems that can arise from a Process' input and output streams.
29
30public class SafeProcess {
31 //public static int DEBUG = 0;
32
33 public static final int STDERR = 0;
34 public static final int STDOUT = 1;
35 public static final int STDIN = 2;
36
37 // charset for reading process stderr and stdout streams
38 //public static final String UTF8 = "UTF-8";
39
40 ///static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
41
42 // input to SafeProcess and initialising it
43 private String command = null;
44 private String[] command_args = null;
45 private String[] envp = null;
46 private File dir = null;
47 private String inputStr = null;
48 private Process process = null;
49 private boolean forciblyTerminateProcess = false;
50
51 // output from running SafeProcess.runProcess()
52 private String outputStr = "";
53 private String errorStr = "";
54 private int exitValue = -1;
55 //private String charset = null;
56
57 // allow callers to process exceptions of the main process thread if they want
58 private ExceptionHandler exceptionHandler = null;
59
60 // whether std/err output should be split at new lines
61 private boolean splitStdOutputNewLines = false;
62 private boolean splitStdErrorNewLines = false;
63
64 // call one of these constructors
65
66 // cmd args version
67 public SafeProcess(String[] cmd_args)
68 {
69 command_args = cmd_args;
70 }
71
72 // cmd string version
73 public SafeProcess(String cmdStr)
74 {
75 command = cmdStr;
76 }
77
78 // cmd args with env version, launchDir can be null.
79 public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
80 {
81 command_args = cmd_args;
82 envp = envparams;
83 dir = launchDir;
84 }
85
86 // The important methods:
87 // to get the output from std err and std out streams after the process has been run
88 public String getStdOutput() { return outputStr; }
89 public String getStdError() { return errorStr; }
90 public int getExitValue() { return exitValue; }
91
92 //public void setStreamCharSet(String charset) { this.charset = charset; }
93
94 // set any string to send as input to the process spawned by SafeProcess
95 public void setInputString(String sendStr) {
96 inputStr = sendStr;
97 }
98
99 // register a SafeProcess ExceptionHandler whose gotException() method will
100 // get called for each exception encountered
101 public void setExceptionHandler(ExceptionHandler exception_handler) {
102 exceptionHandler = exception_handler;
103 }
104
105 // set if you want the std output or err output to have \n at each newline read from the stream
106 public void setSplitStdOutputNewLines(boolean split) {
107 splitStdOutputNewLines = split;
108 }
109 public void setSplitStdErrorNewLines(boolean split) {
110 splitStdErrorNewLines = split;
111 }
112
113 private Process doRuntimeExec() throws IOException {
114 Process prcs = null;
115 Runtime rt = Runtime.getRuntime();
116
117 if(this.command != null) {
118 log("SafeProcess running: " + command);
119 prcs = rt.exec(this.command);
120 }
121 else { // at least command_args must be set now
122
123 // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
124 log("SafeProcess running: " + Arrays.toString(command_args));
125
126 if(this.envp == null) {
127 prcs = rt.exec(this.command_args);
128 } else { // launch process using cmd str with env params
129
130 if(this.dir == null) {
131 //log("\twith: " + Arrays.toString(this.envp));
132 prcs = rt.exec(this.command_args, this.envp);
133 } else {
134 //log("\tfrom directory: " + this.dir);
135 //log("\twith: " + Arrays.toString(this.envp));
136 prcs = rt.exec(this.command_args, this.envp, this.dir);
137 }
138 }
139 }
140
141 return prcs;
142 }
143
144 // Copied from gli's gui/FormatConversionDialog.java
145 private int waitForWithStreams(SafeProcess.OutputStreamGobbler inputGobbler,
146 SafeProcess.InputStreamGobbler outputGobbler,
147 SafeProcess.InputStreamGobbler errorGobbler)
148 throws IOException, InterruptedException
149 {
150 // kick off the stream gobblers
151 inputGobbler.start();
152 errorGobbler.start();
153 outputGobbler.start();
154
155 // any error???
156 try {
157 this.exitValue = process.waitFor(); // can throw an InterruptedException if process did not terminate
158 } catch(InterruptedException ie) {
159 log("*** Process interrupted (InterruptedException). Expected to be a Cancel operation.");
160 // don't print stacktrace: an interrupt here is not an error, it's expected to be a cancel action
161 if(exceptionHandler != null) {
162 exceptionHandler.gotException(ie);
163 }
164
165 // propagate interrupts to worker threads here
166 // unless the interrupt emanated from any of them in any join(),
167 // which will be caught by caller's catch on InterruptedException.
168 // Only if the thread that SafeProcess runs in was interrupted
169 // should we propagate the interrupt to the worker threads.
170 // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
171 // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
172 // But SafeProcess owns the worker threads, so it has every right to interrupt them
173 // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
174
175 // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
176 // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
177 // "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."
178 // Does that mean that since this code implements this thread's interruption policy, it's ok
179 // to swallow the interrupt this time and not let it propagate by commenting out the next line?
180 //Thread.currentThread().interrupt(); // re-interrupt the thread
181
182 inputGobbler.interrupt();
183 errorGobbler.interrupt();
184 outputGobbler.interrupt();
185
186 // Since we have been cancelled (InterruptedException), or on any Exception, we need
187 // to forcibly terminate process eventually after the finally code first waits for each worker thread
188 // to die off. Don't set process=null until after we've forcibly terminated it if needs be.
189 this.forciblyTerminateProcess = true;
190
191 // even after the interrupts, we want to proceed to calling join() on all the worker threads
192 // in order to wait for each of them to die before attempting to destroy the process if it
193 // still hasn't terminated after all that.
194 } finally {
195
196 //log("Process exitValue: " + exitValue);
197
198 // From the comments of
199 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
200 // To avoid running into nondeterministic failures to get the process output
201 // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
202 // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
203
204 // Wait for each of the threads to die, before attempting to destroy the process
205 // Any of these can throw InterruptedExceptions too
206 // and will be processed by the calling function's catch on InterruptedException.
207 // However, no one besides us will interrupting these threads I think...
208 // and we won't be throwing the InterruptedException from within the threads...
209 // So if any streamgobbler.join() call throws an InterruptedException, that would be unexpected
210
211 outputGobbler.join();
212 errorGobbler.join();
213 inputGobbler.join();
214
215
216 // set the variables that the code which created a SafeProcess object may want to inspect
217 this.outputStr = outputGobbler.getOutput();
218 this.errorStr = errorGobbler.getOutput();
219 }
220
221 // Don't return from finally, it's considered an abrupt completion and exceptions are lost, see
222 // http://stackoverflow.com/questions/18205493/can-we-use-return-in-finally-block
223 return this.exitValue;
224 }
225
226
227 public synchronized boolean processRunning() {
228 if(process == null) return false;
229 return SafeProcess.processRunning(this.process);
230 }
231
232 // Run a very basic process: with no reading from or writing to the Process' iostreams,
233 // this just execs the process and waits for it to return
234 public int runBasicProcess() {
235 try {
236 this.forciblyTerminateProcess = true;
237
238 // 1. create the process
239 process = doRuntimeExec();
240 // 2. basic waitFor the process to finish
241 this.exitValue = process.waitFor();
242
243 this.forciblyTerminateProcess = false;
244 } catch(IOException ioe) {
245
246 if(exceptionHandler != null) {
247 exceptionHandler.gotException(ioe);
248 } else {
249 log("IOException: " + ioe.getMessage(), ioe);
250 }
251 } catch(InterruptedException ie) {
252
253 if(exceptionHandler != null) {
254 exceptionHandler.gotException(ie);
255 } else { // Unexpected InterruptedException, so printstacktrace
256 log("Process InterruptedException: " + ie.getMessage(), ie);
257 }
258
259 Thread.currentThread().interrupt();
260 } finally {
261
262 if( this.forciblyTerminateProcess ) {
263 destroyProcess(process); // see runProcess() below
264 }
265 process = null;
266 this.forciblyTerminateProcess = false; // reset
267 }
268 return this.exitValue;
269 }
270
271 // Runs a process with default stream processing. Returns the exitValue
272 public int runProcess() {
273 return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
274 }
275
276 // Run a process with custom stream processing (any custom handlers passed in that are null
277 // will use the default stream processing).
278 // Returns the exitValue from running the Process
279 public int runProcess(CustomProcessHandler procInHandler,
280 CustomProcessHandler procOutHandler,
281 CustomProcessHandler procErrHandler)
282 {
283 SafeProcess.OutputStreamGobbler inputGobbler = null;
284 SafeProcess.InputStreamGobbler errorGobbler = null;
285 SafeProcess.InputStreamGobbler outputGobbler = null;
286
287 try {
288 this.forciblyTerminateProcess = false;
289
290 // 1. get the Process object
291 process = doRuntimeExec();
292
293
294 // 2. create the streamgobblers and set any specified handlers on them
295
296 // PROC INPUT STREAM
297 if(procInHandler == null) {
298 // send inputStr to process. The following constructor can handle inputStr being null
299 inputGobbler = // WriterToProcessInputStream
300 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
301 } else { // user will do custom handling of process' InputStream
302 inputGobbler = new SafeProcess.OutputStreamGobbler(process.getOutputStream(), procInHandler);
303 }
304
305 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
306 if(procErrHandler == null) {
307 errorGobbler // ReaderFromProcessOutputStream
308 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines);
309 } else {
310 errorGobbler
311 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), procErrHandler);
312 }
313
314 // PROC OUT STREAM to monitor for the expected std output line(s)
315 if(procOutHandler == null) {
316 outputGobbler
317 = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines);
318 } else {
319 outputGobbler
320 = new SafeProcess.InputStreamGobbler(process.getInputStream(), procOutHandler);
321 }
322
323
324 // 3. kick off the stream gobblers
325 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
326
327 } catch(IOException ioe) {
328 this.forciblyTerminateProcess = true;
329
330 if(exceptionHandler != null) {
331 exceptionHandler.gotException(ioe);
332 } else {
333 log("IOexception: " + ioe.getMessage(), ioe);
334 }
335 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so print stack trace
336 this.forciblyTerminateProcess = true;
337
338 if(exceptionHandler != null) {
339 exceptionHandler.gotException(ie);
340 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
341 } else {
342 log("*** Unexpected InterruptException when waiting for process stream gobblers to die:" + ie.getMessage(), ie);
343 }
344
345 // see comments in other runProcess()
346 Thread.currentThread().interrupt();
347
348 } finally {
349 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
350 //log("*** In finally of SafeProcess.runProcess(3 params): " + cmd);
351
352 if( this.forciblyTerminateProcess ) {
353 log("*** Going to call process.destroy 2");
354 destroyProcess(process);
355 log("*** Have called process.destroy 2");
356 }
357 process = null;
358 this.forciblyTerminateProcess = false; // reset
359 }
360
361 return this.exitValue;
362 }
363
364 public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
365 {
366 SafeProcess.OutputStreamGobbler inputGobbler = null;
367 SafeProcess.InputStreamGobbler errorGobbler = null;
368 SafeProcess.InputStreamGobbler outputGobbler = null;
369
370 try {
371 this.forciblyTerminateProcess = false;
372
373 // 1. get the Process object
374 process = doRuntimeExec();
375
376
377 // 2. create the streamgobblers and set any specified handlers on them
378
379 // PROC INPUT STREAM
380 // send inputStr to process. The following constructor can handle inputStr being null
381 inputGobbler = // WriterToProcessInputStream
382 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
383
384 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
385 errorGobbler // ReaderFromProcessOutputStream
386 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
387 // PROC OUT STREAM to monitor for the expected std output line(s)
388 outputGobbler
389 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
390
391
392 // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
393 if(outLineByLineHandler != null) {
394 outputGobbler.setLineByLineHandler(outLineByLineHandler);
395 }
396 if(errLineByLineHandler != null) {
397 errorGobbler.setLineByLineHandler(errLineByLineHandler);
398 }
399
400
401 // 4. kick off the stream gobblers
402 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
403
404 } catch(IOException ioe) {
405 this.forciblyTerminateProcess = true;
406
407 if(exceptionHandler != null) {
408 exceptionHandler.gotException(ioe);
409 } else {
410 log("IOexception: " + ioe.getMessage(), ioe);
411 }
412 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so log it
413 this.forciblyTerminateProcess = true;
414
415 if(exceptionHandler != null) {
416 exceptionHandler.gotException(ie);
417 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
418 } else {
419 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
420 }
421 // We're not causing any interruptions that may occur when trying to stop the worker threads
422 // So resort to default behaviour in this catch?
423 // "On catching InterruptedException, re-interrupt the thread."
424 // This is just how InterruptedExceptions tend to be handled
425 // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
426 // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
427 Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
428
429 } finally {
430
431 // Moved into here from GS2PerlConstructor and GShell.runLocal() which said
432 // "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!)"
433 // http://steveliles.github.io/invoking_processes_from_java.html
434 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
435 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
436
437 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
438 //log("*** In finally of SafeProcess.runProcess(2 params): " + cmd);
439
440 if( this.forciblyTerminateProcess ) {
441 log("*** Going to call process.destroy 1");
442 destroyProcess(process);
443 log("*** Have called process.destroy 1");
444 }
445 process = null;
446 this.forciblyTerminateProcess = false; //reset
447 }
448
449 return this.exitValue;
450 }
451
452// stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
453// (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
454// stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
455// Searching for: "forcibly terminate external process launched by Java on Windows"
456static void killAnyPerl() {
457 if(!Utility.isWindows()) return;
458
459 // only on Windows do we need to still kill perl despite process.destroy()
460 try{
461 //Runtime.getRuntime().exec("taskkill /f /im perl.exe");
462 SafeProcess proc = new SafeProcess("taskkill /f /im perl.exe");
463 // /f Specifies to forcefully terminate the process(es).
464 // /t Terminates the specified process and any child processes which were started by it.
465 // /im is the image name (the name of the program), see Image Name column in Win Task Manager.
466 int exitValue = proc.runBasicProcess(); // don't care about IOstreams
467 } catch(Exception e) {
468 log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
469 }
470}
471
472// http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
473// http://www.golesny.de/p/code/javagetpid
474// Need to use JNA (Java Native Access), http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
475// Got 4.1.0 from https://mvnrepository.com/artifact/net.java.dev.jna/jna/4.1.0 and https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform/4.1.0
476// (see also https://mvnrepository.com/artifact/net.java.dev.jna/jna), since github's GettingStarted doc talks about downloading jna.jar but never provides it.
477// See https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
478public static long getProcessID(Process p)
479{
480 long pid = -1;
481 try
482 {
483 //for windows
484 if (p.getClass().getName().equals("java.lang.Win32Process") ||
485 p.getClass().getName().equals("java.lang.ProcessImpl"))
486 {
487 Field f = p.getClass().getDeclaredField("handle");
488 f.setAccessible(true);
489 long handl = f.getLong(p);
490 Kernel32 kernel = Kernel32.INSTANCE;
491 WinNT.HANDLE hand = new WinNT.HANDLE();
492 hand.setPointer(Pointer.createConstant(handl));
493 pid = kernel.GetProcessId(hand);
494 f.setAccessible(false);
495 }
496 //for unix based operating systems
497 else if (p.getClass().getName().equals("java.lang.UNIXProcess"))
498 {
499 Field f = p.getClass().getDeclaredField("pid");
500 f.setAccessible(true);
501 pid = f.getLong(p);
502 f.setAccessible(false);
503 }
504 }
505 catch(Exception ex)
506 {
507 log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex);
508 pid = -1;
509 }
510 return pid;
511}
512
513// UNUSED - only kills toplevel process, same as p.destroy().
514// Call destroyProcess(p) instead to kill any childprocesses launched
515static void killProcessIfOnWindows(Process p) {
516 if(!Utility.isWindows()) return;
517
518 // only on Windows do we need to still kill perl despite process.destroy()
519 // http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
520 // http://www.golesny.de/p/code/javagetpid
521 long processID = getProcessID(p);
522
523 SafeProcess.killWinProcessWithID(processID);
524}
525
526
527// Eventually move to using https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java ?
528// Works with Apache license, http://www.apache.org/licenses/LICENSE-2.0
529
530
531// stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
532// (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
533// stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
534// Searching for: "forcibly terminate external process launched by Java on Windows"
535static void killWinProcessWithID(long processID) {
536
537 // check if we have taskkill or tskill
538 String cmd = null;
539 if (SafeProcess.isAvailable("wmic")) {
540 // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229
541 cmd = "wmic process " + processID + " delete"; // like "kill -9" on Windows
542 } else if(SafeProcess.isAvailable("taskkill")) {
543 cmd = "taskkill /f /t /PID " + processID;
544 } else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed
545 cmd = "tskill " + processID; // https://ss64.com/nt/tskill.html
546 }
547 cmd = "tskill " + processID;
548
549 try{
550 // Despite the /T flag, the following still doesn't kill subprocesses. Taskkill is also not available
551 // universally. Try alternates tskill and pskill, though they too don't kill further processes launched by p.
552 log("Attempting to taskkil process with ID " + processID);
553 //Runtime.getRuntime().exec("taskkill /f /t /PID " + processID);
554 SafeProcess proc = new SafeProcess(cmd);
555 // /f Specifies to forcefully terminate the process(es).
556 // /t Terminates the specified process and any child processes which were started by it.
557 int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete" there is output that needs flushing
558
559 } catch(Exception e) {
560 log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
561 }
562}
563
564// On linux and mac, p.destroy() suffices to kill processes launched by p as well.
565// On Windows we need to do more work, since processes launched by p remain around executing until termination otherwise.
566// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until termination.
567static void destroyProcess(Process p) {
568 //killProcessIfOnWindows(p); // doesn't work: only terminates first level process launched, same as p.destroy()
569 //p.destroy();
570 //killAnyPerl(); // kills any running perl, even those not launched by GS
571
572 if(!Utility.isWindows()) {
573 p.destroy();
574 return;
575 }
576
577
578
579 // We don't want to kill all perl running processes. Just the ones whose command we sort of know and can see in TaskManager
580 // under the "Command Line" column of the Processes tab.
581 // This is possible with wmic, available since xp. First check wmic is really present on this Windows machine
582 // https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line
583 if(!SafeProcess.isAvailable("wmic")) {
584 log("wmic used to kill subprocesses is not available. Unable to terminate subprocesses...");
585 log("Kill them manually from the TaskManager or they will proceed to run to termination");
586
587 // At least we can get rid of the top level process we launched
588 p.destroy();
589 return;
590 }
591
592
593 // get the process id of the process we launched
594 long processID = SafeProcess.getProcessID(p);
595
596
597 // What we want to be able to do on Windows is to kill a process by its command line as seen in Task Manager for that process.
598 // full-import.pl launches import.pl, full-build will launch buildcol.pl and activate.pl.
599 // These tend to have the collection name still at the end, so we can terminate a process knowing the collection name, since
600 // we can use wildcards in our wmic termination command.
601 // However, lucene_passes.pl is launched by buildcol.pl and doesn't have the collection name in its launching cmd line.
602
603 // Works beatufilly to kill any CommandLine (as seen in TaskManager) that matches
604 //C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate
605 // https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/
606 // "WMIC Wildcard Search using like and %"
607 // All subsequent perl processes launched by Greenstone are launched with bin\windows\perl\bin\perl. So this works well:
608 // wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate
609 // However do we want to terminate all perl processes launched by Greenstone, or only those that we can infer were spawned by
610 // the SafeProcess that we've been running?
611
612 /*
613 // Works in terminal, but when run through GLI, GLI is terminated too...
614 try{
615
616 log("Attempting to use Windows to terminate sub processes");
617 Runtime.getRuntime().exec("wmic PATH win32_process Where \"CommandLine like '%bin%windows%perl%bin%perl%'\" Call Terminate");
618 } catch(Exception e) {
619 log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
620 }
621 */
622
623 // https://superuser.com/questions/851692/track-which-program-launches-a-certain-process
624 // Can get parent process id and process id, and then kill those with child process id
625 // http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows
626 // Can get the pids of all childprocesses launched by parent process with parent pid.
627 // And vice versa:
628 // e.g. if you know the parent pid and want to know all the pids of the child processes spawned:
629 // wmic process where (parentprocessid=596) get processid
630 // if you know a child process id and want to know the parent's id:
631 // wmic process where (processid=180) get parentprocessid
632
633 String[] child_pids = null;
634
635
636 log("Attempting to use Windows to terminate sub processes");
637 SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+processID+") get processid");
638 proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n
639 // Output looks like:
640 // ProcessId
641 // 6040
642 // 180
643 // 4948
644 // 1084
645 // 6384
646 // If no children, then STDERR output starts with the following, possibly succeeded by empty lines:
647 // No Instance(s) Available.
648 int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way.
649 log("@@@@ Return value from proc: " + exitValue);
650 String stdOutput = proc.getStdOutput();
651 // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split
652
653 log("@@@@ Got output:\n" + stdOutput);
654 if(stdOutput.indexOf("No Instance(s) Available.") == -1) {
655 //int start = stdOutput.indexOf("\n"); // skip first line, which is the column name "ProcessId"
656 //stdOutput = stdOutput.substring(start+1);
657 //child_pids = stdOutput.split("/(\\r?\\n/)+"); // get rid of newline, so we should only have a list of Long numbers
658
659 // Now we can get rid of the top level process
660 p.destroy();
661 // find all childprocesses for that pid and terminate them too:
662 Scanner sc = new Scanner(stdOutput);
663 while (sc.hasNext()) {
664 if(!sc.hasNextLong()) {
665 sc.next(); // discard the current token since it's not a Long
666 } else {
667 long child_pid = sc.nextLong();
668 log("Attempting to terminate child pid: " + child_pid);
669 SafeProcess.killWinProcessWithID(child_pid);
670 }
671 }
672 sc.close();
673 } else {
674 // No child process. Only the top level process needs to be got rid of:
675 p.destroy();
676
677 }
678
679 /*
680
681 // Now we can get rid of the top level process
682 p.destroy();
683
684
685 // Then kill each subprocess.
686
687 if(child_pids != null) {
688 for(int i = 0; i < child_pids.length; i++) {
689 log("Attempting to terminate child pid: |" + child_pids[i] + "|");
690 SafeProcess.killWinProcessWithID(Long.parseLong(child_pids[i].trim()));
691 }
692 }
693 */
694
695}
696
697
698// Run `which` on a program to find out if it is available. which.exe is included in winbin.
699// On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same.
700// where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html
701// There is no `where` on Linux/Mac, must use which for them.
702// On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names.
703public static boolean isAvailable(String program) {
704 try {
705 // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in"
706 // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed
707 SafeProcess prcs = new SafeProcess("which " + program);
708 prcs.runProcess();
709 String output = prcs.getStdOutput();
710 if(output.equals("")) {
711 return false;
712 }
713 //System.err.println("*** 'which " + program + "' returned: " + output);
714 return true;
715 } catch (Exception exc) {
716 return false;
717 }
718}
719
720// Google Java external process destroy kill subprocesses
721// https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
722
723//******************** Inner class and interface definitions ********************//
724// Static inner classes can be instantiated without having to instantiate an object of the outer class first
725
726// Can have public static interfaces too,
727// see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
728// Implementors need to take care that the implementations are thread safe
729// http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
730public static interface ExceptionHandler {
731
732 // when implementing ExceptionHandler.gotException(), if it manipulates anything that's
733 // not threadsafe, declare gotException() as a synchronized method to ensure thread safety
734 public void gotException(Exception e); // can't declare as synchronized in interface method declaration
735}
736
737// Write your own run() body for any StreamGobbler. You need to create an instance of a class
738// extending CustomProcessHandler for EACH IOSTREAM of the process that you want to handle.
739// Do not create a single CustomProcessHandler instance and reuse it for all three streams,
740// i.e. don't call SafeProcess' runProcess(x, x, x); It should be runProcess(x, y, z).
741// Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams
742// example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class.
743// CustomProcessHandler is made an abstract class instead of an interface to force classes that want
744// to use a CustomProcessHandler to create a separate class that extends CustomProcessHandler, rather than
745// that the classes that wish to use it "implementing" the CustomProcessHandler interface itself: the
746// CustomProcessHandler.run() method may then be called in the major thread from which the Process is being
747// executed, rather than from the individual threads that deal with each iostream of the Process.
748public static abstract class CustomProcessHandler {
749
750 protected final int source;
751
752 protected CustomProcessHandler(int src) {
753 this.source = src; // STDERR or STDOUT or STDIN
754 }
755
756 public String getThreadNamePrefix() {
757 return SafeProcess.streamToString(this.source);
758 }
759
760 public abstract void run(Closeable stream); //InputStream or OutputStream
761}
762
763// When using the default stream processing to read from a process' stdout or stderr stream, you can
764// create a class extending LineByLineHandler for the process' err stream and one for its output stream
765// to do something on a line by line basis, such as sending the line to a log
766public static abstract class LineByLineHandler {
767 protected final int source;
768
769 protected LineByLineHandler(int src) {
770 this.source = src; // STDERR or STDOUT
771 }
772
773 public String getThreadNamePrefix() {
774 return SafeProcess.streamToString(this.source);
775 }
776
777 public abstract void gotLine(String line); // first non-null line
778 public abstract void gotException(Exception e); // for when an exception occurs instead of getting a line
779}
780
781
782//**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
783
784// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
785// This class is used in FormatConversionDialog to properly read from the stdout and stderr
786// streams of a Process, Process.getInputStream() and Process.getErrorSream()
787public static class InputStreamGobbler extends Thread
788{
789 private InputStream is = null;
790 private StringBuffer outputstr = new StringBuffer();
791 private boolean split_newlines = false;
792 private CustomProcessHandler customHandler = null;
793 private LineByLineHandler lineByLineHandler = null;
794
795 protected InputStreamGobbler() {
796 super("InputStreamGobbler");
797 }
798
799 public InputStreamGobbler(InputStream is)
800 {
801 this(); // sets thread name
802 this.is = is;
803 this.split_newlines = false;
804 }
805
806 public InputStreamGobbler(InputStream is, boolean split_newlines)
807 {
808 this(); // sets thread name
809 this.is = is;
810 this.split_newlines = split_newlines;
811
812 }
813
814 public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler)
815 {
816 this(); // thread name
817 this.is = is;
818 this.customHandler = customHandler;
819 this.adjustThreadName(customHandler.getThreadNamePrefix());
820 }
821
822
823 private void adjustThreadName(String prefix) {
824 this.setName(prefix + this.getName());
825 }
826
827 public void setLineByLineHandler(LineByLineHandler lblHandler) {
828 this.lineByLineHandler = lblHandler;
829 this.adjustThreadName(lblHandler.getThreadNamePrefix());
830 }
831
832 // default run() behaviour
833 public void runDefault()
834 {
835 BufferedReader br = null;
836 try {
837 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
838 String line=null;
839 while ( !this.isInterrupted() && (line = br.readLine()) != null ) {
840
841 //log("@@@ GOT LINE: " + line);
842 outputstr.append(line);
843 if(split_newlines) {
844 outputstr.append(Utility.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
845 }
846
847 if(lineByLineHandler != null) { // let handler deal with newlines
848 lineByLineHandler.gotLine(line);
849 }
850 }
851
852 } catch (IOException ioe) {
853 if(lineByLineHandler != null) {
854 lineByLineHandler.gotException(ioe);
855 } else {
856 log("Exception when reading process stream with " + this.getName() + ": ", ioe);
857 }
858 } finally {
859 if(this.isInterrupted()) {
860 log("@@@ Successfully interrupted " + this.getName() + ".");
861 }
862 SafeProcess.closeResource(br);
863 }
864 }
865
866 public void runCustom() {
867 this.customHandler.run(is);
868 }
869
870 public void run() {
871 if(this.customHandler == null) {
872 runDefault();
873 } else {
874 runCustom();
875 }
876 }
877
878 public String getOutput() {
879 return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
880 }
881} // end static inner class InnerStreamGobbler
882
883
884// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
885// This class is used in FormatConversionDialog to properly write to the inputstream of a Process
886// Process.getOutputStream()
887public static class OutputStreamGobbler extends Thread
888{
889 private OutputStream os = null;
890 private String inputstr = "";
891 private CustomProcessHandler customHandler = null;
892
893 protected OutputStreamGobbler() {
894 super("stdinOutputStreamGobbler"); // thread name
895 }
896
897 public OutputStreamGobbler(OutputStream os) {
898 this(); // set thread name
899 this.os = os;
900 }
901
902 public OutputStreamGobbler(OutputStream os, String inputstr)
903 {
904 this(); // set thread name
905 this.os = os;
906 this.inputstr = inputstr;
907 }
908
909 public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) {
910 this(); // set thread name
911 this.os = os;
912 this.customHandler = customHandler;
913 }
914
915 // default run() behaviour
916 public void runDefault() {
917
918 if (inputstr == null) {
919 return;
920 }
921
922 // also quit if the process was interrupted before we could send anything to its stdin
923 if(this.isInterrupted()) {
924 log(this.getName() + " thread was interrupted.");
925 return;
926 }
927
928 BufferedWriter osw = null;
929 try {
930 osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
931 //System.out.println("@@@ SENDING LINE: " + inputstr);
932 osw.write(inputstr, 0, inputstr.length());
933 osw.newLine();//osw.write("\n");
934 osw.flush();
935
936 // Don't explicitly send EOF when using StreamGobblers as below,
937 // as the EOF char is echoed to output.
938 // Flushing the write handle and/or closing the resource seems
939 // to already send EOF silently.
940
941 /*if(Utility.isWindows()) {
942 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
943 } else { // EOF on Linux/Mac is Ctrl-D
944 osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
945 }
946 osw.flush();
947 */
948 } catch (IOException ioe) {
949 log("Exception writing to SafeProcess' inputstream: ", ioe);
950 } finally {
951 SafeProcess.closeResource(osw);
952 }
953 }
954
955 // call the user's custom handler for the run() method
956 public void runCustom() {
957 this.customHandler.run(os);
958 }
959
960 public void run()
961 {
962 if(this.customHandler == null) {
963 runDefault();
964 } else {
965 runCustom();
966 }
967 }
968} // end static inner class OutputStreamGobbler
969
970//**************** Static methods **************//
971
972
973 // logger and DebugStream print commands are synchronized, therefore thread safe.
974 public static void log(String msg) {
975 //logger.info(msg);
976
977 System.err.println(msg);
978
979 //DebugStream.println(msg);
980 }
981
982 public static void log(String msg, Exception e) { // Print stack trace on the exception
983 //logger.error(msg, e);
984
985 System.err.println(msg);
986 e.printStackTrace();
987
988 //DebugStream.println(msg);
989 //DebugStream.printStackTrace(e);
990 }
991
992 public static void log(Exception e) {
993 //logger.error(e);
994
995 e.printStackTrace();
996
997 //DebugStream.printStackTrace(e);
998 }
999
1000 public static void log(String msg, Exception e, boolean printStackTrace) {
1001 if(printStackTrace) {
1002 log(msg, e);
1003 } else {
1004 log(msg);
1005 }
1006 }
1007
1008 public static String streamToString(int src) {
1009 String stream;
1010 switch(src) {
1011 case STDERR:
1012 stream = "stderr";
1013 break;
1014 case STDOUT:
1015 stream = "stdout";
1016 break;
1017 default:
1018 stream = "stdin";
1019 }
1020 return stream;
1021 }
1022
1023//**************** Useful static methods. Copied from GLI's Utility.java ******************
1024 // For safely closing streams/handles/resources.
1025 // For examples of use look in the Input- and OutputStreamGobbler classes.
1026 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
1027 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
1028 public static boolean closeResource(Closeable resourceHandle) {
1029 boolean success = false;
1030 try {
1031 if(resourceHandle != null) {
1032 resourceHandle.close();
1033 resourceHandle = null;
1034 success = true;
1035 }
1036 } catch(Exception e) {
1037 log("Exception closing resource: " + e.getMessage(), e);
1038 resourceHandle = null;
1039 success = false;
1040 } finally {
1041 return success;
1042 }
1043 }
1044
1045 public static boolean closeProcess(Process prcs) {
1046 boolean success = true;
1047 if( prcs != null ) {
1048 success = success && closeResource(prcs.getErrorStream());
1049 success = success && closeResource(prcs.getOutputStream());
1050 success = success && closeResource(prcs.getInputStream());
1051 prcs.destroy();
1052 }
1053 return success;
1054 }
1055
1056// Moved from GShell.java
1057 /** 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
1058 * @param process the Process to test
1059 * @return true if it is still executing, false otherwise
1060 */
1061 static public boolean processRunning(Process process) {
1062 boolean process_running = false;
1063
1064 try {
1065 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
1066 }
1067 catch(IllegalThreadStateException itse) {
1068 process_running = true;
1069 }
1070 catch(Exception exception) {
1071 log(exception); // DebugStream.printStackTrace(exception);
1072 }
1073 return process_running;
1074 }
1075
1076} // end class SafeProcess
Note: See TracBrowser for help on using the repository browser.