PageRenderTime 35ms CodeModel.GetById 19ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/interpreter/tags/at2dist220411/src/edu/vub/at/actors/natives/FarReferencesThreadPool.java

http://ambienttalk.googlecode.com/
Java | 292 lines | 149 code | 19 blank | 124 comment | 10 complexity | 6843211225bbba89d5e369f09c0853f8 MD5 | raw file
  1/**
  2 * AmbientTalk/2 Project
  3 * (c) Software Languages Lab, 2006 - 2010
  4 * 
  5 * Permission is hereby granted, free of charge, to any person
  6 * obtaining a copy of this software and associated documentation
  7 * files (the "Software"), to deal in the Software without
  8 * restriction, including without limitation the rights to use,
  9 * copy, modify, merge, publish, distribute, sublicense, and/or
 10 * sell copies of the Software, and to permit persons to whom the
 11 * Software is furnished to do so, subject to the following
 12 * conditions:
 13 *
 14 * The above copyright notice and this permission notice shall be
 15 * included in all copies or substantial portions of the Software.
 16 *
 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 19 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 21 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 22 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 24 * OTHER DEALINGS IN THE SOFTWARE.
 25 * 
 26**/
 27package edu.vub.at.actors.natives;
 28
 29import java.util.HashMap;
 30import java.util.concurrent.Callable;
 31import java.util.concurrent.ExecutorService;
 32import java.util.concurrent.Executors;
 33
 34import edu.vub.at.actors.ATFarReference;
 35import edu.vub.at.actors.ATLetter;
 36import edu.vub.at.actors.eventloops.BlockingFuture;
 37import edu.vub.at.actors.eventloops.Event;
 38import edu.vub.at.actors.id.ATObjectID;
 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.XTypeMismatch;
 45import edu.vub.at.objects.ATObject;
 46import edu.vub.at.objects.ATTable;
 47import edu.vub.at.objects.natives.NATNil;
 48import edu.vub.at.util.logging.Logging;
 49
 50/**
 51 * An instance of the class FarReferencesThreadPool represents a pool of threads for
 52 * all remote far references belonging to an ELVirtualMachine. 
 53 * 
 54 * ELDiscoveryActor is initialized without a FarReferencesThreadPool as it only 
 55 * holds local far references to closures corresponding to when(er):discovered listeners.
 56 * 
 57 * FarReferencesThreadPool works together with NATRemoteFarRef to ensure that messages are
 58 * sent in the order they are received by a NATFarReference. More concretely:
 59 *   -ELActor owner of the far reference enqueues a message to be transmitted in its outbox when
 60 *   calls NATFarReference.meta_receive().
 61 *   -This will trigger an event_serve in FarReferencesThreadPool and a thread on it will dequeue
 62 *   a letter from the outbox and transmits its message. 
 63 *   -After transmitting a message, event_serve() is called to check if there are other letters to be served.
 64 *   -After a reconnect, event_serve() is also called to serve letters buffered by ELActor during a disconnection.
 65 *   
 66 *  This behaviour is similar to an EventLoop, just that it may not be the same thread
 67 *  removing from the outbox, but one of the thread pool. 
 68 *  Removals from the NATFarReference outbox will be always executed by a thread of the pool 
 69 *  while additions can be executed either by ELActor owner (due to meta_receive) 
 70 *  or a thread of the pool if the message transmission failed and needs to be put back to the outbox.
 71 */
 72public final class FarReferencesThreadPool {
 73	
 74	
 75	/**
 76	 * The virtual machine to which this far ref pool belongs.
 77	 * One can get the dispatcher from ELVirtualMachine, but keep for optimization.
 78	 */
 79	private final ELVirtualMachine host_;
 80
 81	/** the network layer used to actually transmit an AmbientTalk message */ 
 82	private final CommunicationBus dispatcher_;
 83	
 84	/** the pool of threads**/
 85	private final ExecutorService pool;
 86	
 87	
 88	/**
 89	 *  map of reference (NATRemoteFarREf) -> future for retract requests (BlockingFuture) 
 90	 *  to be able to synchronize a thread performing a retract operation, and 
 91	 *  a thread transmitting a message when the request is processed.
 92	 *  See {@link sync_event_retractUnsentMessages}
 93	 */
 94	private final HashMap<ATFarReference, BlockingFuture>  retractFutures_;
 95	
 96
 97	public FarReferencesThreadPool(ELVirtualMachine host) {
 98		host_ = host;
 99		dispatcher_ = host_.communicationBus_;
100		pool = Executors.newCachedThreadPool();
101		retractFutures_ = new HashMap();
102	}
103			
104	protected final void receive(Event event) {
105		Handle h = new Handle(event);
106	   	pool.execute( h);
107	}
108	
109	// helper class to wrap the AT event to be
110	// processed.
111	class Handle implements Runnable{
112		private Event event_;
113		public Handle( Event event){
114			event_ = event;
115		}
116		public void run() {
117			event_.process(host_);
118		}	
119	}
120	
121
122	/**
123	 * This is a named subclass of event, which allows access to the letter
124	 * that is being transmitted. The constructor is executed by:
125	 * ELActor sending the message when reference is connected, or
126	 * a thread in the pool that schedules this transmission event after a reconnection.  
127	 */
128	class TransmissionEvent extends Event{
129		/** the letter containing the serialized and original message */	
130		public final ATLetter letter_;
131		/** the <i>wire representation</i> of the remote receiver of this message */
132		public final ATObjectID destination_;
133		/* the first-class AT reference sending this message */
134		public final ATFarReference reference_;
135		
136		public TransmissionEvent(ATFarReference reference, ATLetter letter) throws InterpreterException {
137			super ("transmit( ["+ letter.base_receiver().asFarReference() + ","+ letter.base_message().asAsyncMessage() +"])");
138			letter_ = letter;
139			reference_ = reference;
140			destination_ =  reference_.asNativeFarReference().impl_getObjectId();
141		}
142		public void process(Object owner){
143			Address destAddress = getDestinationVMAddress();
144			if (destAddress != null) {
145				try {		
146					new CMDTransmitATMessage(destination_.getActorId(), letter_.asNativeOutboxLetter().impl_getSerializedMessage()).send(
147							dispatcher_, destAddress);
148					// getting here means the message was succesfully transmitted
149					reference_.asNativeRemoteFarReference().setTransmitting(false);
150					// check if 1) there is a retract request for this reference
151					// and afterwards 2) if another message to be transmitted.
152					// Needed now because pool is not an event loop.
153					handleRetractRequest(reference_);
154					reference_.asNativeRemoteFarReference().impl_transmit();
155				} catch (NetworkException e) {
156					// TODO: the message MAY have been transmitted! (i.e. an orphan might have
157					// been created: should make this more explicit to the AT programmer)
158					// To solve this add message ids, and check you don't process twice the same message.
159					Logging.RemoteRef_LOG.warn(reference_
160							+ ": timeout while trying to transmit message, retrying");
161					// try to send it again, if the remote VM went offline, 
162					// next time this message is processed destAddress == null.
163					this.process(owner);
164				} catch (XTypeMismatch e) {
165					Logging.RemoteRef_LOG.warn(reference_
166							+ ": unexpected type mismatch: " + e.getMessage());
167					e.printStackTrace();
168				} catch (InterpreterException e) {
169					Logging.RemoteRef_LOG.warn(reference_
170							+ ": unexpected error while handling retract request after a successful transmission: " + e.getMessage());
171					e.printStackTrace();
172				} 
173			} else {
174				Logging.RemoteRef_LOG.info(reference_ + ": suspected a disconnection from " +
175						destination_ + " because destination VM ID was not found in address book");
176				// destAddress is null is because it was removed from a event_memberLeft();	
177				try {
178					reference_.asNativeRemoteFarReference().impl_transmitFailed(letter_);
179					handleRetractRequest(reference_); 
180				} catch (XTypeMismatch e) {
181					Logging.RemoteRef_LOG.warn(reference_
182							+ ": unexpected type mismatch: " + e.getMessage());
183					e.printStackTrace();
184				} catch (InterpreterException e) {
185					Logging.RemoteRef_LOG.warn(reference_
186							+ ": unexpected error while handling retract request after transmission failed: " + e.getMessage());
187					e.printStackTrace();
188				}					
189			}
190		}
191		private Address getDestinationVMAddress() {
192			return host_.vmAddressBook_.getAddressOf(destination_.getVirtualMachineId());
193		}
194	}
195	/** transmitting is embedded first in another Event so that only 
196	  * a thread from this thread pool will dequeue messages from the mailbox.
197	  * 
198	  */
199	public void event_serve(final ATFarReference reference) {
200		receive(new Event("serve()") {
201			public void process(Object owner) {
202				try {
203					ATObject result = reference.asNativeRemoteFarReference().impl_serve();
204					// do not span a new thread to transmit, try to transmit it itself. 
205					// Not comparing to Evaluator.getNil() because it will return a different instance.
206					if (!( result instanceof NATNil)) {
207						TransmissionEvent transmit = new TransmissionEvent(reference, result.asNativeOutboxLetter());
208						transmit.process(owner);
209					}
210				} catch (InterpreterException e) {
211					Logging.RemoteRef_LOG.warn(this + ": serve() failed ", e);
212				}
213			}
214		} );
215	}
216	
217	/**
218	 * Signals that the owning actor of a far reference has requested to retract unsent
219	 * messages. The request will be handled as soon as the transmission of the current
220	 * letter has finished.
221	 * 
222	 * Note that it cannot happen that two consecutives retracts requests arrive for the
223	 * same reference since the ELActor is blocked on a future waiting for the request 
224	 * to be processed.
225	 * 
226	 * @return a blocking future the ELActor thread can wait on.
227	 */
228	private BlockingFuture setRetractingFuture(ATFarReference reference) {
229		// first assign future to a local variable to avoid race conditions on writing to the outboxFuture_ variable!
230		final BlockingFuture future = new BlockingFuture();
231		synchronized(this) { 
232			// synchronized access because different thread in the pool could
233			// be processing retractUnsentMessages requests for different references.  
234			retractFutures_.put(reference, future);
235		}
236		return future;
237	}
238	
239	/**
240	 * Checks in the {@link #retractFutures_} for pending retractUnsentMessages request 
241	 * for a given reference.
242	 * 
243	 * After handling the retract request, the entry in the {@link #retractFutures_} for 
244	 * the given reference is removed. This is extremely important because it signifies
245	 * that there is no more pending retract request for this reference.
246	 */
247	private synchronized void handleRetractRequest(ATFarReference reference) throws XTypeMismatch, InterpreterException{	
248		BlockingFuture retractFuture = retractFutures_.remove(reference);
249		if (retractFuture != null) {
250			retractFuture.resolve(reference.asNativeFarReference().impl_retractUnsentMessages());
251		}
252	}
253	
254	public ATTable sync_event_retractUnsentMessages(final ATFarReference reference) throws InterpreterException {
255		try {
256			return (ATTable) pool.submit(new Callable() {
257				public Object call() throws Exception {
258					/** we need to synchronize the whole block to  make sure that getTransmitting and 
259					 * adding the retract future to the map is done atomically to avoid that the
260					 * ELActor is block till the next transmission event because the transmission
261					 * thread didn't see the request because:
262					 * 		Transmission -thread 1		Retract Request - thread 2
263					 *	T1									getTransmit() -> true
264					 *	T2   	setTransmit(false)	
265					 *	T3		handleRetractRequest()
266					 *	T4									setRetractingFuture(reference);
267					 * 
268					*/
269					synchronized (this) {
270						if ( reference.asNativeRemoteFarReference().getTransmitting()){
271							// if there is a thread transmitting a message for this reference:
272							// resolve the future with a BlockingFuture to wait for the result of the transmission.
273							BlockingFuture future = setRetractingFuture(reference);
274							return future.get();
275						} else {
276							// if there is no thread transmitting a message for this reference:
277							// resolve the future immediately with the content of its oubox.
278							return reference.asNativeFarReference().impl_retractUnsentMessages();
279						}
280					}
281				}
282			}).get();
283		} catch (Exception e) {
284			if (e instanceof InterpreterException) {
285				throw (InterpreterException) e;
286			} else {
287				Logging.RemoteRef_LOG.fatal("Unexpected Java exception: "+e.getMessage(), e);
288				throw new RuntimeException("Unexpected exception: "+e);
289			}
290		}
291	}
292}