- Timestamp:
- 2017-05-18T20:38:56+12:00 (7 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java
r31664 r31692 10 10 import java.io.OutputStream; 11 11 import java.io.OutputStreamWriter; 12 import java.net.Socket; 12 13 import java.util.Arrays; 13 14 import java.util.Scanner; 14 15 import java.util.Stack; 16 import javax.swing.SwingUtilities; 17 15 18 16 19 import com.sun.jna.*; … … 39 42 public static final int STDIN = 2; 40 43 // 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 44 public static String WIN_KILL_CMD; 45 46 /** 47 * Boolean interruptible is used to mark any sections of blocking code that should not be interrupted 48 * with an InterruptedExceptions. At present only the cancelRunningProcess() attempts to do such a thing 49 * and avoids doing so when interruptible is false. 50 * Note that interruptible is also used as a lock, so remember to synchronize on it when using it! 51 */ 52 public Boolean interruptible = Boolean.TRUE; 43 53 44 54 // charset for reading process stderr and stdout streams … … 55 65 private Process process = null; 56 66 private boolean forciblyTerminateProcess = false; 67 68 /** a ref to the thread in which the Process is being executed (the thread wherein Runtime.exec() is called) */ 69 private Thread theProcessThread = null; 57 70 58 71 // output from running SafeProcess.runProcess() … … 64 77 // allow callers to process exceptions of the main process thread if they want 65 78 private ExceptionHandler exceptionHandler = null; 79 /** allow callers to implement hooks that get called during the main phases of the internal 80 * process' life cycle, such as before and after process.destroy() gets called 81 */ 82 private MainProcessHandler mainHandler = null; 66 83 67 84 // whether std/err output should be split at new lines … … 110 127 } 111 128 129 /** to set a handler that will handle the main (SafeProcess) thread, 130 * implementing the hooks that will get called during the internal process' life cycle, 131 * such as before and after process.destroy() is called */ 132 public void setMainHandler(MainProcessHandler handler) { 133 this.mainHandler = handler; 134 } 135 112 136 // set if you want the std output or err output to have \n at each newline read from the stream 113 137 public void setSplitStdOutputNewLines(boolean split) { … … 118 142 } 119 143 144 145 /* 146 public boolean canInterrupt() { 147 boolean canInterrupt; 148 synchronized(interruptible) { 149 canInterrupt = interruptible.booleanValue(); 150 } 151 return canInterrupt; 152 } 153 */ 154 155 /** 156 * Call this method when you want to prematurely and safely terminate any process 157 * that SafeProcess may be running. 158 * You may want to implement the SafeProcess.MainHandler interface to write code 159 * for any hooks that will get called during the process' life cycle. 160 * @return false if process has already terminated or if it was already terminating 161 * when cancel was called. In such cases no interrupt is sent. Returns boolean sentInterrupt. 162 */ 163 public synchronized boolean cancelRunningProcess() { 164 // on interrupt: 165 // - forciblyTerminate will be changed to true if the interrupt came in when the process was 166 // still running (and so before the process' streams were being joined) 167 // - and forciblyTerminate will still remain false if the interrupt happens when the process' 168 // streams are being/about to be joined (hence after the process naturally terminated). 169 // So we don't touch the value of this.forciblyTerminate here. 170 // The value of forciblyTerminate determines whether Process.destroy() and its associated before 171 // and after handlers are called or not: we don't bother destroying the process if it came to 172 // a natural end. 173 174 // no process to interrupt, so we're done 175 if(this.process == null) { 176 log("@@@ No Java process to interrupt."); 177 return false; 178 } 179 180 boolean sentInterrupt = false; 181 182 // can't interrupt when SafeProcess is joining (cleanly terminating) worker threads 183 // have to wait until afterward 184 if (interruptible) { 185 // either way, we can now interrupt the thread - if we have one (we should) 186 if(this.theProcessThread != null) { // we're told which thread should be interrupted 187 this.theProcessThread.interrupt(); 188 log("@@@ Successfully sent interrupt to process."); 189 sentInterrupt = true; 190 } 191 } 192 else { // wait for join()s to finish. 193 // During and after joining(), there's no need to interrupt any more anyway: no calls 194 // subsequent to joins() block, so everything thereafter is insensitive to InterruptedExceptions. 195 196 if(SwingUtilities.isEventDispatchThread()) { 197 log("#### Event Dispatch thread, returning"); 198 return false; 199 } 200 201 while(!interruptible) { 202 203 log("######### Waiting for process to become interruptible..."); 204 205 // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html 206 // wait will release lock on this object, and regain it when loop condition interruptible is true 207 try { 208 this.wait(); // can't interrupt when SafeProcess is joining (cleanly terminating) worker threads, so wait 209 } catch(Exception e) { 210 log("@@@ Interrupted exception while waiting for SafeProcess' worker threads to finish joining on cancelling process"); 211 } 212 } 213 214 // now the process is sure to have ended as the worker threads would have been joined 215 } 216 217 return sentInterrupt; 218 } 219 220 120 221 // In future, think of changing the method doRuntimeExec() over to using ProcessBuilder 121 222 // instead of Runtime.exec(). ProcessBuilder seems to have been introduced from Java 5. 122 223 // https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html 123 // https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/224 // See also https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/ 124 225 // which suggests using Apache Common Exec to launch processes and says what will be forthcoming in Java 9 125 226 … … 159 260 } 160 261 262 this.theProcessThread = Thread.currentThread(); // store a ref to the thread wherein the Process is being run 161 263 return prcs; 162 264 } … … 173 275 outputGobbler.start(); 174 276 175 // any error???176 277 try { 177 this.exitValue = process.waitFor(); // can throw an InterruptedException if process did not terminate278 this.exitValue = process.waitFor(); // can throw an InterruptedException if process was cancelled/prematurely terminated 178 279 } catch(InterruptedException ie) { 179 280 log("*** Process interrupted (InterruptedException). Expected to be a Cancel operation."); … … 185 286 // propagate interrupts to worker threads here 186 287 // unless the interrupt emanated from any of them in any join(), 187 // which will be caught by caller'scatch on InterruptedException.288 // which will be caught by the calling method's own catch on InterruptedException. 188 289 // Only if the thread that SafeProcess runs in was interrupted 189 290 // should we propagate the interrupt to the worker threads. … … 215 316 216 317 //log("Process exitValue: " + exitValue); 318 ///log("@@@@ Before join phase. Forcibly terminating: " + this.forciblyTerminateProcess); 217 319 218 320 // From the comments of … … 225 327 // Any of these can throw InterruptedExceptions too 226 328 // 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(); 329 330 331 // Thread.joins() below are blocking calls, same as Process.waitFor(), and a cancel action could 332 // send an interrupt during any Join: the InterruptedException ensuing will then break out of the 333 // joins() section. We don't want that to happen: by the time the joins() start happening, the 334 // actual process has finished in some way (naturally terminated or interrupted), and nothing 335 // should interrupt the joins() (nor ideally any potential p.destroy after that). 336 // So we mark the join() section as an un-interruptible section, and make anyone who's seeking 337 // to interrupt just then first wait for this Thread (in which SafeProcess runs) to become 338 // interruptible again. Thos actually assumes anything interruptible can still happen thereafter 339 // when in reality, none of the subsequent actions after the joins() block. So they nothing 340 // thereafter, which is the cleanup phase, will actually respond to an InterruptedException. 341 342 343 if(this.mainHandler != null) { 344 // this method can unset forcible termination flag 345 // if the process had already naturally terminated by this stage: 346 this.forciblyTerminateProcess = mainHandler.beforeWaitingForStreamsToEnd(this.forciblyTerminateProcess); 347 } 348 349 ///log("@@@@ After beforeJoin Handler. Forcibly terminating: " + this.forciblyTerminateProcess); 350 351 // Anyone could interrupt/cancel during waitFor() above, 352 // but no one should interrupt while the worker threads come to a clean close, 353 // so make anyone wanting to cancel the process at this stage wait() 354 // until we're done with the join()s: 355 synchronized(interruptible) { 356 interruptible = Boolean.FALSE; 357 } 358 //Thread.sleep(5000); // Uncomment to test this uninterruptible section, also comment out block checking for 359 // EventDispatchThread in cancelRunningProcess() and 2 calls to progress.enableCancelJob() in DownloadJob.java 360 outputGobbler.join(); 232 361 errorGobbler.join(); 233 inputGobbler.join(); 234 235 362 inputGobbler.join(); 363 364 synchronized(interruptible) { 365 interruptible = Boolean.TRUE; 366 } 367 368 ///log("@@@@ Join phase done..."); 369 370 // notify any of those waiting to interrupt this thread, that they may feel free to do so again 371 // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html 372 synchronized(this) { 373 this.notify(); 374 } 375 236 376 // set the variables that the code which created a SafeProcess object may want to inspect 237 377 this.outputStr = outputGobbler.getOutput(); 238 this.errorStr = errorGobbler.getOutput(); 378 this.errorStr = errorGobbler.getOutput(); 379 380 // call the after join()s hook 381 if(this.mainHandler != null) { 382 this.forciblyTerminateProcess = mainHandler.afterStreamsEnded(this.forciblyTerminateProcess); 383 } 239 384 } 240 385 … … 283 428 284 429 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 430 } finally { 431 432 cleanUp("SafeProcess.runBasicProcess"); 292 433 } 293 434 return this.exitValue; … … 347 488 348 489 349 490 // 3. kick off the stream gobblers 350 491 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler); 351 492 … … 365 506 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die"); 366 507 } else { 367 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);508 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie); 368 509 } 369 510 … … 371 512 Thread.currentThread().interrupt(); 372 513 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 514 } finally { 515 516 cleanUp("SafeProcess.runProcess(3 params)"); 384 517 } 385 518 … … 424 557 425 558 426 // 4. kick off the stream gobblers559 // 3. kick off the stream gobblers 427 560 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler); 428 561 … … 452 585 Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop? 453 586 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 587 } finally { 588 589 cleanUp("SafeProcess.runProcess(2 params)"); 472 590 } 473 591 474 592 return this.exitValue; 593 } 594 595 private void cleanUp(String callingMethod) { 596 597 // Moved into here from GS2PerlConstructor and GShell.runLocal() which said 598 // "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!)" 599 // http://steveliles.github.io/invoking_processes_from_java.html 600 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2 601 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec 602 603 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command; 604 //log("*** In finally of " + callingMethod + ": " + cmd); 605 606 // if we're forcibly terminating the process, call the before- and afterDestroy hooks 607 // besides actually destroying the process 608 if( this.forciblyTerminateProcess ) { 609 log("*** Going to call process.destroy from " + callingMethod); 610 611 if(mainHandler != null) mainHandler.beforeProcessDestroy(); 612 SafeProcess.destroyProcess(process); // see runProcess(2 args/3 args) 613 if(mainHandler != null) mainHandler.afterProcessDestroy(); 614 615 log("*** Have called process.destroy from " + callingMethod); 616 } 617 618 process = null; 619 this.theProcessThread = null; // let the process thread ref go too 620 boolean wasForciblyTerminated = this.forciblyTerminateProcess; 621 this.forciblyTerminateProcess = false; // reset 622 623 if(mainHandler != null) mainHandler.doneCleanup(wasForciblyTerminated); 475 624 } 476 625 … … 608 757 // e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until it naturally terminates. 609 758 static void destroyProcess(Process p) { 759 log("### in SafeProcess.destroyProcess(Process p)"); 760 610 761 // If it isn't windows, process.destroy() terminates any child processes too 611 762 if(!Utility.isWindows()) { … … 626 777 // so we can use it to find the pids of any subprocesses it launched in order to terminate those too. 627 778 628 long processID = SafeProcess.getProcessID(p); 629 log("Attempting to terminate sub processes of Windows process with pid " + processID); 630 terminateSubProcessesRecursively(processID, p); 779 long processID = SafeProcess.getProcessID(p); 780 if(processID == -1) { // the process doesn't exist or no longer exists (terminated naturally?) 781 p.destroy(); // minimum step, do this anyway, at worst there's no process and this won't have any effect 782 } else { 783 log("Attempting to terminate sub processes of Windows process with pid " + processID); 784 terminateSubProcessesRecursively(processID, p); 785 } 631 786 632 787 } … … 706 861 private static String getWinProcessKillCmd(Long processID) { 707 862 // 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 863 // because of a cyclical dependency regarding this during static initialization 864 709 865 if(WIN_KILL_CMD == null) { 710 866 if(SafeProcess.isAvailable("wmic")) { … … 734 890 // where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html 735 891 // 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.892 // On windows, "which tskill" fails (and "where tskill" works), but "which" succeeds on taskkill|wmic|browser names. 737 893 public static boolean isAvailable(String program) { 738 894 try { … … 764 920 public static interface ExceptionHandler { 765 921 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 922 /** 923 * Called whenever an exception occurs during the execution of the main thread of SafeProcess 924 * (the thread in which the Process is run). 925 * Since this method can't be declared as synchronized in this interface method declaration, 926 * when implementing ExceptionHandler.gotException(), if it manipulates anything that's 927 * not threadsafe, declare gotException() as a synchronized method to ensure thread safety 928 */ 929 public void gotException(Exception e); 930 } 931 932 /** On interrupting (cancelling) a process, 933 * if the class that uses SafeProcess wants to do special handling 934 * either before and after join() is called on all the worker threads, 935 * or, only on forcible termination, before and after process.destroy() is to be called, 936 * then that class can implement this MainProcessHandler interface 937 */ 938 public static interface MainProcessHandler { 939 /** 940 * Called before the streamgobbler join()s. 941 * If not overriding, the default implementation should be: 942 * public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating) { return forciblyTerminating; } 943 * When overriding: 944 * @param forciblyTerminating is true if currently it's been decided that the process needs to be 945 * forcibly terminated. Return false if you don't want it to be. For a basic implementation, 946 * return the parameter. 947 * @return true if the process is still running and therefore still needs to be destroyed, or if 948 * you can't determine whether it's still running or not. Process.destroy() will then be called. 949 * @return false if the process has already naturally terminated by this stage. Process.destroy() 950 * won't be called, and neither will the before- and after- processDestroy methods of this class. 951 */ 952 public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating); 953 /** 954 * Called after the streamgobbler join()s have finished. 955 * If not overriding, the default implementation should be: 956 * public boolean afterStreamsEnded(boolean forciblyTerminating) { return forciblyTerminating; } 957 * When overriding: 958 * @param forciblyTerminating is true if currently it's been decided that the process needs to be 959 * forcibly terminated. Return false if you don't want it to be. For a basic implementation, 960 * return the parameter (usual case). 961 * @return true if the process is still running and therefore still needs to be destroyed, or if 962 * can't determine whether it's still running or not. Process.destroy() will then be called. 963 * @return false if the process has already naturally terminated by this stage. Process.destroy() 964 * won't be called, and neither will the before- and after- processDestroy methods of this class. 965 */ 966 public boolean afterStreamsEnded(boolean forciblyTerminating); 967 /** 968 * called after join()s and before process.destroy()/destroyProcess(Process), iff forciblyTerminating 969 */ 970 public void beforeProcessDestroy(); 971 /** 972 * Called after process.destroy()/destroyProcess(Process), iff forciblyTerminating 973 */ 974 public void afterProcessDestroy(); 975 976 /** 977 * Always called after process ended: whether it got destroyed or not 978 */ 979 public void doneCleanup(boolean wasForciblyTerminated); 769 980 } 770 981 … … 1080 1291 } 1081 1292 1293 // in Java 6, Sockets don't yet implement Closeable 1294 public static boolean closeSocket(Socket resourceHandle) { 1295 boolean success = false; 1296 try { 1297 if(resourceHandle != null) { 1298 resourceHandle.close(); 1299 resourceHandle = null; 1300 success = true; 1301 } 1302 } catch(Exception e) { 1303 log("Exception closing resource: " + e.getMessage(), e); 1304 resourceHandle = null; 1305 success = false; 1306 } finally { 1307 return success; 1308 } 1309 } 1310 1082 1311 public static boolean closeProcess(Process prcs) { 1083 1312 boolean success = true;
Note:
See TracChangeset
for help on using the changeset viewer.