PageRenderTime 28ms CodeModel.GetById 14ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms

/interpreter/tags/at2-build270707/src/edu/vub/at/actors/natives/ELFarReference.java

http://ambienttalk.googlecode.com/
Java | 393 lines | 160 code | 43 blank | 190 comment | 20 complexity | 89690bb492ce923865f06b803189b1d3 MD5 | raw file
  1/**
  2 * AmbientTalk/2 Project
  3 * ELFarReference.java created on 28-dec-2006 at 10:45:41
  4 * (c) Programming Technology Lab, 2006 - 2007
  5 * Authors: Tom Van Cutsem & Stijn Mostinckx
  6 * 
  7 * Permission is hereby granted, free of charge, to any person
  8 * obtaining a copy of this software and associated documentation
  9 * files (the "Software"), to deal in the Software without
 10 * restriction, including without limitation the rights to use,
 11 * copy, modify, merge, publish, distribute, sublicense, and/or
 12 * sell copies of the Software, and to permit persons to whom the
 13 * Software is furnished to do so, subject to the following
 14 * conditions:
 15 *
 16 * The above copyright notice and this permission notice shall be
 17 * included in all copies or substantial portions of the Software.
 18 *
 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 26 * OTHER DEALINGS IN THE SOFTWARE.
 27 */
 28package edu.vub.at.actors.natives;
 29
 30import edu.vub.at.actors.ATAsyncMessage;
 31import edu.vub.at.actors.eventloops.BlockingFuture;
 32import edu.vub.at.actors.eventloops.Event;
 33import edu.vub.at.actors.eventloops.EventLoop;
 34import edu.vub.at.actors.eventloops.EventQueue;
 35import edu.vub.at.actors.id.ATObjectID;
 36import edu.vub.at.actors.net.ConnectionListener;
 37import edu.vub.at.actors.net.cmd.CMDTransmitATMessage;
 38import edu.vub.at.actors.net.comm.Address;
 39import edu.vub.at.actors.net.comm.CommunicationBus;
 40import edu.vub.at.actors.net.comm.NetworkException;
 41import edu.vub.at.exceptions.InterpreterException;
 42import edu.vub.at.exceptions.XIOProblem;
 43import edu.vub.at.objects.ATObject;
 44import edu.vub.at.objects.ATTable;
 45import edu.vub.at.objects.natives.NATTable;
 46import edu.vub.at.util.logging.Logging;
 47
 48import java.lang.ref.WeakReference;
 49import java.util.Vector;
 50
 51/**
 52 * An instance of the class ELFarReference represents the event loop processor for
 53 * a remote far reference. That is, the event queue of this event loop serves as 
 54 * an 'outbox' which is dedicated for storing all messages sent to a particular
 55 * receiver object hosted by a remote virtual machine.
 56 * 
 57 * This event loop handles events from its event queue by trying to transmit them
 58 * to a remote virtual machine.
 59 * 
 60 * Concurrent processing behaviour using a CSP-like nondeterministic select:
 61 *  execute = select {
 62 *       ?receive(event) => handle(event)
 63 *    [] ?interrupted() => retractOutbox()
 64 *  }
 65 *  handle(e) = select {
 66 *       connected => transmit(e)
 67 *    [] ?interrupted() => putBack(e); retractOutbox()
 68 *  }
 69 * 
 70 * @author tvcutsem
 71 */
 72public final class ELFarReference extends EventLoop implements ConnectionListener {
 73	
 74	/**
 75	 * When the {@link ELActor} owning this far reference wants to reify the event queue
 76	 * as an outbox, it will 'interrupt' this event loop by setting this field to a
 77	 * non-null value.
 78	 * <p>
 79	 * The {@link #handle(Event)} method tests for the presence of such an interrupt and
 80	 * will call the {@link #handleRetractRequest()} method if this field is set.
 81	 * <p>
 82	 * Marked volatile because this variable is read/written by multiple threads without
 83	 * synchronizing.
 84	 */
 85	private volatile BlockingFuture outboxFuture_ = null;
 86	
 87	/**
 88	 * the actor to which this far ref belongs, i.e. the only event loop that will schedule
 89	 * transmission events into this event loop's event queue
 90	 */
 91	private final ELActor owner_;
 92
 93	/** the first-class AT language value wrapping this event loop */
 94	private final NATRemoteFarRef farRef_;
 95    //TODO MAKE HIS farRef weak. But then, we have a garbage cycle TO THINK ABOUT!!
 96	//private final WeakReference farRef_;
 97	
 98	/** the <i>wire representation</i> of the remote receiver of my messages */
 99	private final ATObjectID destination_;
100	
101	/** the network layer used to actually transmit an AmbientTalk message */
102	private final CommunicationBus dispatcher_;
103	
104	/**
105	 * A state variable denoting whether the far reference denoted by this
106	 * event loop is 'connected' or 'disconnected' from its remote object.
107	 * 
108	 * If this variable is set to <tt>false</tt>, the event loop will block
109	 * and stop processing new events until it is set to <tt>true</tt> again.
110	 */
111	private boolean connected_;
112		
113	public ELFarReference(ATObjectID destination, ELActor owner, NATRemoteFarRef ref) {
114		super("far reference " + destination);
115		
116		farRef_ = ref;
117	//	farRef_ = new WeakReference(ref);
118		destination_ = destination;
119		owner_ = owner;
120		
121		connected_ = true;
122		// register the remote reference with the MembershipNotifier to keep track
123		// of the state of the connection with the remote VM
124		owner_.getHost().connectionManager_.addConnectionListener(destination_.getVirtualMachineId(), this);
125		
126		dispatcher_ = owner_.getHost().communicationBus_;
127	}
128	
129	/**
130	 * Acknowledges the interrupt set by this far reference's owning actor.
131	 * Resolves the {@link #outboxFuture_} interrupt future with the vector of transmission
132	 * events that are  in the event queue of the far reference. This is used to retrieve copies
133	 * of all messages being sent through this far reference. Note that this method performs no
134	 * deserialisation of the events into (copied) messages. Such deserialisation needs to be
135	 * done by an {@link ELActor}, not the ELFarReference which will execute this method.
136	 * 
137	 * After handling the retract request, the {@link #outboxFuture_} is reset to <tt>null</tt>.
138	 * This is extremely important because it signifies that there is no more pending retract request.
139	 */
140	public void handleRetractRequest() {
141		outboxFuture_.resolve(eventQueue_.flush());
142		// if the future is not reset to null, the event loop would continually
143		// resolve the future from this point on!
144		outboxFuture_ = null;
145	}
146	
147	/**
148	 * Process message transmission events only when the remote reference
149	 * is connected. Otherwise, wait until notified by the <tt>connected</tt>
150	 * callback.
151	 * 
152	 * When handling a transmission event from the event queue, the event
153	 * loop can only process the event if either:
154	 * <ul>
155	 *  <li>the far reference is currently <i>connected</i>. In this case,
156	 *      the incoming event is simply processed.
157	 *  <li>the far reference is disconnected, so blocked waiting to become
158	 *      reconnected, but is being interrupted by its owner to flush
159	 *      its outbox. In this case, the current event on which the event
160	 *      loop is blocked is re-inserted into the event queue (in front!)
161	 *      and the interrupt is honoured.
162	 * </ul>
163	 * 
164	 */
165	public void handle(Event event) {
166		synchronized (this) {
167			while (!connected_ && outboxFuture_ == null) {
168				try {
169					this.wait();
170				} catch (InterruptedException e) {
171					if (askedToStop_) {
172						return; // fall through and make the thread die
173					}
174				}
175			}
176			
177			if(outboxFuture_ != null) {
178				// re-enqueue the current event in its proper position
179				receivePrioritized(event);
180				
181				// flush the queue
182				handleRetractRequest();
183				
184			// else is strictly necessary as the handleInterrupt method has side effects, 
185			// removing the current event from the queue, such that it would be incorrect 
186			// to still send it 
187			} else { // if (connected_) {
188				event.process(this);
189			}
190		}
191	}
192	
193	/**
194	 * This is a named subclass of event, which allows access to the AmbientTalk message
195	 * that is being transmitted. The constructor is still executed by the {@link ELActor}
196	 * that schedules this transmission event. This ensures
197	 * that the serialization of the message happens in the correct thread.
198	 * 
199	 * @see {@link ELFarReference#event_transmit(ATAsyncMessage)}
200	 * @author smostinc
201	 */
202	private class TransmissionEvent extends Event {
203		public final Packet serializedMessage_;
204		
205		// Called by ELActor
206		public TransmissionEvent(ATAsyncMessage msg) throws XIOProblem {
207			super("transmit("+msg+")");
208			serializedMessage_ = new Packet(msg.toString(), msg);
209		}
210		
211		/**
212		 * This code is executed by the {@link ELFarReference} event loop.
213		 */
214		public void process(Object owner) {
215			Address destAddress = getDestinationVMAddress();
216
217			if (destAddress != null) {
218				try {
219					new CMDTransmitATMessage(destination_.getActorId(), serializedMessage_).send(
220							dispatcher_, destAddress);
221
222					// getting here means the message was succesfully transmitted
223					
224				} catch (NetworkException e) {
225					// TODO: the message MAY have been transmitted! (i.e. an orphan might have
226					// been created: should make this more explicit to the AT programmer)
227					Logging.RemoteRef_LOG.warn(this
228									+ ": timeout while trying to transmit message, retrying");
229					receivePrioritized(this);
230				}
231			} else {
232				Logging.RemoteRef_LOG.info(this + ": suspected a disconnection from " +
233						destination_ + " because destination VM ID was not found in address book");
234				connected_ = false;
235				receivePrioritized(this);
236			}
237		}
238	}
239	
240	/**
241	 * Inserts an AmbientTalk message into this far reference's outbox.
242	 */
243	public void event_transmit(final ATAsyncMessage msg) throws XIOProblem {
244		// the message will still be serialized in the actor's thread
245		receive(new TransmissionEvent(msg));
246		
247		// the reception of a new event may awaken a sleeping ELFarReference thread, 
248		// so we interrupt the processor, forcing it to reevaluate its conditions
249		// we don't use wait/notify because in order to notify the event loop, we
250		// would require a lock on it, which might cause this actor to block on
251		// remote communication. Therefore, we use the interrupt mechanism instead.
252		processor_.interrupt();
253	}
254	
255	/**
256	 * Interrupts this event loop by issuing a request for flushing
257	 * its event queue.
258	 * 
259	 * This code is executed by the event loop thread of the {@link #owner_}!
260	 * 
261	 * @return a table of copies for the messages currently in the outbox
262	 */
263	public ATTable retractUnsentMessages() throws InterpreterException {
264		BlockingFuture eventVectorF = setRetractingFuture();
265		Vector events = null;
266		
267		try {
268			// actor has to wait a bit until the event loop has stopped processing
269			events = (Vector) eventVectorF.get();
270		} catch(Exception e) {
271			// should never occur!
272			e.printStackTrace();
273			return NATTable.EMPTY;
274		}
275		
276		ATObject[] messages = new ATObject[events.size()];
277		
278		for(int i = 0; i < events.size(); i++) {
279			TransmissionEvent current = (TransmissionEvent)events.get(i);
280			messages[i] = current.serializedMessage_.unpack();
281		}
282			
283		return NATTable.atValue(messages);
284	}
285	
286	public ATObjectID getDestination() {
287		return destination_;
288	}
289	
290	/* ========================================================
291	 * == Implementation of the ConnectionListener interface ==
292	 * ========================================================
293	 */
294
295	public synchronized void connected() {
296		// sanity check: don't connect  twice
297		if (!connected_) {
298			Logging.RemoteRef_LOG.info(this + ": reconnected to " + destination_);
299			connected_ = true;
300			this.notify();
301			farRef_.notifyConnected();	
302		}
303	}
304
305	public synchronized void disconnected() {
306		// sanity check: don't disconnect twice
307		if (connected_) {
308			// Will only take effect when next trying to send a message
309			// If currently sending, the message will time out first.
310			Logging.RemoteRef_LOG.info(this + ": disconnected from " + destination_);
311			connected_ = false;
312			farRef_.notifyDisconnected();	
313		}
314	}
315	
316	public synchronized void takenOffline(){
317		Logging.RemoteRef_LOG.info( this + ": remote object taken offline");
318		connected_ = false;
319		farRef_.notifyTakenOffline();
320	}
321	
322	/**
323	 * Overrides the default event handling strategy of this event loop.
324	 * It is no longer possible to simply block and wait for a new event
325	 * by performing {@link EventQueue#dequeue()} because this event loop
326	 * can be triggered by two kinds of wake-up calls, either:
327	 * <ul>
328	 *  <li>a new event arrives in the event queue, or
329	 *  <li>the actor owning this far reference interrupts it to flush
330	 *  its event queue into a reified outbox representation
331	 * </ul>
332	 * 
333	 * Therefore, this event loop synchronises on two different boolean
334	 * conditions. When exiting the <tt>wait</tt>-loop, the event loop has
335	 * to check which condition woke it up:
336	 * <ul>
337	 *  <li>if it was an incoming event, handle it
338	 *  <li>if it was interrupted by the owning actor, honor the interrupt
339	 * </ul>
340	 * 
341	 * Note that while executing, the event loop takes a lock on itself!
342	 * This synchronizes event processing with state transition notifications
343	 * via the {@link #connected()} and {@link #disconnected()} methods.
344	 */
345	protected void execute() {
346		synchronized (this) {
347			while (eventQueue_.isEmpty() && outboxFuture_ == null) {
348				try {
349					this.wait();
350				} catch (InterruptedException e) {
351					if (askedToStop_) {
352						return; // fall through and make the thread die
353					}
354				}
355			}
356
357			if (outboxFuture_ != null) {
358				handleRetractRequest();
359			} else { // if(!eventQueue_.isEmpty()) {
360				try {
361					handle(eventQueue_.dequeue());
362				} catch (InterruptedException e) {
363					// fall through, perhaps the thread was notified
364					// that it had to stop
365				}
366			}
367		}
368	}
369	
370	private Address getDestinationVMAddress() {
371		return owner_.getHost().vmAddressBook_.getAddressOf(destination_.getVirtualMachineId());
372	}
373	
374	/**
375	 * Signals the far reference that its owning actor has requested to retract unsent
376	 * messages. The interrupt will be handled as soon as the processing of the current
377	 * event has finished.
378	 * @return a blocking future the ELActor thread can wait on.
379	 */
380	private BlockingFuture setRetractingFuture() {
381		outboxFuture_ = new BlockingFuture();
382		
383		// the reception of a new interrupt may awaken a sleeping ELFarReference 
384		// thread, so we interrupt them, forcing them to reevaluate their conditions
385		processor_.interrupt();
386		return outboxFuture_;
387	}
388
389	protected void finalize() throws Throwable{
390		Logging.RemoteRef_LOG.info(this + ": ELFARREF STOPPED!!");
391		
392	}
393}