|
|||||||||||||||||||
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 | 163 | public void run(Process p) { |
167 | 164 | debug.log("Remote JVM quit"); |
168 | 164 | _monitor.set(State.FRESH); |
169 | //debug.log("Entered state " + State.FRESH); | |
170 | 164 | debug.logStart("handleSlaveQuit"); |
171 | 164 | 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 | 235 | public void checkStillAlive() { } |
256 | ||
257 | } | |
258 |
|