PageRenderTime 39ms CodeModel.GetById 15ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 1ms

/interpreter/tags/reactive-pattern-matching/src/edu/vub/at/actors/natives/ELFarReference.java

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