/interpreter/tags/at2dist090708/src/edu/vub/at/actors/natives/ELFarReference.java
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}