Changeset 31650 for main

Show
Ignore:
Timestamp:
05.05.2017 21:39:34 (3 years ago)
Author:
ak19
Message:

0. Debugging in SafeProcess? is turned off. 1. Tidying up recent commit to do with terminating an external process' subproceses on Windows. Informative comments moved into a single large comment. Removed unused methods. 2. Terminating a process on Windows now recursively terminates subprocesses. The prev commit only terminated one level down, the children. 3. Work out the win process termination command only once. 4. Subtle issues fixed: when there are no subprocess ids, this info comes through STDERR, but when there are subproc ids it comes through in STDOUT. 5. Tested again on Windows, things work well.

Files:
1 modified

Legend:

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

    r31646 r31650  
    1212import java.util.Arrays; 
    1313import java.util.Scanner; 
     14import java.util.Stack; 
    1415 
    1516import com.sun.jna.*; 
     
    2829// to avoid blocking problems that can arise from a Process' input and output streams. 
    2930 
     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 
    3034public class SafeProcess { 
    31     //public static int DEBUG = 0; 
     35    public static int DEBUG = 0; 
    3236 
    3337    public static final int STDERR = 0; 
    3438    public static final int STDOUT = 1; 
    3539    public static final int STDIN = 2; 
    36  
     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     
    3744    // charset for reading process stderr and stdout streams 
    3845    //public static final String UTF8 = "UTF-8";     
     
    111118    } 
    112119 
     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 
    113125    private Process doRuntimeExec() throws IOException { 
    114126    Process prcs = null; 
     
    122134         
    123135        // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java 
    124         log("SafeProcess running: " + Arrays.toString(command_args)); 
     136        //log("SafeProcess running:" + Arrays.toString(command_args)); 
     137        StringBuffer cmdDisplay = new StringBuffer(); 
     138        for(int i = 0; i < command_args.length; i++) { 
     139            cmdDisplay.append(" ").append(command_args[i]); 
     140        } 
     141        log("SafeProcess running: [" + cmdDisplay + "]"); 
     142        cmdDisplay = null; // let the GC have it     
     143         
    125144         
    126145        if(this.envp == null) {  
     
    231250 
    232251    // Run a very basic process: with no reading from or writing to the Process' iostreams, 
    233     // this just execs the process and waits for it to return 
     252    // this just execs the process and waits for it to return. 
     253    // Don't call this method but the zero-argument runProcess() instead if your process will 
     254    // output stuff to its stderr and stdout streams but you don't need to monitory these. 
     255    // Because, as per a comment in GLI's GS3ServerThread.java, 
     256    // in Java 6, it wil block if you don't handle a process' streams when the process is 
     257    // outputting something. (Java 7+ won't block if you don't bother to handle the output streams) 
    234258    public int runBasicProcess() { 
    235259    try { 
     
    399423 
    400424 
    401             // 4. kick off the stream gobblers 
     425        // 4. kick off the stream gobblers 
    402426        this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler); 
    403427        
     
    450474    } 
    451475 
    452 // stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java   
    453 // (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?) 
    454 // stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process 
    455 // Searching for: "forcibly terminate external process launched by Java on Windows"  
    456 static void killAnyPerl() { 
    457     if(!Utility.isWindows()) return; 
    458      
    459     // only on Windows do we need to still kill perl despite process.destroy() 
    460     try{ 
    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 
    467     } catch(Exception e) { 
    468         log("@@@ Exception attempting to stop perl " + e.getMessage(), e);       
    469     } 
    470 } 
     476/* 
     477 
     478 On Windows, p.destroy() terminates process p that Java launched, 
     479 but does not terminate any processes that p may have launched. Presumably since they didn't form a proper process tree. 
     480    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 
     481    https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161(v=vs.85).aspx 
     482 
     483 Searching for: "forcibly terminate external process launched by Java on Windows"    
     484 Not possible: stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java  
     485 But can use taskkill or tskill or wmic commands to terminate a process by processID 
     486 stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process 
     487 Taskkill command can kill by Image Name, such as all running perl, e.g. taskkill /f /im perl.exe 
     488 But what if we kill perl instances not launched by GS? 
     489    /f Specifies to forcefully terminate the process(es). We need this flag switched on to kill childprocesses. 
     490    /t Terminates the specified process and any child processes which were started by it.  
     491            /t didn't work to terminate subprocesses. Maybe since the process wasn't launched as  
     492            a properly constructed processtree. 
     493    /im is the image name (the name of the program), see Image Name column in Win Task Manager. 
     494     
     495 We don't want to kill all perl running processes.  
     496 Another option is to use wmic, available since Windows XP, to kill a process based on its command 
     497 which we sort of know (SafeProcess.command) and which can be seen in TaskManager under the  
     498 "Command Line" column of the Processes tab. 
     499    https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line  
     500 The following works kill any Command Line that matches -site localsite lucene-jdbm-demo 
     501    C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate 
     502 "WMIC Wildcard Search using 'like' and %" 
     503    https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/ 
     504 However, we're not even guaranteed that every perl command GS launches will contain the collection name 
     505 Nor do we want to kill all perl processes that GS launches with bin\windows\perl\bin\perl, though this works: 
     506    wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate 
     507 The above could kill GS perl processes we don't intend to terminate, as they're not spawned by the particular 
     508 Process we're trying to terminate from the root down. 
     509     
     510 Solution: We can use taskkill or the longstanding tskill or wmic to kill a process by ID. Since we can 
     511 kill an external process that SafeProcess launched OK, and only have trouble killing any child processes 
     512 it launched, we need to know the pids of the child processes.  
     513  
     514 We can use Windows' wmic to discover the childpids of a process whose id we know. 
     515 And we can use JNA to get the process ID of the external process that SafeProcess launched. 
     516  
     517 To find the processID of the process launched by SafeProcess, 
     518 need to use Java Native Access (JNA) jars, available jna.jar and jna-platform.jar. 
     519    http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program 
     520    http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id 
     521    http://www.golesny.de/p/code/javagetpid 
     522    https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md 
     523 We're using JNA v 4.1.0, https://mvnrepository.com/artifact/net.java.dev.jna/jna 
     524   
     525 WMIC can show us a list of parent process id and process id of running processes, and then we can 
     526 kill those child processes with a specific process id. 
     527    https://superuser.com/questions/851692/track-which-program-launches-a-certain-process 
     528    http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows 
     529 WMIC can get us the pids of all childprocesses launched by parent process denoted by parent pid. 
     530 And vice versa: 
     531    if you know the parent pid and want to know all the pids of the child processes spawned: 
     532        wmic process where (parentprocessid=596) get processid 
     533    if you know a child process id and want to know the parent's id: 
     534        wmic process where (processid=180) get parentprocessid 
     535  
     536 The above is the current solution. 
     537  
     538 Eventually, instead of running a windows command to kill the process ourselves, consider changing over to use 
     539    https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java  
     540 (works with Apache license, http://www.apache.org/licenses/LICENSE-2.0) 
     541 This is a Java class that uses JNA to terminate processes. It also has the getProcessID() method.  
     542  
     543 Linux ps equivalent on Windows is "tasklist", see 
     544    http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program 
     545 
     546*/ 
    471547 
    472548// 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 
     549// Uses Java Native Access, JNA 
    478550public static long getProcessID(Process p) 
    479551{ 
     
    511583} 
    512584 
    513 // UNUSED - only kills toplevel process, same as p.destroy(). 
    514 // Call destroyProcess(p) instead to kill any childprocesses launched 
    515 static 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  
    530585 
    531586// stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java   
     
    535590static void killWinProcessWithID(long processID) { 
    536591     
    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; 
     592    String cmd = SafeProcess.getWinProcessKillCmd(processID); 
     593    if (cmd == null) return; 
    548594     
    549595    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 
     596        log("\tAttempting to terminate Win subprocess with pid: " + processID); 
     597        SafeProcess proc = new SafeProcess(cmd);             
     598        int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete" 
     599            // there is output that needs flushing, so don't use runBasicProcess() 
    558600             
    559601    } catch(Exception e) { 
     
    562604} 
    563605 
     606 
    564607// 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. 
     608// On Windows we need to do more work, since otherwise processes launched by p remain around executing until they naturally terminate. 
     609// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until it naturally terminates. 
    567610static 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      
     611    // If it isn't windows, process.destroy() terminates any child processes too 
    572612    if(!Utility.isWindows()) { 
    573613        p.destroy(); 
    574614        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 
     615    }    
     616 
    583617    if(!SafeProcess.isAvailable("wmic")) { 
    584         log("wmic used to kill subprocesses is not available. Unable to terminate subprocesses..."); 
     618        log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses..."); 
    585619        log("Kill them manually from the TaskManager or they will proceed to run to termination"); 
    586620         
     
    588622        p.destroy(); 
    589623        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"); 
     624    }    
     625     
     626    // get the process id of the process we launched, 
     627    // so we can use it to find the pids of any subprocesses it launched in order to terminate those too. 
     628     
     629    long processID = SafeProcess.getProcessID(p);        
     630    log("Attempting to terminate sub processes of Windows process with pid " + processID); 
     631    terminateSubProcessesRecursively(processID, p); 
     632     
     633} 
     634 
     635// Helper function. Only for Windows. 
     636// Counterintuitively, we're be killing all parent processess and then all child procs and all their descendants 
     637// as soon as we discover any further process each (sub)process has launched. The parent processes are killed 
     638// first in each case for 2 reasons:  
     639// 1. on Windows, killing the parent process leaves the child running as an orphan anyway, so killing the 
     640// parent is an independent action, the child process is not dependent on the parent; 
     641// 2. Killing a parent process prevents it from launching further processes while we're killing off each child process 
     642private static void terminateSubProcessesRecursively(long parent_pid, Process p) { 
     643     
     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"); 
    638646    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   
    639663    // Output looks like: 
    640664    // ProcessId 
     
    646670    // If no children, then STDERR output starts with the following, possibly succeeded by empty lines: 
    647671    // 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(); 
     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     
    661683        // find all childprocesses for that pid and terminate them too: 
     684        Stack<Long> subprocs = new Stack<Long>(); 
    662685        Scanner sc = new Scanner(stdOutput); 
    663686        while (sc.hasNext()) { 
     
    666689          } else { 
    667690            long child_pid = sc.nextLong(); 
    668             log("Attempting to terminate child pid: " + child_pid); 
    669             SafeProcess.killWinProcessWithID(child_pid); 
     691            subprocs.push(new Long(child_pid));          
    670692          } 
    671693        } 
    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())); 
     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    } 
     702} 
     703 
     704// This method should only be called on a Windows OS 
     705private static String getWinProcessKillCmd(Long processID) { 
     706    // check if we first need to init WIN_KILL_CMD. We do this only once, but can't do it in a static codeblock 
     707     
     708    if(WIN_KILL_CMD == null) {       
     709        if(SafeProcess.isAvailable("wmic")) { 
     710            // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229 
     711            WIN_KILL_CMD = "wmic process _PROCID_ delete"; // like "kill -9" on Windows 
    691712        } 
    692     } 
    693     */ 
    694      
     713        else if(SafeProcess.isAvailable("taskkill")) { // check if we have taskkill or else use the longstanding tskill 
     714         
     715            WIN_KILL_CMD = "taskkill /f /t /PID _PROCID_"; // need to forcefully /f terminate the process                
     716                //  /t "Terminates the specified process and any child processes which were started by it." 
     717                // But despite the /T flag, the above doesn't kill subprocesses. 
     718        } 
     719        else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed 
     720            WIN_KILL_CMD = "tskill _PROCID_"; // https://ss64.com/nt/tskill.html 
     721        }        
     722    } 
     723     
     724    if(WIN_KILL_CMD == null) { // can happen if none of the above cmds were available 
     725        return null; 
     726    } 
     727    return WIN_KILL_CMD.replace( "_PROCID_", Long.toString(processID) ); 
    695728} 
    696729 
     
    9731006    // logger and DebugStream print commands are synchronized, therefore thread safe. 
    9741007    public static void log(String msg) { 
     1008    if(DEBUG == 0) return; 
    9751009    //logger.info(msg); 
    9761010 
     
    9811015 
    9821016    public static void log(String msg, Exception e) { // Print stack trace on the exception 
     1017    if(DEBUG == 0) return; 
    9831018    //logger.error(msg, e); 
    9841019 
     
    9911026 
    9921027    public static void log(Exception e) { 
     1028    if(DEBUG == 0) return;       
    9931029    //logger.error(e); 
    9941030