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