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

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

Emacs tabbing and 2 extra lines of comments.

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