PageRenderTime 55ms CodeModel.GetById 11ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 1ms

/interpreter/tags/at2dist091109/src/edu/vub/at/actors/natives/ELFarReference.java

http://ambienttalk.googlecode.com/
Java | 370 lines | 143 code | 39 blank | 188 comment | 18 complexity | 4dba0434f1de8cc694f47842424ce7ac 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.cmd.CMDTransmitATMessage;
 39import edu.vub.at.actors.net.comm.Address;
 40import edu.vub.at.actors.net.comm.CommunicationBus;
 41import edu.vub.at.actors.net.comm.NetworkException;
 42import edu.vub.at.exceptions.InterpreterException;
 43import edu.vub.at.exceptions.XIOProblem;
 44import edu.vub.at.objects.ATObject;
 45import edu.vub.at.objects.ATTable;
 46import edu.vub.at.objects.natives.NATTable;
 47import edu.vub.at.util.logging.Logging;
 48
 49/**
 50 * An instance of the class ELFarReference represents the event loop processor for
 51 * a remote far reference. That is, the event queue of this event loop serves as 
 52 * an 'outbox' which is dedicated for storing all messages sent to a particular
 53 * receiver object hosted by a remote virtual machine.
 54 * 
 55 * This event loop handles events from its event queue by trying to transmit them
 56 * to a remote virtual machine.
 57 * 
 58 * Concurrent processing behaviour using a CSP-like nondeterministic select:
 59 *  execute = select {
 60 *       ?receive(event) => handle(event)
 61 *    [] ?interrupted() => retractOutbox()
 62 *  }
 63 *  handle(e) = select {
 64 *       connected => transmit(e)
 65 *    [] ?interrupted() => putBack(e); retractOutbox()
 66 *  }
 67 * 
 68 * @author tvcutsem
 69 */
 70public final class ELFarReference extends EventLoop {
 71	
 72	/**
 73	 * When the {@link ELActor} owning this far reference wants to reify the event queue
 74	 * as an outbox, it will 'interrupt' this event loop by setting this field to a
 75	 * non-null value.
 76	 * <p>
 77	 * The {@link #handle(Event)} method tests for the presence of such an interrupt and
 78	 * will call the {@link #handleRetractRequest()} method if this field is set.
 79	 * <p>
 80	 * Marked volatile because this variable is read/written by multiple threads without
 81	 * synchronizing.
 82	 */
 83	private volatile BlockingFuture outboxFuture_ = null;
 84	
 85	/**
 86	 * the actor to which this far ref belongs, i.e. the only event loop that will schedule
 87	 * transmission events into this event loop's event queue
 88	 */
 89	private final ELActor owner_;
 90
 91	/** the first-class AT language value wrapping this event loop */
 92	private final NATRemoteFarRef farRef_;
 93    //TODO MAKE HIS farRef weak. But then, we have a garbage cycle TO THINK ABOUT!!
 94	//private final WeakReference farRef_;
 95	
 96	/** the <i>wire representation</i> of the remote receiver of my messages */
 97	private final ATObjectID destination_;
 98	
 99	/** the network layer used to actually transmit an AmbientTalk message */
100	private final CommunicationBus dispatcher_;
101	
102	/**
103	 * A state variable denoting whether the far reference denoted by this
104	 * event loop is 'connected' or 'disconnected' from its remote object.
105	 * 
106	 * If this variable is set to <tt>false</tt>, the event loop will block
107	 * and stop processing new events until it is set to <tt>true</tt> again.
108	 */
109	private boolean connected_;
110		
111	public ELFarReference(ATObjectID destination, ELActor owner, NATRemoteFarRef ref, boolean isConnected) {
112		super("far reference " + destination);
113		
114		farRef_ = ref;
115	//	farRef_ = new WeakReference(ref);
116		destination_ = destination;
117		owner_ = owner;
118		connected_ = isConnected;		
119		dispatcher_ = owner_.getHost().communicationBus_;
120		
121		this.start();
122	}
123	
124	public synchronized void setConnected(boolean newState){
125		connected_ = newState;
126		this.notify();
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(ATObject, ATAsyncMessage)}
200	 * @author smostinc
201	 */
202	private class TransmissionEvent extends Event {
203		public final Packet serializedMessage_;
204		// an original message is a pair of [ATObject receiver, ATAsyncMessage message]
205		// used to be able to retract a message without involving 
206		// serialization/desearialization because sometimes o != resolve(pass(o))
207		public final ATTable originalMessage_;
208		
209		// Called by ELActor
210		
211		/**
212		 * @param pair a pair of [ATObject receiver, ATAsyncMessage message]
213		 */
214		public TransmissionEvent(ATTable pair) throws XIOProblem {
215			super("transmit("+pair+")");
216			serializedMessage_ = new Packet(pair.toString(), pair);
217			originalMessage_ = pair;
218		}
219		
220		/**
221		 * This code is executed by the {@link ELFarReference} event loop.
222		 */
223		public void process(Object owner) {
224			Address destAddress = getDestinationVMAddress();
225
226			if (destAddress != null) {
227				try {
228					new CMDTransmitATMessage(destination_.getActorId(), serializedMessage_).send(
229							dispatcher_, destAddress);
230
231					// getting here means the message was succesfully transmitted
232					
233				} catch (NetworkException e) {
234					// TODO: the message MAY have been transmitted! (i.e. an orphan might have
235					// been created: should make this more explicit to the AT programmer)
236					Logging.RemoteRef_LOG.warn(this
237									+ ": timeout while trying to transmit message, retrying");
238					receivePrioritized(this);
239				}
240			} else {
241				Logging.RemoteRef_LOG.info(this + ": suspected a disconnection from " +
242						destination_ + " because destination VM ID was not found in address book");
243				connected_ = false;
244				receivePrioritized(this);
245			}
246		}
247	}
248	
249	/**
250	 * Inserts an AmbientTalk message into this far reference's outbox.
251	 */
252	public void event_transmit(ATObject receiver, final ATAsyncMessage msg) throws XIOProblem {
253		// the message will still be serialized in the actor's thread
254		receive(new TransmissionEvent(NATTable.of(receiver, msg)));
255		
256		// the reception of a new event may awaken a sleeping ELFarReference thread, 
257		// so we interrupt the processor, forcing it to reevaluate its conditions
258		// we don't use wait/notify because in order to notify the event loop, we
259		// would require a lock on it, which might cause this actor to block on
260		// remote communication. Therefore, we use the interrupt mechanism instead.
261		processor_.interrupt();
262	}
263	
264	/**
265	 * Interrupts this event loop by issuing a request for flushing
266	 * its event queue.
267	 * 
268	 * This code is executed by the event loop thread of the {@link #owner_}!
269	 * 
270	 * @return a table of copies for the messages currently in the outbox
271	 */
272	public ATTable retractUnsentMessages() throws InterpreterException {
273		BlockingFuture eventVectorF = setRetractingFuture();
274		Vector events = null;
275		
276		try {
277			// actor has to wait a bit until the event loop has stopped processing
278			events = (Vector) eventVectorF.get();
279		} catch(Exception e) {
280			// should never occur!
281			e.printStackTrace();
282			return NATTable.EMPTY;
283		}
284		
285		ATObject[] messages = new ATObject[events.size()];
286		
287		for(int i = 0; i < events.size(); i++) {
288			TransmissionEvent current = (TransmissionEvent)events.get(i);
289			// hand out the original version of the message prior to serialization.
290			ATObject[] pair = current.originalMessage_.asNativeTable().elements_;
291			messages[i] = pair[1].asAsyncMessage();
292		}
293			
294		return NATTable.atValue(messages);
295	}
296	
297	/**
298	 * Overrides the default event handling strategy of this event loop.
299	 * It is no longer possible to simply block and wait for a new event
300	 * by performing {@link EventQueue#dequeue()} because this event loop
301	 * can be triggered by two kinds of wake-up calls, either:
302	 * <ul>
303	 *  <li>a new event arrives in the event queue, or
304	 *  <li>the actor owning this far reference interrupts it to flush
305	 *  its event queue into a reified outbox representation
306	 * </ul>
307	 * 
308	 * Therefore, this event loop synchronises on two different boolean
309	 * conditions. When exiting the <tt>wait</tt>-loop, the event loop has
310	 * to check which condition woke it up:
311	 * <ul>
312	 *  <li>if it was an incoming event, handle it
313	 *  <li>if it was interrupted by the owning actor, honor the interrupt
314	 * </ul>
315	 * 
316	 * Note that while executing, the event loop takes a lock on itself!
317	 * This synchronizes event processing with state transition notifications
318	 * via the {@link #setConnected(boolean)} method.
319	 */
320	protected void execute() {
321		synchronized (this) {
322			while (eventQueue_.isEmpty() && outboxFuture_ == null) {
323				try {
324					this.wait();
325				} catch (InterruptedException e) {
326					if (askedToStop_) {
327						return; // fall through and make the thread die
328					}
329				}
330			}
331			
332			if (outboxFuture_ != null) {
333				handleRetractRequest();
334			} else { // if(!eventQueue_.isEmpty()) {
335				try {
336					handle(eventQueue_.dequeue());
337				} catch (InterruptedException e) {
338					// fall through, perhaps the thread was notified
339					// that it had to stop
340				}
341			}
342		}
343	}
344	
345	private Address getDestinationVMAddress() {
346		return owner_.getHost().vmAddressBook_.getAddressOf(destination_.getVirtualMachineId());
347	}
348	
349	/**
350	 * Signals the far reference that its owning actor has requested to retract unsent
351	 * messages. The interrupt will be handled as soon as the processing of the current
352	 * event has finished.
353	 * @return a blocking future the ELActor thread can wait on.
354	 */
355	private BlockingFuture setRetractingFuture() {
356		// first assign future to a local variable to avoid race conditions on writing to the outboxFuture_ variable!
357		final BlockingFuture future = new BlockingFuture();
358		outboxFuture_ = future;
359		
360		// the reception of a new interrupt may awaken a sleeping ELFarReference 
361		// thread, so we interrupt them, forcing them to reevaluate their conditions
362		processor_.interrupt();
363		return future;
364	}
365
366	protected void finalize() throws Throwable{
367		Logging.RemoteRef_LOG.info(this + ": ELFARREF STOPPED!!");
368		
369	}
370}