Changeset 31646


Ignore:
Timestamp:
05/04/17 20:32:17 (4 years ago)
Author:
ak19
Message:
  1. Moved isAvailable(program) into SafeProcess.java from FileAssociationManager.java. 2. SafeProcess.java now successfully terminates any child processes launched on Windows too because of the addition of new methods. But the real solution may need to be in Perl, especially if using mobile windows OS where the system tools currently being used may not be present. 4. Adding JNA jar files, jna.jar and jna-platform.jar of version 4.1.0 (2013), to gli's lib. These are needed to get the PID of a running process on Windows. 5. Need to tidy up code, need to check linux is not affected by platform specific import statements used by Windows, need to make the process termination recursive (not just children, but all descendants need to be killed on destroyProcess(p). Rather than wmic delete or taskkill or tskill, can use the linked Java code Processes.java which is dependent on JNA.
Location:
main/trunk/gli
Files:
2 added
2 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/gli/src/org/greenstone/gatherer/file/FileAssociationManager.java

    r31637 r31646  
    117117        String [] browsers = new String [] {"mozilla", "netscape", "firefox"};
    118118        for (int i=0; i<browsers.length; i++) {
    119         if (isAvailable(browsers[i])) {
     119        if (SafeProcess.isAvailable(browsers[i])) {
    120120            command = browsers[i]+ " %1";
    121121            break;
     
    351351    }
    352352
    353 
    354     protected boolean isAvailable(String program) {
    355     try {
    356         // `which bla` does nothing, prompt is returned
    357         // `which grep` returns a line of output with the path to grep
    358         SafeProcess prcs = new SafeProcess("which " + program);
    359         prcs.runProcess();
    360         String output = prcs.getStdOutput();
    361         if(output.equals("")) {
    362         return false;
    363         }
    364         //System.err.println("*** 'which " + program + "' returned: " + output);
    365         return true;
    366     } catch (Exception exc) {
    367         return false;
    368     }
    369     }
    370353}
  • main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java

    r31643 r31646  
    1111import java.io.OutputStreamWriter;
    1212import java.util.Arrays;
     13import java.util.Scanner;
     14
     15import com.sun.jna.*;
     16import com.sun.jna.platform.win32.Kernel32;
     17import com.sun.jna.platform.win32.WinNT;
     18
     19import java.lang.reflect.Field;
     20//import java.lang.reflect.Method;
    1321
    1422import org.apache.log4j.*;
     
    253261
    254262        if( this.forciblyTerminateProcess ) {
    255         process.destroy(); // see runProcess() below
    256         //killAnyPerl();
     263        destroyProcess(process); // see runProcess() below     
    257264        }
    258265        process = null;
     
    299306        if(procErrHandler == null) {
    300307        errorGobbler // ReaderFromProcessOutputStream
    301             = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
     308            = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines);
    302309        } else {
    303310        errorGobbler
     
    308315        if(procOutHandler == null) {
    309316        outputGobbler
    310             = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
     317            = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines);
    311318        } else {
    312319        outputGobbler
     
    345352        if( this.forciblyTerminateProcess ) {
    346353        log("*** Going to call process.destroy 2");
    347         process.destroy();
    348         //killAnyPerl();
     354        destroyProcess(process);               
    349355        log("*** Have called process.destroy 2");
    350356        }
     
    434440        if( this.forciblyTerminateProcess ) {
    435441        log("*** Going to call process.destroy 1");
    436         process.destroy();
    437         //killAnyPerl();
     442        destroyProcess(process);   
    438443        log("*** Have called process.destroy 1");
    439444        }
     
    454459    // only on Windows do we need to still kill perl despite process.destroy()
    455460    try{
    456         Runtime.getRuntime().exec("taskkill /f /im perl.exe");     
     461        //Runtime.getRuntime().exec("taskkill /f /im perl.exe");
     462        SafeProcess proc = new SafeProcess("taskkill /f /im perl.exe");
     463            // /f Specifies to forcefully terminate the process(es).
     464            //  /t Terminates the specified process and any child processes which were started by it.
     465            // /im is the image name (the name of the program), see Image Name column in Win Task Manager.
     466        int exitValue = proc.runBasicProcess(); // don't care about IOstreams
    457467    } catch(Exception e) {
    458468        log("@@@ Exception attempting to stop perl " + e.getMessage(), e);     
    459469    }
    460470}
     471
     472// http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
     473// http://www.golesny.de/p/code/javagetpid
     474// Need to use JNA (Java Native Access), http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
     475// Got 4.1.0 from https://mvnrepository.com/artifact/net.java.dev.jna/jna/4.1.0 and https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform/4.1.0
     476// (see also https://mvnrepository.com/artifact/net.java.dev.jna/jna), since github's GettingStarted doc talks about downloading jna.jar but never provides it.
     477// See https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
     478public static long getProcessID(Process p)
     479{
     480    long pid = -1;
     481    try
     482    {
     483        //for windows
     484        if (p.getClass().getName().equals("java.lang.Win32Process") ||
     485               p.getClass().getName().equals("java.lang.ProcessImpl"))
     486        {
     487            Field f = p.getClass().getDeclaredField("handle");
     488            f.setAccessible(true);             
     489            long handl = f.getLong(p);
     490            Kernel32 kernel = Kernel32.INSTANCE;
     491            WinNT.HANDLE hand = new WinNT.HANDLE();
     492            hand.setPointer(Pointer.createConstant(handl));
     493            pid = kernel.GetProcessId(hand);
     494            f.setAccessible(false);
     495        }
     496        //for unix based operating systems
     497        else if (p.getClass().getName().equals("java.lang.UNIXProcess"))
     498        {
     499            Field f = p.getClass().getDeclaredField("pid");
     500            f.setAccessible(true);
     501            pid = f.getLong(p);
     502            f.setAccessible(false);
     503        }
     504    }
     505    catch(Exception ex)
     506    {
     507        log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex); 
     508        pid = -1;
     509    }
     510    return pid;
     511}
     512
     513// UNUSED - only kills toplevel process, same as p.destroy().
     514// Call destroyProcess(p) instead to kill any childprocesses launched
     515static void killProcessIfOnWindows(Process p) {
     516    if(!Utility.isWindows()) return;
     517   
     518    // only on Windows do we need to still kill perl despite process.destroy()
     519    // http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
     520    // http://www.golesny.de/p/code/javagetpid
     521    long processID = getProcessID(p);
     522   
     523    SafeProcess.killWinProcessWithID(processID);   
     524}
     525
     526
     527// Eventually move to using https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java ?
     528// Works with Apache license, http://www.apache.org/licenses/LICENSE-2.0
     529
     530
     531// stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java 
     532// (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
     533// stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
     534// Searching for: "forcibly terminate external process launched by Java on Windows"
     535static void killWinProcessWithID(long processID) {
     536   
     537    // check if we have taskkill or tskill
     538    String cmd = null;
     539    if (SafeProcess.isAvailable("wmic")) {
     540        // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229
     541        cmd = "wmic process " + processID + " delete"; // like "kill -9" on Windows
     542    } else if(SafeProcess.isAvailable("taskkill")) {
     543        cmd = "taskkill /f /t /PID " + processID;
     544    } else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed
     545        cmd = "tskill " + processID; // https://ss64.com/nt/tskill.html
     546    }
     547    cmd = "tskill " + processID;
     548   
     549    try{       
     550        // Despite the /T flag, the following still doesn't kill subprocesses. Taskkill is also not available
     551        // universally. Try alternates tskill and pskill, though they too don't kill further processes launched by p.
     552        log("Attempting to taskkil process with ID " + processID);
     553        //Runtime.getRuntime().exec("taskkill /f /t /PID " + processID);
     554        SafeProcess proc = new SafeProcess(cmd);
     555            // /f Specifies to forcefully terminate the process(es).
     556            //  /t Terminates the specified process and any child processes which were started by it.
     557        int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete" there is output that needs flushing
     558           
     559    } catch(Exception e) {
     560        log("@@@ Exception attempting to stop perl " + e.getMessage(), e);     
     561    }
     562}
     563
     564// On linux and mac, p.destroy() suffices to kill processes launched by p as well.
     565// On Windows we need to do more work, since processes launched by p remain around executing until termination otherwise.
     566// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until termination.
     567static void destroyProcess(Process p) {
     568    //killProcessIfOnWindows(p); // doesn't work: only terminates first level process launched, same as p.destroy()
     569    //p.destroy();
     570    //killAnyPerl(); // kills any running perl, even those not launched by GS
     571   
     572    if(!Utility.isWindows()) {
     573        p.destroy();
     574        return;
     575    }
     576   
     577   
     578   
     579    // We don't want to kill all perl running processes. Just the ones whose command we sort of know and can see in TaskManager
     580    // under the "Command Line" column of the Processes tab.
     581    // This is possible with wmic, available since xp. First check wmic is really present on this Windows machine
     582    // https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line
     583    if(!SafeProcess.isAvailable("wmic")) {
     584        log("wmic used to kill subprocesses is not available. Unable to terminate subprocesses...");
     585        log("Kill them manually from the TaskManager or they will proceed to run to termination");
     586       
     587        // At least we can get rid of the top level process we launched
     588        p.destroy();
     589        return;
     590    }
     591   
     592   
     593    // get the process id of the process we launched
     594    long processID = SafeProcess.getProcessID(p);
     595   
     596   
     597    // What we want to be able to do on Windows is to kill a process by its command line as seen in Task Manager for that process.
     598    // full-import.pl launches import.pl, full-build will launch buildcol.pl and activate.pl.
     599    // These tend to have the collection name still at the end, so we can terminate a process knowing the collection name, since
     600    // we can use wildcards in our wmic termination command.
     601    // However, lucene_passes.pl is launched by buildcol.pl and doesn't have the collection name in its launching cmd line.
     602   
     603    // Works beatufilly to kill any CommandLine (as seen in TaskManager) that matches
     604    //C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate
     605    // https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/
     606    // "WMIC Wildcard Search using like and %"
     607    // All subsequent perl processes launched by Greenstone are launched with bin\windows\perl\bin\perl. So this works well:
     608    // wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate
     609    // However do we want to terminate all perl processes launched by Greenstone, or only those that we can infer were spawned by
     610    // the SafeProcess that we've been running?
     611   
     612    /*
     613    // Works in terminal, but when run through GLI, GLI is terminated too...
     614    try{       
     615       
     616        log("Attempting to use Windows to terminate sub processes");
     617        Runtime.getRuntime().exec("wmic PATH win32_process Where \"CommandLine like '%bin%windows%perl%bin%perl%'\" Call Terminate");
     618    } catch(Exception e) {
     619        log("@@@ Exception attempting to stop perl " + e.getMessage(), e);     
     620    }
     621    */
     622   
     623    // https://superuser.com/questions/851692/track-which-program-launches-a-certain-process
     624    // Can get parent process id and process id, and then kill those with child process id
     625    // http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows
     626    // Can get the pids of all childprocesses launched by parent process with parent pid.
     627    // And vice versa:
     628    // e.g. if you know the parent pid and want to know all the pids of the child processes spawned:
     629    // wmic process where (parentprocessid=596) get processid
     630    // if you know a child process id and want to know the parent's id:
     631    // wmic process where (processid=180) get parentprocessid
     632   
     633    String[] child_pids = null;
     634       
     635       
     636    log("Attempting to use Windows to terminate sub processes");
     637    SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+processID+") get processid");
     638    proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n
     639    // Output looks like:
     640    // ProcessId
     641    // 6040
     642    // 180
     643    // 4948
     644    // 1084
     645    // 6384
     646    // If no children, then STDERR output starts with the following, possibly succeeded by empty lines:
     647    // No Instance(s) Available.
     648    int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way.
     649    log("@@@@ Return value from proc: " + exitValue);
     650    String stdOutput = proc.getStdOutput();
     651    // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split
     652       
     653    log("@@@@ Got output:\n" + stdOutput);
     654    if(stdOutput.indexOf("No Instance(s) Available.") == -1) {
     655        //int start = stdOutput.indexOf("\n"); // skip first line, which is the column name "ProcessId"
     656        //stdOutput = stdOutput.substring(start+1);     
     657        //child_pids = stdOutput.split("/(\\r?\\n/)+"); // get rid of newline, so we should only have a list of Long numbers
     658       
     659        // Now we can get rid of the top level process
     660        p.destroy();
     661        // find all childprocesses for that pid and terminate them too:
     662        Scanner sc = new Scanner(stdOutput);
     663        while (sc.hasNext()) {
     664          if(!sc.hasNextLong()) {
     665              sc.next(); // discard the current token since it's not a Long
     666          } else {
     667            long child_pid = sc.nextLong();
     668            log("Attempting to terminate child pid: " + child_pid);
     669            SafeProcess.killWinProcessWithID(child_pid);
     670          }
     671        }
     672        sc.close();
     673    } else {
     674        // No child process. Only the top level process needs to be got rid of:
     675        p.destroy();
     676       
     677    }
     678   
     679    /*
     680   
     681    // Now we can get rid of the top level process
     682    p.destroy();
     683   
     684   
     685    // Then kill each subprocess.
     686   
     687    if(child_pids != null) {
     688        for(int i = 0; i < child_pids.length; i++) {
     689            log("Attempting to terminate child pid: |" + child_pids[i] + "|");
     690            SafeProcess.killWinProcessWithID(Long.parseLong(child_pids[i].trim()));
     691        }
     692    }
     693    */
     694   
     695}
     696
     697
     698// Run `which` on a program to find out if it is available. which.exe is included in winbin.
     699// On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same.
     700// where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html
     701// There is no `where` on Linux/Mac, must use which for them.
     702// On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names.
     703public static boolean isAvailable(String program) {     
     704    try {
     705        // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in"
     706        // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed
     707        SafeProcess prcs = new SafeProcess("which " + program);     
     708        prcs.runProcess();
     709        String output = prcs.getStdOutput();
     710        if(output.equals("")) {
     711        return false;
     712        }
     713        //System.err.println("*** 'which " + program + "' returned: " + output);
     714        return true;
     715    } catch (Exception exc) {
     716        return false;
     717    }
     718}   
     719   
     720// Google Java external process destroy kill subprocesses
     721// https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/ 
    461722   
    462723//******************** Inner class and interface definitions ********************//
     
    544805   
    545806    public InputStreamGobbler(InputStream is, boolean split_newlines)
    546     {
     807    {   
    547808    this(); // sets thread name
    548809    this.is = is;
    549810    this.split_newlines = split_newlines;
     811   
    550812    }
    551813   
Note: See TracChangeset for help on using the changeset viewer.