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

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