Changeset 31650


Ignore:
Timestamp:
2017-05-05T21:39:34+12:00 (7 years ago)
Author:
ak19
Message:
  1. 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.
File:
1 edited

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