|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| AbstractMasterJVM.java | 60% | 71.2% | 91.7% | 72.7% |
|
||||||||||||||
| 1 | /*BEGIN_COPYRIGHT_BLOCK | |
| 2 | * | |
| 3 | * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu) | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * * Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * * Redistributions in binary form must reproduce the above copyright | |
| 11 | * notice, this list of conditions and the following disclaimer in the | |
| 12 | * documentation and/or other materials provided with the distribution. | |
| 13 | * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the | |
| 14 | * names of its contributors may be used to endorse or promote products | |
| 15 | * derived from this software without specific prior written permission. | |
| 16 | * | |
| 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
| 21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
| 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 28 | * | |
| 29 | * This software is Open Source Initiative approved Open Source Software. | |
| 30 | * Open Source Initative Approved is a trademark of the Open Source Initiative. | |
| 31 | * | |
| 32 | * This file is part of DrJava. Download the current version of this project | |
| 33 | * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/ | |
| 34 | * | |
| 35 | * END_COPYRIGHT_BLOCK*/ | |
| 36 | ||
| 37 | package edu.rice.cs.util.newjvm; | |
| 38 | ||
| 39 | import java.rmi.NoSuchObjectException; | |
| 40 | import java.rmi.RemoteException; | |
| 41 | import java.rmi.server.UnicastRemoteObject; | |
| 42 | import java.io.Serializable; | |
| 43 | import java.util.Map; | |
| 44 | ||
| 45 | import edu.rice.cs.util.UnexpectedException; | |
| 46 | import edu.rice.cs.plt.collect.CollectUtil; | |
| 47 | import edu.rice.cs.plt.concurrent.ConcurrentUtil; | |
| 48 | import edu.rice.cs.plt.concurrent.JVMBuilder; | |
| 49 | import edu.rice.cs.plt.concurrent.StateMonitor; | |
| 50 | import edu.rice.cs.plt.lambda.LazyThunk; | |
| 51 | import edu.rice.cs.plt.lambda.Runnable1; | |
| 52 | import edu.rice.cs.plt.lambda.Thunk; | |
| 53 | import edu.rice.cs.plt.lambda.WrappedException; | |
| 54 | import edu.rice.cs.plt.reflect.ReflectException; | |
| 55 | import edu.rice.cs.plt.reflect.ReflectUtil; | |
| 56 | ||
| 57 | import static edu.rice.cs.plt.debug.DebugUtil.debug; | |
| 58 | import static edu.rice.cs.plt.debug.DebugUtil.error; | |
| 59 | ||
| 60 | /** | |
| 61 | * An abstract class implementing the logic to invoke and control, via RMI, a second Java virtual | |
| 62 | * machine. This class is used by subclassing it. (See package documentation for more details.) | |
| 63 | * The state-changing methods of this class consistently block until a precondition for the state | |
| 64 | * change is satisfied — for example, {@link #quitSlave} cannot complete until a slave is | |
| 65 | * running. Only one thread may change the state at a time. Thus, clients should be careful | |
| 66 | * to only invoke state-changing methods when they are guaranteed to succeed (only invoking | |
| 67 | * {@code quitSlave()}, for example, when it is known to have been matched by a successful | |
| 68 | * {@code invokeSlave} invocation). | |
| 69 | * | |
| 70 | * @version $Id: AbstractMasterJVM.java 5246 2010-05-07 19:10:44Z mgricken $ | |
| 71 | */ | |
| 72 | public abstract class AbstractMasterJVM implements MasterRemote { | |
| 73 | ||
| 74 | /** | |
| 75 | * Synchronization strategy: compare-and-swap guarantees that only one thread enters a STARTING, or | |
| 76 | * QUITTING, or DISPOSED state. After that, the only state transitions out of STARTING/QUITTING occur | |
| 77 | * in the same thread (or a single designated worker thread); all other threads must wait until the | |
| 78 | * transition to FRESH or RUNNING. | |
| 79 | */ | |
| 80 | private enum State { FRESH, STARTING, RUNNING, QUITTING, DISPOSED }; | |
| 81 | ||
| 82 | /** Loads an instance of the given AbstractSlaveJVM class. Invoked in the slave JVM. */ | |
| 83 | private static class SlaveFactory implements Thunk<AbstractSlaveJVM>, Serializable { | |
| 84 | private final String _className; | |
| 85 | 161 | public SlaveFactory(String className) { _className = className; } |
| 86 | 187 | public AbstractSlaveJVM value() { |
| 87 | 187 | try { return (AbstractSlaveJVM) ReflectUtil.getStaticField(_className, "ONLY"); } |
| 88 | catch (ReflectException e) { | |
| 89 | 0 | try { return (AbstractSlaveJVM) ReflectUtil.loadObject(_className); } |
| 90 | 0 | catch (ReflectException e2) { throw new WrappedException(e2); } |
| 91 | } | |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | private final StateMonitor<State> _monitor; | |
| 96 | private final SlaveFactory _slaveFactory; | |
| 97 | private final LazyThunk<MasterRemote> _masterStub; | |
| 98 | /** The slave JVM remote stub (non-null when the state is RUNNING). */ | |
| 99 | private volatile SlaveRemote _slave; | |
| 100 | ||
| 101 | /** | |
| 102 | * Set up the master JVM object. Does not start a slave JVM. | |
| 103 | * @param slaveClassName The fully-qualified class name of the class to start up in the second JVM. Must be a | |
| 104 | * subclass of {@link AbstractSlaveJVM}. | |
| 105 | */ | |
| 106 | 161 | protected AbstractMasterJVM(String slaveClassName) { |
| 107 | 161 | _monitor = new StateMonitor<State>(State.FRESH); |
| 108 | 161 | _slaveFactory = new SlaveFactory(slaveClassName); |
| 109 | 161 | _masterStub = new LazyThunk<MasterRemote>(new Thunk<MasterRemote>() { |
| 110 | 161 | public MasterRemote value() { |
| 111 | 161 | try { return (MasterRemote) UnicastRemoteObject.exportObject(AbstractMasterJVM.this, 0); } |
| 112 | catch (RemoteException re) { | |
| 113 | 0 | error.log(re); |
| 114 | 0 | throw new UnexpectedException(re); |
| 115 | } | |
| 116 | } | |
| 117 | }); | |
| 118 | 161 | _slave = null; |
| 119 | // Make sure RMI doesn't use an IP address that might change | |
| 120 | 161 | System.setProperty("java.rmi.server.hostname", "127.0.0.1"); |
| 121 | } | |
| 122 | ||
| 123 | /** | |
| 124 | * Callback for when the slave JVM has connected, and the bidirectional communications link has been | |
| 125 | * established. Provides access to the newly-created slave JVM. | |
| 126 | */ | |
| 127 | protected abstract void handleSlaveConnected(SlaveRemote newSlave); | |
| 128 | ||
| 129 | /** | |
| 130 | * Callback for when the slave JVM has quit. | |
| 131 | * @param status The exit code returned by the slave JVM. | |
| 132 | */ | |
| 133 | protected abstract void handleSlaveQuit(int status); | |
| 134 | ||
| 135 | /** | |
| 136 | * Callback for when the slave JVM fails to either run or respond to {@link SlaveRemote#start}. | |
| 137 | * @param e Exception that occurred during startup. | |
| 138 | */ | |
| 139 | protected abstract void handleSlaveWontStart(Exception e); | |
| 140 | ||
| 141 | /** | |
| 142 | * Creates and starts the slave JVM. If the the slave is currently running, waits until it completes. | |
| 143 | * Also waits until the new process has started up and calls one of {@link #handleSlaveConnected} | |
| 144 | * or {@link #handleSlaveWontStart} before returning. | |
| 145 | * @param jvmBuilder JVMBuilder to use in starting the remote process. | |
| 146 | * @throws IllegalStateException If this object has been disposed. | |
| 147 | */ | |
| 148 | 187 | protected final void invokeSlave(JVMBuilder jvmBuilder) { |
| 149 | 187 | transition(State.FRESH, State.STARTING); |
| 150 | ||
| 151 | // update jvmBuilder with any special properties | |
| 152 | 187 | Map<String, String> props = ConcurrentUtil.getPropertiesAsMap("plt.", "drjava.", "edu.rice.cs."); |
| 153 | 187 | if (!props.containsKey("plt.log.working.dir") && // Set plt.log.working.dir, in case the working dir changes |
| 154 | (props.containsKey("plt.debug.log") || props.containsKey("plt.error.log") || | |
| 155 | props.containsKey("plt.log.factory"))) { | |
| 156 | 187 | props.put("plt.log.working.dir", System.getProperty("user.dir", "")); |
| 157 | } | |
| 158 | // include props, but shadow them with any definitions in jvmBuilder | |
| 159 | 187 | final JVMBuilder tweakedJVMBuilder = jvmBuilder.properties(CollectUtil.union(props, jvmBuilder.properties())); |
| 160 | ||
| 161 | 187 | SlaveRemote newSlave = null; |
| 162 | 187 | try { |
| 163 | 187 | debug.logStart("invoking remote JVM process"); |
| 164 | 187 | newSlave = |
| 165 | (SlaveRemote) ConcurrentUtil.exportInProcess(_slaveFactory, tweakedJVMBuilder, new Runnable1<Process>() { | |
| 166 | 167 | public void run(Process p) { |
| 167 | 167 | debug.log("Remote JVM quit"); |
| 168 | 167 | _monitor.set(State.FRESH); |
| 169 | //debug.log("Entered state " + State.FRESH); | |
| 170 | 167 | debug.logStart("handleSlaveQuit"); |
| 171 | 167 | handleSlaveQuit(p.exitValue()); |
| 172 | 28 | debug.logEnd("handleSlaveQuit"); |
| 173 | } | |
| 174 | }); | |
| 175 | 187 | debug.logEnd("invoking remote JVM process"); |
| 176 | } | |
| 177 | catch (Exception e) { | |
| 178 | 0 | debug.log(e); |
| 179 | 0 | debug.logEnd("invoking remote JVM process (failed)"); |
| 180 | 0 | _monitor.set(State.FRESH); |
| 181 | //debug.log("Entered state " + State.FRESH); | |
| 182 | 0 | handleSlaveWontStart(e); |
| 183 | } | |
| 184 | ||
| 185 | 187 | if (newSlave != null) { |
| 186 | 187 | try { newSlave.start(_masterStub.value()); } |
| 187 | catch (RemoteException e) { | |
| 188 | 0 | debug.log(e); |
| 189 | 0 | attemptQuit(newSlave); |
| 190 | 0 | _monitor.set(State.FRESH); |
| 191 | //debug.log("Entered state " + State.FRESH); | |
| 192 | 0 | handleSlaveWontStart(e); |
| 193 | 0 | return; |
| 194 | } | |
| 195 | ||
| 196 | 187 | handleSlaveConnected(newSlave); |
| 197 | 187 | _slave = newSlave; |
| 198 | 187 | _monitor.set(State.RUNNING); |
| 199 | //debug.log("Entered state " + State.RUNNING); | |
| 200 | } | |
| 201 | } | |
| 202 | ||
| 203 | /** | |
| 204 | * Quits slave JVM. If a slave is not currently started and running, blocks until that state is reached. | |
| 205 | * @throws IllegalStateException If this object has been disposed. | |
| 206 | */ | |
| 207 | 184 | protected final void quitSlave() { |
| 208 | 184 | transition(State.RUNNING, State.QUITTING); |
| 209 | 184 | attemptQuit(_slave); |
| 210 | 184 | _slave = null; |
| 211 | 184 | _monitor.set(State.FRESH); |
| 212 | //debug.log("Entered state " + State.FRESH); | |
| 213 | } | |
| 214 | ||
| 215 | /** Make a best attempt to invoke {@code slave.quit()}. Log an error if it fails. */ | |
| 216 | 184 | private static void attemptQuit(SlaveRemote slave) { |
| 217 | 184 | try { slave.quit(); } |
| 218 | 0 | catch (RemoteException e) { error.log("Unable to complete slave.quit()", e); } |
| 219 | } | |
| 220 | ||
| 221 | /** | |
| 222 | * Free the resources required for this object to respond to RMI invocations (useful for applications -- such as | |
| 223 | * testing -- that produce a large number of MasterJVMs as a program runs). Requires the slave to have | |
| 224 | * quit; blocks until that occurs. After an object has been disposed, it is no longer useful. | |
| 225 | */ | |
| 226 | 159 | protected void dispose() { |
| 227 | 159 | transition(State.FRESH, State.DISPOSED); |
| 228 | 159 | if (_masterStub.isResolved()) { |
| 229 | 159 | try { UnicastRemoteObject.unexportObject(this, true); } |
| 230 | 0 | catch (NoSuchObjectException e) { error.log(e); } |
| 231 | } | |
| 232 | } | |
| 233 | ||
| 234 | /** | |
| 235 | * Make a thread-safe state transition. Blocks until the {@code from} state is reached and this | |
| 236 | * thread is successful in performing the transition (only one thread can do so at a time). Throws | |
| 237 | * an IllegalStateException if the DISPOSED state is reached first, since there is never a transition | |
| 238 | * out of the disposed state (the alternative is to block permanently). | |
| 239 | */ | |
| 240 | 530 | private void transition(State from, State to) { |
| 241 | 530 | State s = _monitor.value(); |
| 242 | // watch all state transitions until from->to is successful or the DISPOSED state is reached | |
| 243 | 530 | while (!(s.equals(from) && _monitor.compareAndSet(from, to))) { |
| 244 | 0 | if (s.equals(State.DISPOSED)) { throw new IllegalStateException("In disposed state"); } |
| 245 | 5 | debug.log("Waiting for transition from " + s + " to " + from); |
| 246 | 5 | try { s = _monitor.ensureNotState(s); } |
| 247 | 0 | catch (InterruptedException e) { throw new UnexpectedException(e); } |
| 248 | } | |
| 249 | //debug.log("Entered state " + to); | |
| 250 | } | |
| 251 | ||
| 252 | 0 | protected boolean isDisposed() { return _monitor.value().equals(State.DISPOSED); } |
| 253 | ||
| 254 | /** No-op to prove that the master is still alive. */ | |
| 255 | 254 | public void checkStillAlive() { } |
| 256 | ||
| 257 | } | |
| 258 |
|
||||||||||