Changeset 31705 for main

Show
Ignore:
Timestamp:
25.05.2017 19:03:32 (3 years ago)
Author:
ak19
Message:

Bringing the GS3's src code version of SafeProcess? up to speed with GLI's version, which had code added to get any subprocesses launched by the process internal to SafeProcess? to also terminate successfully on Mac when the SafeProcess? instance is cancelled

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java

    r31697 r31705  
    609609 
    610610        if(mainHandler != null) mainHandler.beforeProcessDestroy(); 
    611         SafeProcess.destroyProcess(process); // see runProcess(2 args/3 args) 
     611        boolean noNeedToDestroyIfOnLinux = true; // Interrupt handling suffices to cleanup process and subprocesses on Linux 
     612        SafeProcess.destroyProcess(process, noNeedToDestroyIfOnLinux); // see runProcess(2 args/3 args) 
    612613        if(mainHandler != null) mainHandler.afterProcessDestroy(); 
    613614 
     
    731732     
    732733 
    733 // stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java   
     734// Can't artificially send Ctrl-C: stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java 
    734735// (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?) 
    735736// stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process 
     
    751752} 
    752753 
    753  
    754 // On linux and mac, p.destroy() suffices to kill processes launched by p as well. 
    755 // On Windows we need to do more work, since otherwise processes launched by p remain around executing until they naturally terminate. 
     754// Kill signals, their names and numerical equivalents: http://www.faqs.org/qa/qa-831.html 
     755// https://stackoverflow.com/questions/8533377/why-child-process-still-alive-after-parent-process-was-killed-in-linux 
     756// Didn't work for when build scripts run from GLI: kill -TERM -pid 
     757// but the other suggestion did work: pkill -TERM -P pid did work 
     758// More reading: 
     759// https://linux.die.net/man/1/kill (manual) 
     760// https://unix.stackexchange.com/questions/117227/why-pidof-and-pgrep-are-behaving-differently 
     761// https://unix.stackexchange.com/questions/67635/elegantly-get-list-of-children-processes 
     762// https://stackoverflow.com/questions/994033/mac-os-x-quickest-way-to-kill-quit-an-entire-process-tree-from-within-a-cocoa-a 
     763// https://unix.stackexchange.com/questions/132224/is-it-possible-to-get-process-group-id-from-proc 
     764// https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated 
     765 
     766/** 
     767 * On Unix, will kill the process denoted by processID and any subprocessed this launched. Tested on a Mac, where this is used. 
     768 * @param force if true will send the -KILL (-9) signal, which may result in abrupt termination without cleanup 
     769 * if false, will send the -TERM (-15) signal, which will allow cleanup before termination. Sending a SIGTERM is preferred. 
     770 * @param killEntireTree if false, will terminate only the process denoted by processID, otherwise all descendants/subprocesses too. 
     771 * @return true if running the kill process returned an exit value of 0 or if it had already been terminated 
     772*/ 
     773static boolean killUnixProcessWithID(long processID, boolean force, boolean killEntireTree) { 
     774     
     775    String signal = force ? "KILL" : "TERM"; // kill -KILL (kill -9) vs preferred kill -TERM (kill -15) 
     776    String cmd; 
     777    if(killEntireTree) { // kill the process denoted by processID and any subprocesses this launched 
     778 
     779    if(Misc.isMac()) {  
     780        // this cmd works on Mac (tested Snow Leopard), but on Linux this cmd only terminates toplevel process 
     781        // when doing full-import, and doesn't always terminate subprocesses when doing full-buildcol.pl 
     782        cmd = "pkill -"+signal + " -P " + processID; // e.g. "pkill -TERM -P pid" 
     783    }  
     784    else { // other unix 
     785        // this cmd works on linux, not recognised on Mac (tested Snow Leopard): 
     786        cmd = "kill -"+signal + " -"+processID; // e.g. "kill -TERM -pid" 
     787                                                // note the hyphen before pid to terminate subprocesses too 
     788    } 
     789 
     790    } else { // kill only the process represented by the processID. 
     791    cmd = "kill -"+signal + " " + processID; // e.g. "kill -TERM pid" 
     792    }  
     793 
     794    SafeProcess proc = new SafeProcess(cmd); 
     795    int exitValue = proc.runProcess(); 
     796     
     797     
     798    if(exitValue == 0) { 
     799    if(force) { 
     800        log("@@@ Successfully sent SIGKILL to unix process tree rooted at " + processID); 
     801    } else {  
     802        log("@@@ Successfully sent SIGTERM to unix process tree rooted at " + processID); 
     803    } 
     804    return true; 
     805    } else if(!Misc.isMac() && exitValue == 1) { 
     806    // https://stackoverflow.com/questions/28332888/return-value-of-kill 
     807    // "kill returns an exit code of 0 (true) if the process still existed it and was killed. 
     808    // kill returns an exit code of 1 (false) if the kill failed, probably because the process was no longer running." 
     809    // On Linux, interrupting the process and its worker threads and closing resources already successfully terminates 
     810    // the process and its subprocesses (don't need to call this method at all to terminate the processes: the processes 
     811    // aren't running when we get to this method) 
     812    log("@@@ Sending termination signal returned exit value 1. On linux this happens when the process has already been terminated"); 
     813    return true; 
     814    } else { 
     815    log("@@@ Not able to successfully terminate process, got exitvalue " + exitValue); 
     816    log("@@@ Got output: |" + proc.getStdOutput() + "|");  
     817    log("@@@ Got err output: |" + proc.getStdError() + "|"); 
     818    // caller can try again with kill -KILL, by setting force parameter to true 
     819    return false; 
     820    } 
     821} 
     822 
     823public static void destroyProcess(Process p) { 
     824    // A cancel action results in an interruption to the process thread, which in turn interrupts 
     825    // the SafeProcess' worker threads, all which clean up after themselves. 
     826    // On linux, this suffices to cleanly terminate a Process and any subprocesses that may have launched 
     827    // so we don't need to do extra work in such a case. But the interrupts happen only when SafeProcess calls 
     828    // destroyProcess() on the Process it was running internally, and not if anyone else tries to end a  
     829    // Process by calling SafeProcess.destroyProcess(p). In such cases, the Process needs to be actively terminated: 
     830    boolean canSkipExtraWorkIfLinux = true; 
     831    SafeProcess.destroyProcess(p, !canSkipExtraWorkIfLinux); 
     832} 
     833 
     834// On linux, the SafeProcess code handling an Interruption suffices to successfully and cleanly terminate 
     835// the process and any subprocesses launched by p as well (and not even an extra p.destroy() is needed). 
     836// On Windows, and Mac too, we need to do more work, since otherwise processes launched by p remain 
     837// around executing until they naturally terminate. 
    756838// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until it naturally terminates. 
    757 static void destroyProcess(Process p) { 
     839private static void destroyProcess(Process p, boolean canSkipExtraWorkIfLinux) { 
    758840    log("### in SafeProcess.destroyProcess(Process p)"); 
    759841 
    760842    // If it isn't windows, process.destroy() terminates any child processes too 
    761     if(!Misc.isWindows()) { 
     843    if(Misc.isWindows()) { 
     844     
     845    if(!SafeProcess.isAvailable("wmic")) { 
     846        log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses..."); 
     847        log("Kill them manually from the TaskManager or they will proceed to run to termination"); 
     848         
     849        // At least we can get rid of the top level process we launched 
     850        p.destroy(); 
     851        return; 
     852    }    
     853     
     854    // get the process id of the process we launched, 
     855    // so we can use it to find the pids of any subprocesses it launched in order to terminate those too. 
     856     
     857    long processID = SafeProcess.getProcessID(p); 
     858    if(processID == -1) { // the process doesn't exist or no longer exists (terminated naturally?) 
     859        p.destroy(); // minimum step, do this anyway, at worst there's no process and this won't have any effect 
     860    } else { 
     861        log("Attempting to terminate sub processes of Windows process with pid " + processID); 
     862        terminateSubProcessesRecursively(processID, p); 
     863    } 
     864    return; 
     865     
     866    } 
     867    else { // linux or mac 
     868 
     869    // if we're on linux and would have already terminated by now (in which case canSkipExtraWorkForLinux would be true), 
     870    // then there's nothing much left to do. This would only be the case if SafeProcess is calling this method on its 
     871    // internal process, since it would have successfully cleaned up on Interruption and there would be no process left running 
     872    if(!Misc.isMac() && canSkipExtraWorkIfLinux) { 
     873        log("@@@ Linux: Cancelling a SafeProcess instance does not require any complicated system destroy operation"); 
     874        p.destroy(); // vestigial: this will have no effect if the process had already terminated, which is the case in this block 
     875        return; 
     876    } 
     877    // else we're on a Mac or an external caller (not SafeProcess) has requested explicit termination on Linux 
     878     
     879    long pid = SafeProcess.getProcessID(p); 
     880    /* 
     881    // On Macs (all Unix?) can't get the child processes of a process once it's been destroyed 
     882    macTerminateSubProcessesRecursively(pid, p); // pid, true)   
     883    */ 
     884     
     885    if(pid == -1) { 
     886        p.destroy(); // at minimum, will have no effect if the process had already terminated  
     887    } else { 
     888        boolean forceKill = true; 
     889        boolean killEntireProcessTree = true; 
     890         
     891        if(!killUnixProcessWithID(pid, !forceKill, killEntireProcessTree)) { // send sig TERM (kill -15 or kill -TERM) 
     892        killUnixProcessWithID(pid, forceKill, killEntireProcessTree); // send sig KILL (kill -9 or kill -KILL) 
     893        }        
     894    } 
     895     
     896    return; 
     897    }     
     898} 
     899 
     900 
     901// UNUSED and INCOMPLETE 
     902// But if this method is needed, then need to parse childpids printed by "pgrep -P pid" and write recursive step 
     903// The childpids are probably listed one per line, see https://unix.stackexchange.com/questions/117227/why-pidof-and-pgrep-are-behaving-differently 
     904private static void macTerminateSubProcessesRecursively(long parent_pid, Process p) { //boolean isTopLevelProcess) { 
     905    log("@@@ Attempting to terminate mac process recursively"); 
     906 
     907    // https://unix.stackexchange.com/questions/67635/elegantly-get-list-of-children-processes 
     908    SafeProcess proc = new SafeProcess("pgrep -P "+parent_pid); 
     909    int exitValue = proc.runProcess(); 
     910    String stdOutput = proc.getStdOutput(); 
     911    String stdErrOutput = proc.getStdError(); 
     912 
     913    // now we have the child processes, can terminate the parent process 
     914    if(p != null) { // top level process, can just be terminated the java way with p.destroy() 
    762915    p.destroy(); 
     916    } else { 
     917    boolean forceKill = true; 
     918    boolean killSubprocesses = true; 
     919    // get rid of process denoted by the current pid (but not killing subprocesses it may have launched, 
     920    // since we'll deal with them recursively) 
     921    if(!SafeProcess.killUnixProcessWithID(parent_pid, !forceKill, !killSubprocesses)) { // send kill -TERM, kill -15 
     922        SafeProcess.killUnixProcessWithID(parent_pid, forceKill, !killSubprocesses); // send kill -9, kill -KILL 
     923    } 
     924    } 
     925     
     926    /* 
     927    // get rid of any process with current pid 
     928    if(!isTopLevelProcess && !SafeProcess.killUnixProcessWithID(parent_pid, false)) { // send kill -TERM, kill -15 
     929    SafeProcess.killUnixProcessWithID(parent_pid, true); // send kill -9, kill -KILL 
     930    } 
     931    */ 
     932 
     933    if(stdOutput.trim().equals("") && stdErrOutput.trim().equals("") && exitValue == 1) { 
     934    log("No child processes"); 
     935    // we're done 
    763936    return; 
    764     }    
    765      
    766     if(!SafeProcess.isAvailable("wmic")) { 
    767     log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses..."); 
    768     log("Kill them manually from the TaskManager or they will proceed to run to termination"); 
    769      
    770     // At least we can get rid of the top level process we launched 
    771     p.destroy(); 
    772     return; 
    773     }    
    774      
    775     // get the process id of the process we launched, 
    776     // so we can use it to find the pids of any subprocesses it launched in order to terminate those too. 
    777      
    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 
    781937    } else { 
    782     log("Attempting to terminate sub processes of Windows process with pid " + processID); 
    783     terminateSubProcessesRecursively(processID, p); 
    784     } 
    785      
     938    log("Got childpids on STDOUT: " + stdOutput); 
     939    log("Got childpids on STDERR: " + stdErrOutput); 
     940    } 
    786941} 
    787942 
     
    812967    p.destroy(); 
    813968    } else { // terminate windows way        
    814     SafeProcess.killWinProcessWithID(parent_pid); // get rid off current pid         
     969    SafeProcess.killWinProcessWithID(parent_pid); // get rid of process with current pid 
    815970    } 
    816971