Changeset 31651
- Timestamp:
- 2017-05-05T21:47:15+12:00 (7 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java
r31650 r31651 38 38 public static final int STDOUT = 1; 39 39 public static final int STDIN = 2; 40 41 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 42 43 43 … … 118 118 } 119 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 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 125 126 private Process doRuntimeExec() throws IOException { 126 127 Process prcs = null; … … 550 551 public static long getProcessID(Process p) 551 552 { 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; 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; 583 582 } 584 583 585 584 586 585 // stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java … … 589 588 // Searching for: "forcibly terminate external process launched by Java on Windows" 590 589 static void killWinProcessWithID(long processID) { 591 592 593 594 595 try{596 597 598 599 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() 600 599 601 602 603 600 } catch(Exception e) { 601 log("@@@ Exception attempting to stop perl " + e.getMessage(), e); 602 } 604 603 } 605 604 … … 609 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. 610 609 static void destroyProcess(Process p) { 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 610 // If it isn't windows, process.destroy() terminates any child processes too 611 if(!Utility.isWindows()) { 612 p.destroy(); 613 return; 614 } 615 616 if(!SafeProcess.isAvailable("wmic")) { 617 log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses..."); 618 log("Kill them manually from the TaskManager or they will proceed to run to termination"); 619 620 // At least we can get rid of the top level process we launched 621 p.destroy(); 622 return; 623 } 624 625 // get the process id of the process we launched, 626 // so we can use it to find the pids of any subprocesses it launched in order to terminate those too. 627 628 long processID = SafeProcess.getProcessID(p); 629 log("Attempting to terminate sub processes of Windows process with pid " + processID); 630 terminateSubProcessesRecursively(processID, p); 631 633 632 } 634 633 … … 642 641 private static void terminateSubProcessesRecursively(long parent_pid, Process p) { 643 642 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 } 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 } 702 703 } 703 704 704 705 // This method should only be called on a Windows OS 705 706 private static String getWinProcessKillCmd(Long processID) { 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 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) ); 728 729 } 729 730 … … 735 736 // On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names. 736 737 public 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) { 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("")) { 749 745 return false; 750 746 } 747 //System.err.println("*** 'which " + program + "' returned: " + output); 748 return true; 749 } catch (Exception exc) { 750 return false; 751 } 751 752 } 752 753
Note:
See TracChangeset
for help on using the changeset viewer.