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