|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| ProcessTaskController.java | 56.2% | 81.3% | 85.7% | 78.1% |
|
||||||||||||||
| 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 | } |
|
||||||||||