Ignore:
Timestamp:
2017-05-01T18:23:39+12:00 (7 years ago)
Author:
ak19
Message:

Rewriting GShell.runLocal() to use SafeProcess instead of Process. This required overhaul of SafeProcess to correctly deal with interrupts as the build/import process launched in GShell can be cancelled from the CreatePane. SafeProcess now also keeps track of its internal process object and has static logging methods to simplify the changes required when swapping between the GS3 version and GS2 version of SafeProcess. May eventually call DebugStream methods from the log methods for GLI. Will add a DEBUG flag to allow verbose logging.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/gli/src/org/greenstone/gatherer/shell/GShell.java

    r26574 r31638  
    5656import org.greenstone.gatherer.metadata.DocXMLFileManager;
    5757import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
    58 import org.greenstone.gatherer.util.StaticStrings;
     58import org.greenstone.gatherer.util.SafeProcess;
     59import org.greenstone.gatherer.util.StaticStrings;
    5960import org.greenstone.gatherer.util.Utility;
    6061
     
    6364 */
    6465public class GShell
    65     extends Thread {
     66    extends Thread implements SafeProcess.ExceptionHandler {
    6667    /** A flag used to determine if this process has been asked to cancel. */
    6768    private boolean cancel = false;
     
    8485    private String commandOutput = null;
    8586
     87    /** The process safely run in this GShell. Make sure to set to null when done with. */
     88    SafeProcess prcs = null;
     89
    8690    /** Elements in process type enumeration. */
    8791    static final public int BUILD = 0;
     
    113117    static public String GSHELL_FEDORA_COLDELETE = "gshell_fedora_col_delete";
    114118
    115     /** Determine if the given process is still executing. It does this by attempting to throw an exception - not the most efficient way, but the only one as far as I know
    116      * @param process the Process to test
    117      * @return true if it is still executing, false otherwise
    118      */
    119     static public boolean processRunning(Process process) {
    120     boolean process_running = false;
    121 
    122     try {
    123         process.exitValue(); // This will throw an exception if the process hasn't ended yet.
    124     }
    125     catch(IllegalThreadStateException itse) {
    126         process_running = true;
    127     }
    128     catch(Exception exception) {
    129         DebugStream.printStackTrace(exception);
    130     }
    131     return process_running;
    132     }
    133119
    134120    /** Constructor gatherer all the data required to create a new process, and emit meaningfull messages.
     
    236222
    237223
    238     private void runLocal(String[] args, BufferedOutputStream bos)
     224    // old_runLocal uses a potentially unsafe way of running a process and an inefficient way of reading
     225    // from the process stdout and stderr streams. Replaced by new runLocal() which uses SafeProcess and
     226    // the new CustomProcessHandler inner class instantiated for each of the 2 process output streams.
     227    private void old_runLocal(String[] args, BufferedOutputStream bos)
    239228    {
    240229    try {
     
    260249        StringBuffer stdline_buffer = new StringBuffer();
    261250   
    262         while(/*type != GShell.NEW &&*/ processRunning(prcs) && !hasSignalledStop()) {
     251        while(/*type != GShell.NEW &&*/ SafeProcess.processRunning(prcs) && !hasSignalledStop()) {
    263252        // Hopefully this doesn't block if the process is trying to write to STDOUT.
    264253        if((eisr!=null) && eisr.ready()) {
     
    348337    }
    349338   
     339    private void runLocal(String[] args, BufferedOutputStream bos)
     340    {
     341    // in case we stop between import and build, let's not launch further processes
     342    // that only pressing cancel, which results in interrupts, can subsequently stop.
     343    if(hasSignalledStop()) {
     344        return;
     345    }
     346
     347    String command = "";
     348    for(int i = 0; i < args.length; i++) {
     349        command = command + args[i] + " ";
     350    }
     351   
     352    ///ystem.err.println("Command: " + command);
     353    fireMessage(type, Dictionary.get("GShell.Command") + ": " + command, status, null);
     354   
     355    prcs = new SafeProcess(args);
     356    SafeProcess.CustomProcessHandler processOutHandler
     357        = new SynchronizedProcessHandler(bos, SafeProcess.STDOUT);
     358    SafeProcess.CustomProcessHandler processErrHandler
     359        = new SynchronizedProcessHandler(bos, SafeProcess.STDERR);
     360   
     361    prcs.setExceptionHandler(this);
     362    int exitValue = prcs.runProcess(null, processOutHandler, processErrHandler); // use default procIn handling
     363   
     364    if(exitValue == 0) {
     365        System.err.println("*** Exited normally");
     366        status = OK;
     367        fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Success"), status, null);
     368    }
     369    else {
     370        System.err.println("*** Exited abnormally with exit value " + exitValue);
     371        status = ERROR;
     372        fireMessage(type, typeAsString(type) + "> " + Dictionary.get("GShell.Failure"), status, null);
     373    }
     374   
     375    prcs = null;
     376    }
     377
     378    /** When cancel is called on this GShell thread from a separate thread,
     379     * This GShell will safely terminate any process it's currently running (by interrupting it)
     380     * and will set this.prcs to null at the end. */
     381    public void cancel() {
     382    if(prcs != null) { //GShell is running a process, so interrupt the GShell/SafeProcess thread
     383
     384        SafeProcess.log("********** HAS SIGNALLED STOP. INTERRUPTING THE GSHELL/SAFEPROCESS THREAD");
     385
     386        this.interrupt(); // interrupt this thread which is running the SafeProcess prcs
     387        // this will propagate the CANCEL status
     388        prcs = null;       
     389    }
     390    hasSignalledStop(); // synchronized. Will set status to CANCELLED in thread-safe manner.   
     391    }
    350392   
    351393
     
    589631     * @return A <strong>boolean</strong> indicating if the user wanted to stop.
    590632     */
    591     public boolean hasSignalledStop() {
     633    public synchronized boolean hasSignalledStop() {
    592634    boolean has_signalled_stop = false;
    593635    if(progress != null) {
     
    639681    return name;
    640682    }
     683
     684    // From interface SafeProcess.ExceptionHandler
     685    // Called when an exception happens during the running of our perl process, as we want to set
     686    // the GShell status to ERROR.
     687    // However, exceptions when reading from our perl process' stderr and stdout streams are handled
     688    // by SynchronizedProcessHandler.gotException() below, since they happen in separate threads
     689    // from this one (the one from which the perl process is run).
     690    public void gotException(Exception e) {
     691
     692    if(e instanceof InterruptedException) {
     693        SafeProcess.log("*** InterruptedException in GShell.runLocal()");
     694        setStatus(CANCELLED); // expected exception
     695    } else {
     696        DebugStream.println("Exception in GShell.runLocal() - unexpected");
     697        DebugStream.printStackTrace(e);
     698        setStatus(ERROR); // status particularly needs to be synchronized on
     699    }
     700    }
     701
     702    public synchronized void setStatus(int newStatus) {
     703    status = newStatus;
     704    }
     705
     706    // Each instance of this class is run in its own thread by class SafeProcess.InputGobbler.
     707    // This class deals with each incoming line from the perl process' stderr or stdout streams. One
     708    // instance of this class for each stream. However, since multiple instances of this CustomProcessHandler
     709    // could be (and in fact, are) writing to the same file in their own threads, several objects, not just
     710    // the bufferedwriter object, needed to be made threadsafe.
     711    // This class also handles exceptions during the running of the perl process.
     712    // The runPerlCommand code originally would do a sendProcessStatus on each exception, so we ensure
     713    // we do that here too, to continue original behaviour. These calls are also synchronized to make their
     714    // use of the EventListeners threadsafe.
     715    protected class SynchronizedProcessHandler extends SafeProcess.CustomProcessHandler
     716    {
     717    private final BufferedOutputStream bos; // needs to be final to be able to synchronize on the shared object
     718   
     719    public SynchronizedProcessHandler(BufferedOutputStream bos, int src) {
     720        super(src); // will set this.source to STDERR or STDOUT
     721        this.bos = bos; // caller will close bw, since many more than one
     722                            // SynchronizedProcessHandlers are using it
     723    }
     724
     725    // trying to keep synchronized methods as short as possible
     726    private void logException(Exception e) {
     727        String stream = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
     728        String msg = "Got exception when processing the perl process' " + stream + " stream.";
     729
     730        DebugStream.println(msg); // method is already synchronized
     731        DebugStream.printStackTrace(e); // method is already synchronized
     732        SafeProcess.log("*** " + msg, e);
     733
     734        GShell.this.setStatus(GShell.ERROR);
     735    }
     736
     737    private void log(String msg) {
     738        DebugStream.println(msg); // already synchro
     739        System.err.println("@@@@@ " + msg);
     740    }
     741
     742    public void run(Closeable inputStream) {
     743        InputStream is = (InputStream) inputStream;
     744
     745        BufferedReader br = null;
     746        try {       
     747
     748        br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
     749        String line=null;
     750
     751        ///while ( !GShell.this.prcs.processRunning() || hasSignalledStop()) { // need to sync on prcs
     752        while ( !Thread.currentThread().isInterrupted() && (line = br.readLine()) != null ) {
     753            this.gotLine(line); // synchronized         
     754        }       
     755
     756        } catch (Exception e) {
     757        logException(e);
     758        } finally {
     759        System.err.println("*********");
     760        if(Thread.currentThread().isInterrupted()) {
     761            log("We've been asked to stop.");
     762           
     763        }
     764        SafeProcess.closeResource(br);
     765        String stream = (this.source == SafeProcess.STDERR) ? "stderr" : "stdout";
     766        System.err.println("*** In finally of " + stream + " stream");
     767        System.err.println("*********");
     768        }
     769    }
     770
     771    protected synchronized void gotLine(String line) {
     772        fireMessage(GShell.this.type,   // not final, needs synchro
     773            GShell.this.typeAsString(type) + "> " + line,
     774            GShell.this.status, // not final, needs synchro
     775            this.bos);          // needs synchro
     776    }
     777
     778    }
     779
    641780}
Note: See TracChangeset for help on using the changeset viewer.