2010-09-21 18:52:41 +02:00
|
|
|
/**
|
2011-07-14 16:03:08 +02:00
|
|
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
2010-09-21 18:52:41 +02:00
|
|
|
*/
|
|
|
|
|
|
2010-10-26 12:49:25 +02:00
|
|
|
package akka.dispatch
|
2010-09-21 18:52:41 +02:00
|
|
|
|
2010-10-26 12:49:25 +02:00
|
|
|
import akka.AkkaException
|
2011-05-18 17:25:30 +02:00
|
|
|
import java.util.{ Comparator, PriorityQueue }
|
2010-10-26 12:49:25 +02:00
|
|
|
import akka.util._
|
2011-09-21 15:01:47 +02:00
|
|
|
import java.util.Queue
|
|
|
|
|
import akka.actor.ActorContext
|
2011-09-21 16:27:31 +02:00
|
|
|
import java.util.concurrent._
|
2011-09-26 11:50:26 +02:00
|
|
|
import atomic.{ AtomicInteger, AtomicReferenceFieldUpdater }
|
|
|
|
|
import annotation.tailrec
|
2010-09-21 18:52:41 +02:00
|
|
|
|
2011-04-29 17:15:00 +02:00
|
|
|
class MessageQueueAppendFailedException(message: String, cause: Throwable = null) extends AkkaException(message, cause)
|
2010-09-21 18:52:41 +02:00
|
|
|
|
2011-09-28 19:55:42 +02:00
|
|
|
private[dispatch] object Mailbox {
|
|
|
|
|
|
2011-09-26 11:39:07 +02:00
|
|
|
type Status = Int
|
2011-09-28 19:55:42 +02:00
|
|
|
|
2011-10-04 14:28:05 +02:00
|
|
|
/*
|
|
|
|
|
* the following assigned numbers CANNOT be changed without looking at the code which uses them!
|
|
|
|
|
*/
|
|
|
|
|
|
2011-09-28 19:55:42 +02:00
|
|
|
// primary status: only first three
|
2011-10-04 14:28:05 +02:00
|
|
|
final val Open = 0 // _status is not initialized in AbstractMailbox, so default must be zero!
|
2011-09-28 19:55:42 +02:00
|
|
|
final val Suspended = 1
|
|
|
|
|
final val Closed = 2
|
2011-10-04 14:28:05 +02:00
|
|
|
// secondary status: Scheduled bit may be added to Open/Suspended
|
|
|
|
|
final val Scheduled = 4
|
2011-09-23 13:14:17 +02:00
|
|
|
}
|
|
|
|
|
|
2010-09-21 18:52:41 +02:00
|
|
|
/**
|
|
|
|
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
|
|
|
*/
|
2011-10-04 15:22:41 +02:00
|
|
|
abstract class Mailbox extends AbstractMailbox with MessageQueue with SystemMessageQueue with Runnable {
|
2011-09-23 13:14:17 +02:00
|
|
|
import Mailbox._
|
|
|
|
|
|
2011-10-04 14:44:06 +02:00
|
|
|
@inline
|
2011-10-04 15:22:41 +02:00
|
|
|
final def status: Mailbox.Status = AbstractMailbox.updater.get(this)
|
2011-09-23 13:14:17 +02:00
|
|
|
|
2011-10-04 14:44:06 +02:00
|
|
|
@inline
|
2011-10-04 14:28:05 +02:00
|
|
|
final def isActive: Boolean = (status & 3) == Open
|
2011-09-28 19:55:42 +02:00
|
|
|
|
2011-10-04 14:44:06 +02:00
|
|
|
@inline
|
2011-10-04 14:28:05 +02:00
|
|
|
final def isSuspended: Boolean = (status & 3) == Suspended
|
2011-09-28 19:55:42 +02:00
|
|
|
|
2011-10-04 14:44:06 +02:00
|
|
|
@inline
|
2011-09-26 11:39:07 +02:00
|
|
|
final def isClosed: Boolean = status == Closed
|
2011-09-28 19:55:42 +02:00
|
|
|
|
2011-10-04 14:44:06 +02:00
|
|
|
@inline
|
2011-10-04 14:28:05 +02:00
|
|
|
final def isScheduled: Boolean = (status & Scheduled) != 0
|
2011-09-23 13:14:17 +02:00
|
|
|
|
2011-10-04 15:22:41 +02:00
|
|
|
@inline
|
|
|
|
|
protected final def updateStatus(oldStatus: Status, newStatus: Status): Boolean =
|
|
|
|
|
AbstractMailbox.updater.compareAndSet(this, oldStatus, newStatus)
|
|
|
|
|
|
|
|
|
|
@inline
|
|
|
|
|
protected final def setStatus(newStatus: Status): Unit =
|
|
|
|
|
AbstractMailbox.updater.set(this, newStatus)
|
|
|
|
|
|
2011-09-26 15:45:24 +02:00
|
|
|
/**
|
|
|
|
|
* Internal method to enforce a volatile write of the status
|
|
|
|
|
*/
|
|
|
|
|
@tailrec
|
2011-09-27 17:41:02 +02:00
|
|
|
final def acknowledgeStatus() {
|
2011-10-04 15:22:41 +02:00
|
|
|
val s = status
|
|
|
|
|
if (updateStatus(s, s)) ()
|
2011-09-26 15:45:24 +02:00
|
|
|
else acknowledgeStatus()
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-28 19:55:42 +02:00
|
|
|
/**
|
2011-10-04 14:28:05 +02:00
|
|
|
* set new primary status Open. Caller does not need to worry about whether
|
|
|
|
|
* status was Scheduled or not.
|
2011-09-28 19:55:42 +02:00
|
|
|
*/
|
|
|
|
|
@tailrec
|
2011-10-04 14:28:05 +02:00
|
|
|
final def becomeOpen(): Boolean = status match {
|
|
|
|
|
case Closed ⇒ setStatus(Closed); false
|
|
|
|
|
case s ⇒ updateStatus(s, Open | s & Scheduled) || becomeOpen()
|
2011-09-28 19:55:42 +02:00
|
|
|
}
|
|
|
|
|
|
2011-10-04 14:28:05 +02:00
|
|
|
/**
|
|
|
|
|
* set new primary status Suspended. Caller does not need to worry about whether
|
|
|
|
|
* status was Scheduled or not.
|
|
|
|
|
*/
|
2011-09-28 19:55:42 +02:00
|
|
|
@tailrec
|
2011-10-04 14:28:05 +02:00
|
|
|
final def becomeSuspended(): Boolean = status match {
|
|
|
|
|
case Closed ⇒ setStatus(Closed); false
|
|
|
|
|
case s ⇒ updateStatus(s, Suspended | s & Scheduled) || becomeSuspended()
|
2011-09-28 19:55:42 +02:00
|
|
|
}
|
|
|
|
|
|
2011-10-04 14:28:05 +02:00
|
|
|
/**
|
|
|
|
|
* set new primary status Closed. Caller does not need to worry about whether
|
|
|
|
|
* status was Scheduled or not.
|
|
|
|
|
*/
|
2011-09-28 19:55:42 +02:00
|
|
|
@tailrec
|
2011-10-04 14:28:05 +02:00
|
|
|
final def becomeClosed(): Boolean = status match {
|
|
|
|
|
case Closed ⇒ setStatus(Closed); false
|
|
|
|
|
case s ⇒ updateStatus(s, Closed) || becomeClosed()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set Scheduled status, keeping primary status as is.
|
|
|
|
|
*/
|
|
|
|
|
@tailrec
|
|
|
|
|
final def setAsScheduled(): Boolean = {
|
|
|
|
|
val s = status
|
|
|
|
|
/*
|
|
|
|
|
* only try to add Scheduled bit if pure Open/Suspended, not Closed or with
|
|
|
|
|
* Scheduled bit already set (this is one of the reasons why the numbers
|
|
|
|
|
* cannot be changed in object Mailbox above)
|
|
|
|
|
*/
|
|
|
|
|
if (s <= Suspended) updateStatus(s, s | Scheduled) || setAsScheduled()
|
|
|
|
|
else false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reset Scheduled status, keeping primary status as is.
|
|
|
|
|
*/
|
|
|
|
|
@tailrec
|
|
|
|
|
final def setAsIdle(): Boolean = {
|
|
|
|
|
val s = status
|
|
|
|
|
/*
|
|
|
|
|
* only try to remove Scheduled bit if currently Scheduled, not Closed or
|
|
|
|
|
* without Scheduled bit set (this is one of the reasons why the numbers
|
|
|
|
|
* cannot be changed in object Mailbox above)
|
|
|
|
|
*/
|
|
|
|
|
if (s >= Scheduled) {
|
|
|
|
|
updateStatus(s, s & ~Scheduled) || setAsIdle()
|
|
|
|
|
} else {
|
|
|
|
|
acknowledgeStatus() // this write is needed to make memory consistent after processMailbox()
|
|
|
|
|
false
|
|
|
|
|
}
|
2011-09-28 19:55:42 +02:00
|
|
|
}
|
2011-09-23 13:14:17 +02:00
|
|
|
|
|
|
|
|
def shouldBeRegisteredForExecution(hasMessageHint: Boolean, hasSystemMessageHint: Boolean): Boolean = status match {
|
2011-10-04 14:28:05 +02:00
|
|
|
case Open | Scheduled ⇒ hasMessageHint || hasSystemMessageHint || hasSystemMessages || hasMessages
|
|
|
|
|
case Closed ⇒ false
|
|
|
|
|
case _ ⇒ hasSystemMessageHint || hasSystemMessages
|
2011-09-23 13:14:17 +02:00
|
|
|
}
|
2011-09-21 15:01:47 +02:00
|
|
|
|
|
|
|
|
final def run = {
|
|
|
|
|
try { processMailbox() } catch {
|
|
|
|
|
case ie: InterruptedException ⇒ Thread.currentThread().interrupt() //Restore interrupt
|
|
|
|
|
} finally {
|
2011-09-28 19:55:42 +02:00
|
|
|
setAsIdle()
|
2011-09-23 13:14:17 +02:00
|
|
|
dispatcher.registerForExecution(this, false, false)
|
2011-09-21 15:01:47 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process the messages in the mailbox
|
|
|
|
|
*
|
|
|
|
|
* @return true if the processing finished before the mailbox was empty, due to the throughput constraint
|
|
|
|
|
*/
|
|
|
|
|
final def processMailbox() {
|
2011-09-23 13:55:52 +02:00
|
|
|
processAllSystemMessages()
|
2011-09-23 13:14:17 +02:00
|
|
|
|
2011-09-28 19:55:42 +02:00
|
|
|
if (isActive) {
|
2011-09-21 15:01:47 +02:00
|
|
|
var nextMessage = dequeue()
|
|
|
|
|
if (nextMessage ne null) { //If we have a message
|
2011-09-23 13:55:52 +02:00
|
|
|
if (dispatcher.throughput <= 1) { //If we only run one message per process {
|
2011-09-21 15:01:47 +02:00
|
|
|
nextMessage.invoke //Just run it
|
2011-09-23 13:55:52 +02:00
|
|
|
processAllSystemMessages()
|
|
|
|
|
} else { //But otherwise, if we are throttled, we need to do some book-keeping
|
2011-09-21 15:01:47 +02:00
|
|
|
var processedMessages = 0
|
|
|
|
|
val isDeadlineEnabled = dispatcher.throughputDeadlineTime > 0
|
|
|
|
|
val deadlineNs = if (isDeadlineEnabled) System.nanoTime + TimeUnit.MILLISECONDS.toNanos(dispatcher.throughputDeadlineTime)
|
|
|
|
|
else 0
|
|
|
|
|
do {
|
|
|
|
|
nextMessage.invoke
|
2011-09-23 13:14:17 +02:00
|
|
|
|
2011-09-23 13:55:52 +02:00
|
|
|
processAllSystemMessages()
|
2011-09-23 13:14:17 +02:00
|
|
|
|
2011-09-28 19:55:42 +02:00
|
|
|
nextMessage = if (isActive) { // If we aren't suspended, we need to make sure we're not overstepping our boundaries
|
2011-09-22 17:15:51 +02:00
|
|
|
processedMessages += 1
|
|
|
|
|
if ((processedMessages >= dispatcher.throughput) || (isDeadlineEnabled && System.nanoTime >= deadlineNs)) // If we're throttled, break out
|
|
|
|
|
null //We reached our boundaries, abort
|
|
|
|
|
else dequeue //Dequeue the next message
|
2011-09-23 16:21:57 +02:00
|
|
|
} else null //Abort
|
2011-09-21 15:01:47 +02:00
|
|
|
} while (nextMessage ne null)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-09-20 18:34:21 +02:00
|
|
|
|
2011-09-27 17:41:02 +02:00
|
|
|
def processAllSystemMessages() {
|
2011-09-20 18:34:21 +02:00
|
|
|
var nextMessage = systemDequeue()
|
|
|
|
|
while (nextMessage ne null) {
|
2011-09-22 17:15:51 +02:00
|
|
|
nextMessage.invoke()
|
2011-09-20 18:34:21 +02:00
|
|
|
nextMessage = systemDequeue()
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-09-21 15:01:47 +02:00
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
def dispatcher: MessageDispatcher
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait MessageQueue {
|
2011-09-21 15:01:47 +02:00
|
|
|
/*
|
|
|
|
|
* These method need to be implemented in subclasses; they should not rely on the internal stuff above.
|
|
|
|
|
*/
|
|
|
|
|
def enqueue(handle: Envelope)
|
2011-09-23 09:33:53 +02:00
|
|
|
|
2011-09-21 15:01:47 +02:00
|
|
|
def dequeue(): Envelope
|
|
|
|
|
|
2011-09-21 16:27:31 +02:00
|
|
|
def numberOfMessages: Int
|
|
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
def hasMessages: Boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait SystemMessageQueue {
|
2011-09-21 16:27:31 +02:00
|
|
|
def systemEnqueue(handle: SystemEnvelope): Unit
|
2011-09-23 09:33:53 +02:00
|
|
|
|
2011-09-21 16:27:31 +02:00
|
|
|
def systemDequeue(): SystemEnvelope
|
|
|
|
|
|
|
|
|
|
def hasSystemMessages: Boolean
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
trait DefaultSystemMessageQueue { self: SystemMessageQueue ⇒
|
2011-09-21 16:27:31 +02:00
|
|
|
val systemMessages = new ConcurrentLinkedQueue[SystemEnvelope]()
|
|
|
|
|
|
|
|
|
|
def systemEnqueue(handle: SystemEnvelope): Unit = systemMessages offer handle
|
2011-09-23 09:33:53 +02:00
|
|
|
|
2011-09-21 16:27:31 +02:00
|
|
|
def systemDequeue(): SystemEnvelope = systemMessages.poll()
|
|
|
|
|
|
|
|
|
|
def hasSystemMessages: Boolean = !systemMessages.isEmpty
|
2011-09-21 15:01:47 +02:00
|
|
|
}
|
|
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
trait UnboundedMessageQueueSemantics extends QueueBasedMessageQueue {
|
2011-09-21 15:01:47 +02:00
|
|
|
final def enqueue(handle: Envelope): Unit = queue add handle
|
|
|
|
|
final def dequeue(): Envelope = queue.poll()
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
trait BoundedMessageQueueSemantics extends QueueBasedMessageQueue {
|
2011-09-21 15:01:47 +02:00
|
|
|
def pushTimeOut: Duration
|
2011-09-23 09:33:53 +02:00
|
|
|
override def queue: BlockingQueue[Envelope]
|
2011-09-21 15:01:47 +02:00
|
|
|
|
|
|
|
|
final def enqueue(handle: Envelope) {
|
|
|
|
|
if (pushTimeOut.length > 0) {
|
|
|
|
|
queue.offer(handle, pushTimeOut.length, pushTimeOut.unit) || {
|
|
|
|
|
throw new MessageQueueAppendFailedException("Couldn't enqueue message " + handle + " to " + toString)
|
|
|
|
|
}
|
|
|
|
|
} else queue put handle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final def dequeue(): Envelope = queue.poll()
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
trait QueueBasedMessageQueue extends MessageQueue {
|
2011-09-21 18:48:54 +02:00
|
|
|
def queue: Queue[Envelope]
|
2011-09-21 16:27:31 +02:00
|
|
|
final def numberOfMessages = queue.size
|
|
|
|
|
final def hasMessages = !queue.isEmpty
|
2011-09-21 15:01:47 +02:00
|
|
|
}
|
|
|
|
|
|
2010-09-21 18:52:41 +02:00
|
|
|
/**
|
|
|
|
|
* Mailbox configuration.
|
|
|
|
|
*/
|
2011-09-21 15:01:47 +02:00
|
|
|
trait MailboxType {
|
|
|
|
|
def create(dispatcher: MessageDispatcher): Mailbox
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
/**
|
|
|
|
|
* It's a case class for Java (new UnboundedMailbox)
|
|
|
|
|
*/
|
2011-09-21 15:01:47 +02:00
|
|
|
case class UnboundedMailbox() extends MailboxType {
|
2011-09-23 09:33:53 +02:00
|
|
|
override def create(_dispatcher: MessageDispatcher) = new Mailbox with QueueBasedMessageQueue with UnboundedMessageQueueSemantics with DefaultSystemMessageQueue {
|
2011-09-22 12:08:00 +02:00
|
|
|
final val queue = new ConcurrentLinkedQueue[Envelope]()
|
2011-09-23 09:33:53 +02:00
|
|
|
final val dispatcher = _dispatcher
|
2011-09-21 18:48:54 +02:00
|
|
|
}
|
2011-09-21 15:01:47 +02:00
|
|
|
}
|
2010-09-21 18:52:41 +02:00
|
|
|
|
|
|
|
|
case class BoundedMailbox(
|
2011-05-18 17:25:30 +02:00
|
|
|
val capacity: Int = { if (Dispatchers.MAILBOX_CAPACITY < 0) Int.MaxValue else Dispatchers.MAILBOX_CAPACITY },
|
2011-01-20 17:16:44 +01:00
|
|
|
val pushTimeOut: Duration = Dispatchers.MAILBOX_PUSH_TIME_OUT) extends MailboxType {
|
2011-09-21 15:01:47 +02:00
|
|
|
|
2011-05-18 17:25:30 +02:00
|
|
|
if (capacity < 0) throw new IllegalArgumentException("The capacity for BoundedMailbox can not be negative")
|
2010-09-21 18:52:41 +02:00
|
|
|
if (pushTimeOut eq null) throw new IllegalArgumentException("The push time-out for BoundedMailbox can not be null")
|
|
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
override def create(_dispatcher: MessageDispatcher) = new Mailbox with QueueBasedMessageQueue with BoundedMessageQueueSemantics with DefaultSystemMessageQueue {
|
2011-09-22 12:08:00 +02:00
|
|
|
final val queue = new LinkedBlockingQueue[Envelope](capacity)
|
|
|
|
|
final val pushTimeOut = BoundedMailbox.this.pushTimeOut
|
2011-09-23 09:33:53 +02:00
|
|
|
final val dispatcher = _dispatcher
|
2010-09-21 18:52:41 +02:00
|
|
|
}
|
2011-09-21 15:01:47 +02:00
|
|
|
}
|
2010-09-21 18:52:41 +02:00
|
|
|
|
2011-09-21 15:01:47 +02:00
|
|
|
case class UnboundedPriorityMailbox(cmp: Comparator[Envelope]) extends MailboxType {
|
2011-09-23 09:33:53 +02:00
|
|
|
override def create(_dispatcher: MessageDispatcher) = new Mailbox with QueueBasedMessageQueue with UnboundedMessageQueueSemantics with DefaultSystemMessageQueue {
|
2011-09-21 18:48:54 +02:00
|
|
|
val queue = new PriorityBlockingQueue[Envelope](11, cmp)
|
2011-09-23 09:33:53 +02:00
|
|
|
final val dispatcher = _dispatcher
|
2011-09-21 18:48:54 +02:00
|
|
|
}
|
2011-03-09 18:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
2011-09-21 15:01:47 +02:00
|
|
|
case class BoundedPriorityMailbox(
|
|
|
|
|
val cmp: Comparator[Envelope],
|
|
|
|
|
val capacity: Int = { if (Dispatchers.MAILBOX_CAPACITY < 0) Int.MaxValue else Dispatchers.MAILBOX_CAPACITY },
|
|
|
|
|
val pushTimeOut: Duration = Dispatchers.MAILBOX_PUSH_TIME_OUT) extends MailboxType {
|
2011-03-09 18:11:45 +01:00
|
|
|
|
2011-09-21 15:01:47 +02:00
|
|
|
if (capacity < 0) throw new IllegalArgumentException("The capacity for BoundedMailbox can not be negative")
|
|
|
|
|
if (pushTimeOut eq null) throw new IllegalArgumentException("The push time-out for BoundedMailbox can not be null")
|
2011-03-09 18:11:45 +01:00
|
|
|
|
2011-09-23 09:33:53 +02:00
|
|
|
override def create(_dispatcher: MessageDispatcher) = new Mailbox with QueueBasedMessageQueue with BoundedMessageQueueSemantics with DefaultSystemMessageQueue {
|
2011-09-22 12:08:00 +02:00
|
|
|
final val queue = new BoundedBlockingQueue[Envelope](capacity, new PriorityQueue[Envelope](11, cmp))
|
|
|
|
|
final val pushTimeOut = BoundedPriorityMailbox.this.pushTimeOut
|
2011-09-23 09:33:53 +02:00
|
|
|
final val dispatcher = _dispatcher
|
2011-09-21 15:01:47 +02:00
|
|
|
}
|
|
|
|
|
}
|
2011-03-09 18:11:45 +01:00
|
|
|
|