Changeset 31663
- Timestamp:
- 2017-05-08T18:15:32+12:00 (7 years ago)
- Location:
- main/trunk/greenstone3
- Files:
-
- 2 added
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java
r31640 r31663 11 11 import java.io.OutputStreamWriter; 12 12 import java.util.Arrays; 13 import java.util.Scanner; 14 import java.util.Stack; 15 16 import com.sun.jna.*; 17 import com.sun.jna.platform.win32.Kernel32; 18 import com.sun.jna.platform.win32.WinNT; 19 20 import java.lang.reflect.Field; 21 //import java.lang.reflect.Method; 13 22 14 23 import org.apache.log4j.*; … … 20 29 // to avoid blocking problems that can arise from a Process' input and output streams. 21 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 22 34 public class SafeProcess { 23 //public static int DEBUG = 0;35 public static int DEBUG = 0; 24 36 25 37 public static final int STDERR = 0; 26 38 public static final int STDOUT = 1; 27 39 public static final int STDIN = 2; 28 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 29 44 // charset for reading process stderr and stdout streams 30 45 //public static final String UTF8 = "UTF-8"; … … 39 54 private String inputStr = null; 40 55 private Process process = null; 41 56 private boolean forciblyTerminateProcess = false; 57 42 58 // output from running SafeProcess.runProcess() 43 59 private String outputStr = ""; … … 102 118 } 103 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 104 126 private Process doRuntimeExec() throws IOException { 105 127 Process prcs = null; … … 113 135 114 136 // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java 115 log("SafeProcess running: " + Arrays.toString(command_args)); 137 //log("SafeProcess running:" + Arrays.toString(command_args)); 138 StringBuffer cmdDisplay = new StringBuffer(); 139 for(int i = 0; i < command_args.length; i++) { 140 cmdDisplay.append(" ").append(command_args[i]); 141 } 142 log("SafeProcess running: [" + cmdDisplay + "]"); 143 cmdDisplay = null; // let the GC have it 144 116 145 117 146 if(this.envp == null) { … … 139 168 throws IOException, InterruptedException 140 169 { 141 142 143 170 // kick off the stream gobblers 144 171 inputGobbler.start(); … … 176 203 errorGobbler.interrupt(); 177 204 outputGobbler.interrupt(); 178 205 206 // Since we have been cancelled (InterruptedException), or on any Exception, we need 207 // to forcibly terminate process eventually after the finally code first waits for each worker thread 208 // to die off. Don't set process=null until after we've forcibly terminated it if needs be. 209 this.forciblyTerminateProcess = true; 210 179 211 // even after the interrupts, we want to proceed to calling join() on all the worker threads 180 212 // in order to wait for each of them to die before attempting to destroy the process if it … … 204 236 // set the variables that the code which created a SafeProcess object may want to inspect 205 237 this.outputStr = outputGobbler.getOutput(); 206 this.errorStr = errorGobbler.getOutput(); 207 208 // Since we didn't have an exception, process should have terminated now (waitFor blocks until then) 209 // Set process to null so we don't forcibly terminate it below with process.destroy() 210 this.process = null; 238 this.errorStr = errorGobbler.getOutput(); 211 239 } 212 240 … … 223 251 224 252 // Run a very basic process: with no reading from or writing to the Process' iostreams, 225 // this just execs the process and waits for it to return 253 // this just execs the process and waits for it to return. 254 // Don't call this method but the zero-argument runProcess() instead if your process will 255 // output stuff to its stderr and stdout streams but you don't need to monitory these. 256 // Because, as per a comment in GLI's GS3ServerThread.java, 257 // in Java 6, it wil block if you don't handle a process' streams when the process is 258 // outputting something. (Java 7+ won't block if you don't bother to handle the output streams) 226 259 public int runBasicProcess() { 227 260 try { 261 this.forciblyTerminateProcess = true; 262 228 263 // 1. create the process 229 264 process = doRuntimeExec(); … … 231 266 this.exitValue = process.waitFor(); 232 267 233 234 } catch(IOException ioe) { 268 this.forciblyTerminateProcess = false; 269 } catch(IOException ioe) { 270 235 271 if(exceptionHandler != null) { 236 272 exceptionHandler.gotException(ioe); … … 249 285 } finally { 250 286 251 if( process != null ) { 252 process.destroy(); // see runProcess() below 253 } 287 if( this.forciblyTerminateProcess ) { 288 destroyProcess(process); // see runProcess() below 289 } 290 process = null; 291 this.forciblyTerminateProcess = false; // reset 254 292 } 255 293 return this.exitValue; … … 273 311 274 312 try { 313 this.forciblyTerminateProcess = false; 314 275 315 // 1. get the Process object 276 316 process = doRuntimeExec(); … … 291 331 if(procErrHandler == null) { 292 332 errorGobbler // ReaderFromProcessOutputStream 293 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);333 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines); 294 334 } else { 295 335 errorGobbler … … 300 340 if(procOutHandler == null) { 301 341 outputGobbler 302 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);342 = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines); 303 343 } else { 304 344 outputGobbler … … 311 351 312 352 } catch(IOException ioe) { 353 this.forciblyTerminateProcess = true; 354 313 355 if(exceptionHandler != null) { 314 356 exceptionHandler.gotException(ioe); … … 317 359 } 318 360 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so print stack trace 319 361 this.forciblyTerminateProcess = true; 362 320 363 if(exceptionHandler != null) { 321 364 exceptionHandler.gotException(ie); … … 332 375 //log("*** In finally of SafeProcess.runProcess(3 params): " + cmd); 333 376 334 if( process != null) {377 if( this.forciblyTerminateProcess ) { 335 378 log("*** Going to call process.destroy 2"); 336 process.destroy(); 337 process = null; 379 destroyProcess(process); 338 380 log("*** Have called process.destroy 2"); 339 381 } 340 382 process = null; 383 this.forciblyTerminateProcess = false; // reset 341 384 } 342 385 … … 351 394 352 395 try { 396 this.forciblyTerminateProcess = false; 397 353 398 // 1. get the Process object 354 399 process = doRuntimeExec(); … … 379 424 380 425 381 426 // 4. kick off the stream gobblers 382 427 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler); 383 428 384 429 } catch(IOException ioe) { 430 this.forciblyTerminateProcess = true; 431 385 432 if(exceptionHandler != null) { 386 433 exceptionHandler.gotException(ioe); … … 389 436 } 390 437 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so log it 391 438 this.forciblyTerminateProcess = true; 439 392 440 if(exceptionHandler != null) { 393 441 exceptionHandler.gotException(ie); … … 415 463 //log("*** In finally of SafeProcess.runProcess(2 params): " + cmd); 416 464 417 if( process != null) {465 if( this.forciblyTerminateProcess ) { 418 466 log("*** Going to call process.destroy 1"); 419 process.destroy(); 420 process = null; 467 destroyProcess(process); 421 468 log("*** Have called process.destroy 1"); 422 469 } 470 process = null; 471 this.forciblyTerminateProcess = false; //reset 423 472 } 424 473 … … 426 475 } 427 476 428 477 /* 478 479 On Windows, p.destroy() terminates process p that Java launched, 480 but does not terminate any processes that p may have launched. Presumably since they didn't form a proper process tree. 481 https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/e3cb7532-87f6-4ae3-9d80-a3afc8b9d437/how-to-kill-a-process-tree-in-cc-on-windows-platform?forum=vclanguage 482 https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161(v=vs.85).aspx 483 484 Searching for: "forcibly terminate external process launched by Java on Windows" 485 Not possible: stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java 486 But can use taskkill or tskill or wmic commands to terminate a process by processID 487 stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process 488 Taskkill command can kill by Image Name, such as all running perl, e.g. taskkill /f /im perl.exe 489 But what if we kill perl instances not launched by GS? 490 /f Specifies to forcefully terminate the process(es). We need this flag switched on to kill childprocesses. 491 /t Terminates the specified process and any child processes which were started by it. 492 /t didn't work to terminate subprocesses. Maybe since the process wasn't launched as 493 a properly constructed processtree. 494 /im is the image name (the name of the program), see Image Name column in Win Task Manager. 495 496 We don't want to kill all perl running processes. 497 Another option is to use wmic, available since Windows XP, to kill a process based on its command 498 which we sort of know (SafeProcess.command) and which can be seen in TaskManager under the 499 "Command Line" column of the Processes tab. 500 https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line 501 The following works kill any Command Line that matches -site localsite lucene-jdbm-demo 502 C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate 503 "WMIC Wildcard Search using 'like' and %" 504 https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/ 505 However, we're not even guaranteed that every perl command GS launches will contain the collection name 506 Nor do we want to kill all perl processes that GS launches with bin\windows\perl\bin\perl, though this works: 507 wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate 508 The above could kill GS perl processes we don't intend to terminate, as they're not spawned by the particular 509 Process we're trying to terminate from the root down. 510 511 Solution: We can use taskkill or the longstanding tskill or wmic to kill a process by ID. Since we can 512 kill an external process that SafeProcess launched OK, and only have trouble killing any child processes 513 it launched, we need to know the pids of the child processes. 514 515 We can use Windows' wmic to discover the childpids of a process whose id we know. 516 And we can use JNA to get the process ID of the external process that SafeProcess launched. 517 518 To find the processID of the process launched by SafeProcess, 519 need to use Java Native Access (JNA) jars, available jna.jar and jna-platform.jar. 520 http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program 521 http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id 522 http://www.golesny.de/p/code/javagetpid 523 https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md 524 We're using JNA v 4.1.0, https://mvnrepository.com/artifact/net.java.dev.jna/jna 525 526 WMIC can show us a list of parent process id and process id of running processes, and then we can 527 kill those child processes with a specific process id. 528 https://superuser.com/questions/851692/track-which-program-launches-a-certain-process 529 http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows 530 WMIC can get us the pids of all childprocesses launched by parent process denoted by parent pid. 531 And vice versa: 532 if you know the parent pid and want to know all the pids of the child processes spawned: 533 wmic process where (parentprocessid=596) get processid 534 if you know a child process id and want to know the parent's id: 535 wmic process where (processid=180) get parentprocessid 536 537 The above is the current solution. 538 539 Eventually, instead of running a windows command to kill the process ourselves, consider changing over to use 540 https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java 541 (works with Apache license, http://www.apache.org/licenses/LICENSE-2.0) 542 This is a Java class that uses JNA to terminate processes. It also has the getProcessID() method. 543 544 Linux ps equivalent on Windows is "tasklist", see 545 http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program 546 547 */ 548 549 // http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program 550 // Uses Java Native Access, JNA 551 public static long getProcessID(Process p) 552 { 553 long pid = -1; 554 try { 555 //for windows 556 if (p.getClass().getName().equals("java.lang.Win32Process") || 557 p.getClass().getName().equals("java.lang.ProcessImpl")) 558 { 559 Field f = p.getClass().getDeclaredField("handle"); 560 f.setAccessible(true); 561 long handl = f.getLong(p); 562 Kernel32 kernel = Kernel32.INSTANCE; 563 WinNT.HANDLE hand = new WinNT.HANDLE(); 564 hand.setPointer(Pointer.createConstant(handl)); 565 pid = kernel.GetProcessId(hand); 566 f.setAccessible(false); 567 } 568 //for unix based operating systems 569 else if (p.getClass().getName().equals("java.lang.UNIXProcess")) 570 { 571 Field f = p.getClass().getDeclaredField("pid"); 572 f.setAccessible(true); 573 pid = f.getLong(p); 574 f.setAccessible(false); 575 } 576 577 } catch(Exception ex) { 578 log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex); 579 pid = -1; 580 } 581 return pid; 582 } 583 584 585 // stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java 586 // (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?) 587 // stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process 588 // Searching for: "forcibly terminate external process launched by Java on Windows" 589 static void killWinProcessWithID(long processID) { 590 591 String cmd = SafeProcess.getWinProcessKillCmd(processID); 592 if (cmd == null) return; 593 594 try { 595 log("\tAttempting to terminate Win subprocess with pid: " + processID); 596 SafeProcess proc = new SafeProcess(cmd); 597 int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete" 598 // there is output that needs flushing, so don't use runBasicProcess() 599 600 } catch(Exception e) { 601 log("@@@ Exception attempting to stop perl " + e.getMessage(), e); 602 } 603 } 604 605 606 // On linux and mac, p.destroy() suffices to kill processes launched by p as well. 607 // On Windows we need to do more work, since otherwise processes launched by p remain around executing until they naturally terminate. 608 // e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until it naturally terminates. 609 static void destroyProcess(Process p) { 610 // If it isn't windows, process.destroy() terminates any child processes too 611 if(!Misc.isWindows()) { 612 p.destroy(); 613 return; 614 } 615 616 if(!SafeProcess.isAvailable("wmic")) { 617 log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses..."); 618 log("Kill them manually from the TaskManager or they will proceed to run to termination"); 619 620 // At least we can get rid of the top level process we launched 621 p.destroy(); 622 return; 623 } 624 625 // get the process id of the process we launched, 626 // so we can use it to find the pids of any subprocesses it launched in order to terminate those too. 627 628 long processID = SafeProcess.getProcessID(p); 629 log("Attempting to terminate sub processes of Windows process with pid " + processID); 630 terminateSubProcessesRecursively(processID, p); 631 632 } 633 634 // Helper function. Only for Windows. 635 // Counterintuitively, we're be killing all parent processess and then all child procs and all their descendants 636 // as soon as we discover any further process each (sub)process has launched. The parent processes are killed 637 // first in each case for 2 reasons: 638 // 1. on Windows, killing the parent process leaves the child running as an orphan anyway, so killing the 639 // parent is an independent action, the child process is not dependent on the parent; 640 // 2. Killing a parent process prevents it from launching further processes while we're killing off each child process 641 private static void terminateSubProcessesRecursively(long parent_pid, Process p) { 642 643 // Use Windows wmic to find the pids of any sub processes launched by the process denoted by parent_pid 644 SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+parent_pid+") get processid"); 645 proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n 646 int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way. 647 //log("@@@@ Return value from proc: " + exitValue); 648 649 // need output from both stdout and stderr: stderr will say there are no pids, stdout will contain pids 650 String stdOutput = proc.getStdOutput(); 651 String stdErrOutput = proc.getStdError(); 652 653 654 // Now we know the pids of the immediate subprocesses, we can get rid of the parent process 655 // We know the children remain running: since the whole problem on Windows is that these 656 // child processes remain running as orphans after the parent is forcibly terminated. 657 if(p != null) { // we're the top level process, terminate the java way 658 p.destroy(); 659 } else { // terminate windows way 660 SafeProcess.killWinProcessWithID(parent_pid); // get rid off current pid 661 } 662 663 // parse the output to get the sub processes' pids 664 // Output looks like: 665 // ProcessId 666 // 6040 667 // 180 668 // 4948 669 // 1084 670 // 6384 671 // If no children, then STDERR output starts with the following, possibly succeeded by empty lines: 672 // No Instance(s) Available. 673 674 // base step of the recursion 675 if(stdErrOutput.indexOf("No Instance(s) Available.") != -1) { 676 //log("@@@@ Got output on stderr: " + stdErrOutput); 677 // No further child processes. And we already terminated the parent process, so we're done 678 return; 679 } else { 680 //log("@@@@ Got output on stdout:\n" + stdOutput); 681 682 // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split 683 684 // find all childprocesses for that pid and terminate them too: 685 Stack<Long> subprocs = new Stack<Long>(); 686 Scanner sc = new Scanner(stdOutput); 687 while (sc.hasNext()) { 688 if(!sc.hasNextLong()) { 689 sc.next(); // discard the current token since it's not a Long 690 } else { 691 long child_pid = sc.nextLong(); 692 subprocs.push(new Long(child_pid)); 693 } 694 } 695 sc.close(); 696 697 // recursion step if subprocs is not empty (but if it is empty, then it's another base step) 698 if(!subprocs.empty()) { 699 long child_pid = subprocs.pop().longValue(); 700 terminateSubProcessesRecursively(child_pid, null); 701 } 702 } 703 } 704 705 // This method should only be called on a Windows OS 706 private static String getWinProcessKillCmd(Long processID) { 707 // check if we first need to init WIN_KILL_CMD. We do this only once, but can't do it in a static codeblock 708 709 if(WIN_KILL_CMD == null) { 710 if(SafeProcess.isAvailable("wmic")) { 711 // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229 712 WIN_KILL_CMD = "wmic process _PROCID_ delete"; // like "kill -9" on Windows 713 } 714 else if(SafeProcess.isAvailable("taskkill")) { // check if we have taskkill or else use the longstanding tskill 715 716 WIN_KILL_CMD = "taskkill /f /t /PID _PROCID_"; // need to forcefully /f terminate the process 717 // /t "Terminates the specified process and any child processes which were started by it." 718 // But despite the /T flag, the above doesn't kill subprocesses. 719 } 720 else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed 721 WIN_KILL_CMD = "tskill _PROCID_"; // https://ss64.com/nt/tskill.html 722 } 723 } 724 725 if(WIN_KILL_CMD == null) { // can happen if none of the above cmds were available 726 return null; 727 } 728 return WIN_KILL_CMD.replace( "_PROCID_", Long.toString(processID) ); 729 } 730 731 732 // Run `which` on a program to find out if it is available. which.exe is included in winbin. 733 // On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same. 734 // where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html 735 // There is no `where` on Linux/Mac, must use which for them. 736 // On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names. 737 public static boolean isAvailable(String program) { 738 try { 739 // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in" 740 // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed 741 SafeProcess prcs = new SafeProcess("which " + program); 742 prcs.runProcess(); 743 String output = prcs.getStdOutput(); 744 if(output.equals("")) { 745 return false; 746 } 747 //System.err.println("*** 'which " + program + "' returned: " + output); 748 return true; 749 } catch (Exception exc) { 750 return false; 751 } 752 } 753 754 // Google Java external process destroy kill subprocesses 755 // https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/ 756 429 757 //******************** Inner class and interface definitions ********************// 430 758 // Static inner classes can be instantiated without having to instantiate an object of the outer class first … … 515 843 this.is = is; 516 844 this.split_newlines = split_newlines; 845 517 846 } 518 847 … … 644 973 // to already send EOF silently. 645 974 646 /*if( Utility.isWindows()) {975 /*if(Misc.isWindows()) { 647 976 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows 648 977 } else { // EOF on Linux/Mac is Ctrl-D … … 678 1007 // logger and DebugStream print commands are synchronized, therefore thread safe. 679 1008 public static void log(String msg) { 1009 if(DEBUG == 0) return; 680 1010 logger.info(msg); 681 1011 … … 686 1016 687 1017 public static void log(String msg, Exception e) { // Print stack trace on the exception 1018 if(DEBUG == 0) return; 688 1019 logger.error(msg, e); 689 1020 … … 696 1027 697 1028 public static void log(Exception e) { 1029 if(DEBUG == 0) return; 698 1030 logger.error(e); 699 1031 -
main/trunk/greenstone3/web/WEB-INF/lib/cp.mf
r22143 r31663 1 1 Class-Path: LuceneWrapper.jar activation.jar axis-ant.jar 2 2 axis.jar bsf.jar commons-discovery-0.2.jar 3 commons-logging-1.0.4.jar derby.jar gsdl3.jar jaxp.jar 4 j axrpc.jar js.jar junit.jar log4j-1.2.8.jar lucene-1.4.1.jar3 commons-logging-1.0.4.jar derby.jar gsdl3.jar jaxp.jar jaxrpc.jar 4 jna.jar jna-platform.jar js.jar junit.jar log4j-1.2.8.jar lucene-1.4.1.jar 5 5 mail.jar saaj.jar wsdl4j-1.5.1.jar xercesImpl.jar 6 6 xmlsec-1.2.1.jar gutil.jar
Note:
See TracChangeset
for help on using the changeset viewer.