Clover coverage report - PLT Utilities Test Coverage (plt-20120304-r5436)
Coverage timestamp: Sat Mar 3 2012 22:01:56 CST
file stats: LOC: 231   Methods: 7
NCLOC: 126   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
ProcessTaskController.java 56.2% 81.3% 85.7% 78.1%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK*
 2   
 3    PLT Utilities BSD License
 4   
 5    Copyright (c) 2007-2010 JavaPLT group at Rice University
 6    All rights reserved.
 7   
 8    Developed by: Java Programming Languages Team
 9    Rice University
 10    http://www.cs.rice.edu/~javaplt/
 11   
 12    Redistribution and use in source and binary forms, with or without modification, are permitted
 13    provided that the following conditions are met:
 14   
 15    - Redistributions of source code must retain the above copyright notice, this list of conditions
 16    and the following disclaimer.
 17    - Redistributions in binary form must reproduce the above copyright notice, this list of
 18    conditions and the following disclaimer in the documentation and/or other materials provided
 19    with the distribution.
 20    - Neither the name of the JavaPLT group, Rice University, nor the names of the library's
 21    contributors may be used to endorse or promote products derived from this software without
 22    specific prior written permission.
 23   
 24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 25    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 26    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND
 27    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 28    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 29    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 30    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 31    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 32   
 33    *END_COPYRIGHT_BLOCK*/
 34   
 35    package edu.rice.cs.plt.concurrent;
 36   
 37    import static edu.rice.cs.plt.debug.DebugUtil.error;
 38   
 39    import java.io.*;
 40    import java.util.concurrent.Executor;
 41   
 42    import edu.rice.cs.plt.io.IOUtil;
 43    import edu.rice.cs.plt.iter.IterUtil;
 44    import edu.rice.cs.plt.lambda.Runnable1;
 45    import edu.rice.cs.plt.lambda.Thunk;
 46    import edu.rice.cs.plt.lambda.WrappedException;
 47   
 48    /**
 49    * A TaskController that executes a simple task in another Java process. The task and its result must
 50    * be serializable. If the task completes successfully, the remote process is allowed to run indefinitely
 51    * (the process terminates automatically for simple tasks, but if additional non-daemon threads are spawned,
 52    * users are responsible for cleaning up the process). If the task is unsuccessful, the remote process
 53    * is immediately destroyed. A local task, scheduled by an Executor, manages the startup of and exchange
 54    * of information with the remote process. This local task is submitted
 55    * (via {@link Executor#execute}) when {@code start()} is invoked (if the executor blocks, so will
 56    * {@code start()}); its status is changed to "running" when it actually begins executing; if canceled
 57    * in the interim, the status will still be "paused" until the task begins its scheduled execution, but
 58    * no process will ever by spawned.
 59    */
 60    public class ProcessTaskController<R> extends TaskController<R> {
 61    // this could be implemented as a PollingController (preventing the need for another thread if
 62    // onExit is null), but that would be messy -- the update method would have to carefully buffer the contents
 63    // of the process's stdout stream without blocking
 64   
 65    // fields will be changed to null by discard(), but no need for volatile because it's only for garbage collection
 66    private JVMBuilder _jvmBuilder;
 67    private Executor _executor;
 68    private Thunk<? extends R> _task;
 69    // may be null, indicating nothing should be done on exit
 70    private Runnable1<? super Process> _onExit;
 71    // must be volatile because it starts uninitialized
 72    private volatile Thread _t;
 73   
 74    /**
 75    * Create, but do not start, a ProcessTaskController.
 76    * @param jvmBuilder A JVMBuilder for the remote process; must have this class, {@code task}'s class,
 77    * and their dependencies on its class path.
 78    * @param executor An executor for scheduling a local task that manages interaction with the remote
 79    * process. The local task completes once a value has been returned.
 80    * @param task A computation to perform; the task and its return value must be serializable.
 81    */
 82  3 public ProcessTaskController(JVMBuilder jvmBuilder, Executor executor, Thunk<? extends R> task) {
 83  3 _jvmBuilder = jvmBuilder;
 84  3 _executor = executor;
 85  3 _task = task;
 86  3 _onExit = null;
 87  3 _t = null;
 88    }
 89   
 90    /**
 91    * Create, but do not start, a ProcessTaskController. This constructor allows code to be executed
 92    * when the remote process terminates: if the computation terminates successfully, {@code onExit} will
 93    * be run after the process finishes (which may occur in the indefinite future if the task spawns
 94    * additional non-daemon threads).
 95    * @param jvmBuilder A JVMBuilder for the remote process; must have this class, {@code task}'s class,
 96    * and their dependencies on its class path.
 97    * @param executor An executor for scheduling a local task that manages interaction with the remote
 98    * process. If {@code onExit} is defined, the local task completes after the process
 99    * terminates and {@code onExit} has run; otherwise, the local task completes once a
 100    * value has been returned.
 101    * @param task A computation to perform; the task and its return value must be serializable.
 102    * @param onExit An action to perform when the process has quit, or {@code null} for no action.
 103    */
 104  4 public ProcessTaskController(JVMBuilder jvmBuilder, Executor executor, Thunk<? extends R> task,
 105    Runnable1<? super Process> onExit) {
 106  4 _jvmBuilder = jvmBuilder;
 107  4 _executor = executor;
 108  4 _task = task;
 109  4 _onExit = onExit;
 110  4 _t = null;
 111    }
 112   
 113  7 protected void doStart() {
 114  7 _executor.execute(new Runnable() {
 115  7 public void run() {
 116  7 _t = Thread.currentThread();
 117  7 started();
 118  7 try {
 119    // stop if the task was canceled before starting
 120  0 if (Thread.interrupted()) { throw new InterruptedException(); }
 121  7 Process p = _jvmBuilder.start(Runner.class.getName(), IterUtil.<String>empty());
 122  7 try {
 123  7 InputStream in = p.getInputStream();
 124    // skip prefix
 125  7 int matching = 0;
 126  7 while (matching < Runner.PREFIX.length) {
 127  28 int read = in.read();
 128  0 if (read == -1) { throw new EOFException("Data prefix not found"); }
 129  28 else if ((byte) read == Runner.PREFIX[matching]) { matching++; } // cast handles negatives
 130  0 else if ((byte) read == Runner.PREFIX[0]) { matching = 1; } // cast handles negatives
 131  0 else { matching = 0; }
 132    }
 133    // prefix has been matched
 134  7 ObjectOutputStream objOut = new ObjectOutputStream(p.getOutputStream());
 135  7 try { objOut.writeObject(_task); }
 136  7 finally { objOut.close(); }
 137  6 ObjectInputStream objIn = new ObjectInputStream(in);
 138  6 try {
 139  6 @SuppressWarnings("unchecked") R result = (R) objIn.readObject();
 140  6 Exception taskE = (Exception) objIn.readObject();
 141  6 RuntimeException implementationE = (RuntimeException) objIn.readObject();
 142  0 if (implementationE != null) { p.destroy(); finishedWithImplementationException(implementationE); }
 143  1 else if (taskE != null) { p.destroy(); finishedWithTaskException(taskE); }
 144    else {
 145  5 Runnable1<? super Process> onExit = _onExit; // keep local copy so it can be discarded
 146  5 finishedCleanly(result);
 147  5 if (onExit != null) {
 148  0 p.waitFor();
 149  0 onExit.run(p);
 150    }
 151    }
 152    }
 153  6 finally { objIn.close(); }
 154    }
 155    catch (EOFException e) {
 156  0 p.destroy();
 157  0 throw new IOException("Unable to run process; class path may need to be adjusted");
 158    }
 159    // destroy the process on an exception, but let it run if we completed cleanly
 160  1 catch (Throwable e) { p.destroy(); throw e; }
 161    }
 162  0 catch (InterruptedException e) { stopped(); }
 163  0 catch (InterruptedIOException e) { stopped(); }
 164  0 catch (RuntimeException e) { finishedWithImplementationException(e); }
 165  1 catch (Throwable t) { finishedWithImplementationException(new WrappedException(t)); }
 166    }
 167    });
 168    }
 169   
 170  0 protected void doStop() { _t.interrupt(); }
 171   
 172  7 protected void discard() {
 173  7 _jvmBuilder = null;
 174  7 _executor = null;
 175  7 _task = null;
 176  7 _onExit = null;
 177  7 _t = null;
 178    }
 179   
 180    /**
 181    * Reads a serialized thunk from the input stream. Writes to {@code System.out}: 1) The byte array
 182    * {@link #PREFIX}; 2) the result of running the task, or null if running failed; 3) any Exception thrown by the
 183    * task (or null); 4) any RuntimeException due to serialization errors or other implementation-related problems
 184    * (or null). Once running begins, no other output is written to {@code System.out} or {@code System.err}.
 185    */
 186    private static class Runner {
 187    /**
 188    * A byte sequence marking the beginning of the return data. Allows java commands to output
 189    * text before {@code main()} is invoked without corrupting the data stream. (This occurs, for example,
 190    * with flag "-Xrunjdwp".) In order to avoid false positives, this prefix uses non-printing ASCII values.
 191    * To simplify the matching algorithm, each digit is guaranteed to be unique -- if a particular byte
 192    * fails to match, the DFA can only jump to either the initial state or the state after a single match.
 193    */
 194    public static final byte[] PREFIX = { 0x00, 0x03, 0x7f, -0x80 };
 195   
 196  7 public static void main(String... args) {
 197  7 OutputStream out = System.out;
 198  7 IOUtil.attemptClose(System.err); // in case other objects already have a handle on it, try to close the stream
 199  7 IOUtil.ignoreSystemOut();
 200  7 IOUtil.ignoreSystemErr();
 201  7 try {
 202  7 out.write(PREFIX);
 203  7 out.flush();
 204  7 ObjectOutputStream objOut = new ObjectOutputStream(out);
 205  7 try {
 206  7 Object result = null;
 207  7 Exception taskException = null;
 208  7 RuntimeException internalException = null;
 209  7 try {
 210  7 ObjectInputStream objIn = new ObjectInputStream(System.in);
 211  7 try {
 212  7 Thunk<?> task = (Thunk<?>) objIn.readObject();
 213  6 try { result = task.value(); }
 214  1 catch (Exception e) { taskException = e; }
 215    }
 216  7 finally { objIn.close(); }
 217    }
 218  0 catch (RuntimeException e) { internalException = e; }
 219  1 catch (Throwable t) { internalException = new WrappedException(t); }
 220   
 221  7 objOut.writeObject(result);
 222  7 objOut.writeObject(taskException);
 223  7 objOut.writeObject(internalException);
 224    }
 225  6 finally { objOut.close(); }
 226    }
 227  0 catch (IOException e) { error.log("Error writing to System.out", e); }
 228    }
 229    }
 230   
 231    }