diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/framework/exec/ProcessMonitor.java b/proposal/myrmidon/src/java/org/apache/myrmidon/framework/exec/ProcessMonitor.java new file mode 100644 index 000000000..9b1e74b10 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/framework/exec/ProcessMonitor.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.myrmidon.framework.exec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.avalon.framework.logger.AbstractLogEnabled; + +/** + * This class is responsible for monitoring a process. + * It will monitor a process and if it goes longer than its timeout + * then it will terminate it. The monitor will also read data from + * stdout and stderr of process and pass it onto user specified streams. + * It will also in the future do the same for stdin. + * + * @author Peter Donald + * @version $Revision$ $Date$ + */ +public class ProcessMonitor + extends AbstractLogEnabled + implements Runnable +{ + //Time to sleep in loop while processing output + //of command and monitoring for timeout + private final static int SLEEP_TIME = 5; + + //State to indicate process is still running + private static final int STATE_RUNNING = 0; + + //State to indicate process shutdown by itself + private static final int STATE_STOPPED = 1; + + //State to indicate process was terminated due to timeout + private static final int STATE_TERMINATED = 2; + + /** + * The state of the process monitor and thus + * the state of the underlying process. + */ + private int m_state = STATE_RUNNING; + + /** + * This is the process we are monitoring. + */ + private final Process m_process; + + /** + * This specifies the time at which this process will + * timeout. 0 implies no timeout. + */ + private final long m_timeout; + + /** + * Stream from which to read standard input. + */ + private final InputStream m_input; + + /** + * Stream to write standard output to. + */ + private final OutputStream m_output; + + /** + * Stream to write standard error to. + */ + private final OutputStream m_error; + + public ProcessMonitor( final Process process, + final InputStream input, + final OutputStream output, + final OutputStream error, + final long timeoutDuration ) + { + if( null == process ) + { + throw new NullPointerException( "process" ); + } + + if( 0 > timeoutDuration ) + { + throw new IllegalArgumentException( "timeoutDuration" ); + } + + final long now = System.currentTimeMillis(); + long timeout = 0; + if( 0 != timeoutDuration ) + { + timeout = now + timeoutDuration; + } + + m_process = process; + m_input = input; + m_output = output; + m_error = error; + m_timeout = timeout; + } + + /** + * Thread method to monitor the state of the process. + */ + public void run() + { + while( STATE_RUNNING == m_state ) + { + processStandardInput(); + processStandardOutput(); + processStandardError(); + + if( !isProcessStopped() ) + { + checkTimeout(); + } + + try + { + Thread.sleep( SLEEP_TIME ); + } + catch( final InterruptedException ie ) + { + //swallow it + } + } + } + + /** + * Check if process has stopped. If it has then update state + * and return true, else return false. + */ + private boolean isProcessStopped() + { + boolean stopped; + try + { + m_process.exitValue(); + stopped = true; + } + catch( final IllegalThreadStateException itse ) + { + stopped = false; + } + + if( stopped ) + { + m_state = STATE_STOPPED; + } + + return stopped; + } + + /** + * Check if the process has exceeded time allocated to it. + * If it has reached timeout then terminate the process + * and set state to STATE_TERMINATED. + */ + private void checkTimeout() + { + if( 0 == m_timeout ) + { + return; + } + + final long now = System.currentTimeMillis(); + if( now > m_timeout ) + { + m_state = STATE_TERMINATED; + m_process.destroy(); + } + } + + /** + * Process the standard input of process. + * Reading it from user specified stream and copy it + * to processes standard input stream. + */ + private void processStandardInput() + { + if( null != m_input ) + { + //Note can not do this as the process may block + //when written to which will result in this whole + //thread being blocked. Probably need to write to + //stdin in another thread + //copy( m_input, m_process.getOutputStream() ); + } + } + + /** + * Process the standard output of process. + * Reading it and sending it to user specified stream + * or into the void. + */ + private void processStandardOutput() + { + final InputStream input = m_process.getInputStream(); + copy( input, m_output ); + } + + /** + * Process the standard error of process. + * Reading it and sending it to user specified stream + * or into the void. + */ + private void processStandardError() + { + final InputStream input = m_process.getInputStream(); + copy( input, m_error ); + } + + /** + * Copy data from specified input stream to output stream if + * output stream exists. The size of data that should be attempted + * to read is determined by calling available() on input stream. + */ + private void copy( final InputStream input, + final OutputStream output ) + { + try + { + final int available = input.available(); + if( 0 >= available ) return; + + final byte[] data = new byte[ available ]; + final int read = input.read( data ); + + if( null != output ) + { + output.write( data, 0, read ); + } + } + catch( final IOException ioe ) + { + final String message = "Error processing streams"; + getLogger().error( message, ioe ); + } + } +}