Changeset 31705 for main/trunk


Ignore:
Timestamp:
05/25/17 19:03:32 (4 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

File:
1 edited

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   
Note: See TracChangeset for help on using the changeset viewer.