pekko/akka-actor/src/main/scala/dispatch/MessageHandling.scala

170 lines
5.5 KiB
Scala
Raw Normal View History

/**
2009-12-27 16:01:53 +01:00
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
*/
package se.scalablesolutions.akka.dispatch
import se.scalablesolutions.akka.actor.{Actor, ActorRef, ActorInitializationException}
2009-10-06 00:07:27 +02:00
import org.multiverse.commitbarriers.CountDownCommitBarrier
import se.scalablesolutions.akka.AkkaException
import se.scalablesolutions.akka.util.{Duration, HashCode, Logging}
2010-09-07 18:32:50 +02:00
import java.util.{Queue, List}
import java.util.concurrent._
import concurrent.forkjoin.LinkedTransferQueue
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
2010-05-06 08:13:12 +02:00
final class MessageInvocation(val receiver: ActorRef,
val message: Any,
val sender: Option[ActorRef],
val senderFuture: Option[CompletableFuture[Any]],
val transactionSet: Option[CountDownCommitBarrier]) {
2009-12-27 08:24:11 +01:00
if (receiver eq null) throw new IllegalArgumentException("receiver is null")
2010-06-30 16:26:15 +02:00
def invoke = try {
receiver.invoke(this)
} catch {
case e: NullPointerException => throw new ActorInitializationException(
"Don't call 'self ! message' in the Actor's constructor (e.g. body of the class).")
}
def send = receiver.dispatcher.dispatch(this)
2009-10-06 00:07:27 +02:00
override def hashCode(): Int = synchronized {
var result = HashCode.SEED
2010-04-30 20:22:45 +02:00
result = HashCode.hash(result, receiver.actor)
result = HashCode.hash(result, message.asInstanceOf[AnyRef])
result
}
2009-10-06 00:07:27 +02:00
override def equals(that: Any): Boolean = synchronized {
that != null &&
that.isInstanceOf[MessageInvocation] &&
2010-04-30 20:22:45 +02:00
that.asInstanceOf[MessageInvocation].receiver.actor == receiver.actor &&
2009-07-28 17:48:11 +02:00
that.asInstanceOf[MessageInvocation].message == message
2009-10-06 00:07:27 +02:00
}
override def toString = synchronized {
2009-12-08 16:17:22 +01:00
"MessageInvocation[" +
"\n\tmessage = " + message +
"\n\treceiver = " + receiver +
"\n\tsender = " + sender +
"\n\tsenderFuture = " + senderFuture +
"\n\ttransactionSet = " + transactionSet +
"]"
2009-10-06 00:07:27 +02:00
}
}
class MessageQueueAppendFailedException(message: String) extends AkkaException(message)
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
trait MessageQueue {
2010-09-07 18:32:50 +02:00
def enqueue(handle: MessageInvocation)
def dequeue(): MessageInvocation
def size: Int
def isEmpty: Boolean
}
/* Tells the dispatcher that it should create a bounded mailbox with the specified push timeout
2010-09-07 10:12:26 +02:00
* (If capacity > 0)
*/
2010-09-07 18:32:50 +02:00
case class MailboxConfig(capacity: Int, pushTimeOut: Option[Duration], blockingDequeue: Boolean) {
/**
* Creates a MessageQueue (Mailbox) with the specified properties
* bounds = whether the mailbox should be bounded (< 0 means unbounded)
* pushTime = only used if bounded, indicates if and how long an enqueue should block
* blockDequeue = whether dequeues should block or not
*
* The bounds + pushTime generates a MessageQueueAppendFailedException if enqueue times out
*/
def newMailbox(bounds: Int = capacity,
pushTime: Option[Duration] = pushTimeOut,
blockDequeue: Boolean = blockingDequeue) : MessageQueue = {
if (bounds <= 0) { //UNBOUNDED: Will never block enqueue and optionally blocking dequeue
2010-09-07 18:32:50 +02:00
new LinkedTransferQueue[MessageInvocation] with MessageQueue {
def enqueue(handle: MessageInvocation): Unit = this add handle
def dequeue(): MessageInvocation = {
if(blockDequeue) this.take()
else this.poll()
}
}
}
else if (pushTime.isDefined) { //BOUNDED: Timeouted enqueue with MessageQueueAppendFailedException and optionally blocking dequeue
2010-09-07 18:32:50 +02:00
val time = pushTime.get
new BoundedTransferQueue[MessageInvocation](bounds) with MessageQueue {
def enqueue(handle: MessageInvocation) {
if (!this.offer(handle,time.length,time.unit))
throw new MessageQueueAppendFailedException("Couldn't enqueue message " + handle + " to " + this.toString)
}
def dequeue(): MessageInvocation = {
if (blockDequeue) this.take()
else this.poll()
}
}
}
else { //BOUNDED: Blocking enqueue and optionally blocking dequeue
2010-09-07 18:32:50 +02:00
new LinkedBlockingQueue[MessageInvocation](bounds) with MessageQueue {
def enqueue(handle: MessageInvocation): Unit = this put handle
def dequeue(): MessageInvocation = {
if(blockDequeue) this.take()
else this.poll()
}
}
}
}
}
/**
2010-09-07 18:32:50 +02:00
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
trait MessageDispatcher extends Logging {
2010-08-27 12:12:33 +02:00
protected val uuids = new ConcurrentSkipListSet[String]
def dispatch(invocation: MessageInvocation)
def start
def shutdown
def register(actorRef: ActorRef) {
if(actorRef.mailbox eq null)
actorRef.mailbox = createMailbox(actorRef)
uuids add actorRef.uuid
}
def unregister(actorRef: ActorRef) = {
2010-08-27 12:12:33 +02:00
uuids remove actorRef.uuid
//actorRef.mailbox = null //FIXME should we null out the mailbox here?
2010-06-01 09:27:14 +02:00
if (canBeShutDown) shutdown // shut down in the dispatcher's references is zero
}
2010-08-27 12:12:33 +02:00
def canBeShutDown: Boolean = uuids.isEmpty
def isShutdown: Boolean
/**
* Returns the size of the mailbox for the specified actor
*/
def mailboxSize(actorRef: ActorRef):Int
/**
* Creates and returns a mailbox for the given actor
*/
protected def createMailbox(actorRef: ActorRef): AnyRef = null
}
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
trait MessageDemultiplexer {
def select
def wakeUp
def acquireSelectedInvocations: List[MessageInvocation]
def releaseSelectedInvocations
}