2009-12-11 16:37:44 +01:00
|
|
|
/**
|
2009-12-27 16:01:53 +01:00
|
|
|
* Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
|
2009-12-11 16:37:44 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package se.scalablesolutions.akka.dispatch
|
|
|
|
|
|
2010-07-02 11:14:49 +02:00
|
|
|
import se.scalablesolutions.akka.actor.{ActorRef, IllegalActorStateException}
|
2010-08-21 10:45:00 +02:00
|
|
|
|
|
|
|
|
import java.util.Queue
|
|
|
|
|
import java.util.concurrent.{ConcurrentLinkedQueue, LinkedBlockingQueue}
|
2010-05-27 15:50:11 +12:00
|
|
|
|
2009-12-11 16:37:44 +01:00
|
|
|
/**
|
|
|
|
|
* Default settings are:
|
|
|
|
|
* <pre/>
|
|
|
|
|
* - withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity
|
|
|
|
|
* - NR_START_THREADS = 16
|
|
|
|
|
* - NR_MAX_THREADS = 128
|
|
|
|
|
* - KEEP_ALIVE_TIME = 60000L // one minute
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p/>
|
|
|
|
|
*
|
|
|
|
|
* The dispatcher has a fluent builder interface to build up a thread pool to suite your use-case.
|
|
|
|
|
* There is a default thread pool defined but make use of the builder if you need it. Here are some examples.
|
|
|
|
|
* <p/>
|
|
|
|
|
*
|
|
|
|
|
* Scala API.
|
|
|
|
|
* <p/>
|
|
|
|
|
* Example usage:
|
|
|
|
|
* <pre/>
|
|
|
|
|
* val dispatcher = new ExecutorBasedEventDrivenDispatcher("name")
|
|
|
|
|
* dispatcher
|
|
|
|
|
* .withNewThreadPoolWithBoundedBlockingQueue(100)
|
|
|
|
|
* .setCorePoolSize(16)
|
|
|
|
|
* .setMaxPoolSize(128)
|
|
|
|
|
* .setKeepAliveTimeInMillis(60000)
|
|
|
|
|
* .setRejectionPolicy(new CallerRunsPolicy)
|
|
|
|
|
* .buildThreadPool
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p/>
|
|
|
|
|
*
|
|
|
|
|
* Java API.
|
|
|
|
|
* <p/>
|
|
|
|
|
* Example usage:
|
|
|
|
|
* <pre/>
|
|
|
|
|
* ExecutorBasedEventDrivenDispatcher dispatcher = new ExecutorBasedEventDrivenDispatcher("name");
|
|
|
|
|
* dispatcher
|
|
|
|
|
* .withNewThreadPoolWithBoundedBlockingQueue(100)
|
|
|
|
|
* .setCorePoolSize(16)
|
|
|
|
|
* .setMaxPoolSize(128)
|
|
|
|
|
* .setKeepAliveTimeInMillis(60000)
|
|
|
|
|
* .setRejectionPolicy(new CallerRunsPolicy())
|
|
|
|
|
* .buildThreadPool();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p/>
|
|
|
|
|
*
|
|
|
|
|
* But the preferred way of creating dispatchers is to use
|
2010-03-07 09:44:03 +01:00
|
|
|
* the {@link se.scalablesolutions.akka.dispatch.Dispatchers} factory object.
|
2009-12-11 16:37:44 +01:00
|
|
|
*
|
|
|
|
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
2010-06-02 16:56:36 +02:00
|
|
|
* @param throughput positive integer indicates the dispatcher will only process so much messages at a time from the
|
|
|
|
|
* mailbox, without checking the mailboxes of other actors. Zero or negative means the dispatcher
|
|
|
|
|
* always continues until the mailbox is empty.
|
2010-06-30 16:26:15 +02:00
|
|
|
* Larger values (or zero or negative) increase througput, smaller values increase fairness
|
2009-12-11 16:37:44 +01:00
|
|
|
*/
|
2010-08-21 15:36:50 +02:00
|
|
|
class ExecutorBasedEventDrivenDispatcher(
|
2010-08-21 16:13:16 +02:00
|
|
|
_name: String,
|
2010-09-09 17:34:05 +02:00
|
|
|
val throughput: Int = Dispatchers.THROUGHPUT,
|
2010-09-07 18:32:50 +02:00
|
|
|
mailboxConfig: MailboxConfig = Dispatchers.MAILBOX_CONFIG,
|
2010-09-04 12:00:12 +02:00
|
|
|
config: (ThreadPoolBuilder) => Unit = _ => ()) extends MessageDispatcher with ThreadPoolBuilder {
|
2010-08-21 15:36:50 +02:00
|
|
|
|
2010-09-07 18:32:50 +02:00
|
|
|
def this(_name: String, throughput: Int, capacity: Int) = this(_name,throughput,MailboxConfig(capacity,None,false))
|
2010-08-21 15:36:50 +02:00
|
|
|
def this(_name: String, throughput: Int) = this(_name, throughput, Dispatchers.MAILBOX_CAPACITY) // Needed for Java API usage
|
2010-09-04 12:00:12 +02:00
|
|
|
def this(_name: String) = this(_name,Dispatchers.THROUGHPUT,Dispatchers.MAILBOX_CAPACITY) // Needed for Java API usage
|
|
|
|
|
|
2010-09-09 15:49:59 +02:00
|
|
|
//FIXME remove this from ThreadPoolBuilder
|
2010-09-07 18:32:50 +02:00
|
|
|
mailboxCapacity = mailboxConfig.capacity
|
2010-08-21 16:13:16 +02:00
|
|
|
|
2009-12-11 16:37:44 +01:00
|
|
|
@volatile private var active: Boolean = false
|
2010-03-04 21:22:16 +01:00
|
|
|
|
2010-07-18 07:13:43 +02:00
|
|
|
val name = "akka:event-driven:dispatcher:" + _name
|
2010-03-04 21:22:16 +01:00
|
|
|
init
|
|
|
|
|
|
2010-07-21 09:44:18 -04:00
|
|
|
def dispatch(invocation: MessageInvocation) = {
|
2010-09-09 15:49:59 +02:00
|
|
|
getMailbox(invocation.receiver) enqueue invocation
|
2010-07-21 09:44:18 -04:00
|
|
|
dispatch(invocation.receiver)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return the mailbox associated with the actor
|
|
|
|
|
*/
|
2010-09-09 15:49:59 +02:00
|
|
|
private def getMailbox(receiver: ActorRef) = receiver.mailbox.asInstanceOf[MessageQueue]
|
2010-07-21 09:44:18 -04:00
|
|
|
|
|
|
|
|
override def mailboxSize(actorRef: ActorRef) = getMailbox(actorRef).size
|
|
|
|
|
|
2010-09-09 15:49:59 +02:00
|
|
|
override def createMailbox(actorRef: ActorRef): AnyRef = mailboxConfig.newMailbox(bounds = mailboxCapacity, blockDequeue = false)
|
2010-05-27 15:50:11 +12:00
|
|
|
|
|
|
|
|
def dispatch(receiver: ActorRef): Unit = if (active) {
|
2010-07-21 09:44:18 -04:00
|
|
|
|
2009-12-11 16:37:44 +01:00
|
|
|
executor.execute(new Runnable() {
|
2009-12-13 12:29:18 +01:00
|
|
|
def run = {
|
2010-03-16 12:25:58 +01:00
|
|
|
var lockAcquiredOnce = false
|
2010-05-27 15:50:11 +12:00
|
|
|
var finishedBeforeMailboxEmpty = false
|
|
|
|
|
val lock = receiver.dispatcherLock
|
2010-07-21 09:44:18 -04:00
|
|
|
val mailbox = getMailbox(receiver)
|
2010-06-02 16:56:36 +02:00
|
|
|
// this do-while loop is required to prevent missing new messages between the end of the inner while
|
|
|
|
|
// loop and releasing the lock
|
2010-03-16 12:25:58 +01:00
|
|
|
do {
|
2010-04-30 20:22:45 +02:00
|
|
|
if (lock.tryLock) {
|
2010-06-02 16:56:36 +02:00
|
|
|
// Only dispatch if we got the lock. Otherwise another thread is already dispatching.
|
2010-03-16 12:25:58 +01:00
|
|
|
lockAcquiredOnce = true
|
|
|
|
|
try {
|
2010-09-09 17:34:05 +02:00
|
|
|
finishedBeforeMailboxEmpty = processMailbox(receiver,mailbox)
|
2010-03-16 12:25:58 +01:00
|
|
|
} finally {
|
2010-04-30 20:22:45 +02:00
|
|
|
lock.unlock
|
2010-06-16 16:15:34 +02:00
|
|
|
if (finishedBeforeMailboxEmpty) dispatch(receiver)
|
2010-03-04 15:18:30 +01:00
|
|
|
}
|
2009-12-15 10:31:24 +01:00
|
|
|
}
|
2010-05-27 15:50:11 +12:00
|
|
|
} while ((lockAcquiredOnce && !finishedBeforeMailboxEmpty && !mailbox.isEmpty))
|
2009-12-15 20:38:53 +01:00
|
|
|
}
|
2010-03-03 12:46:19 +01:00
|
|
|
})
|
2010-07-21 15:10:22 -04:00
|
|
|
} else {
|
|
|
|
|
log.warning("%s is shut down,\n\tignoring the rest of the messages in the mailbox of\n\t%s", toString, receiver)
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-02 16:56:36 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process the messages in the mailbox of the given actor.
|
|
|
|
|
*
|
|
|
|
|
* @return true if the processing finished before the mailbox was empty, due to the throughput constraint
|
|
|
|
|
*/
|
2010-09-09 17:34:05 +02:00
|
|
|
def processMailbox(receiver: ActorRef,mailbox: MessageQueue): Boolean = {
|
|
|
|
|
val throttle = throughput > 0
|
2010-06-02 16:56:36 +02:00
|
|
|
var processedMessages = 0
|
2010-09-09 17:34:05 +02:00
|
|
|
var nextMessage = mailbox.dequeue
|
|
|
|
|
if (nextMessage ne null) {
|
|
|
|
|
do {
|
|
|
|
|
nextMessage.invoke
|
|
|
|
|
|
|
|
|
|
if(throttle) { //Will be JIT:Ed away when false
|
|
|
|
|
processedMessages += 1
|
|
|
|
|
if (processedMessages >= throughput) //If we're throttled, break out
|
|
|
|
|
return !mailbox.isEmpty
|
|
|
|
|
}
|
|
|
|
|
nextMessage = mailbox.dequeue
|
2010-06-02 16:56:36 +02:00
|
|
|
}
|
2010-09-09 17:34:05 +02:00
|
|
|
while (nextMessage ne null)
|
2010-06-02 16:56:36 +02:00
|
|
|
}
|
2010-09-09 17:34:05 +02:00
|
|
|
|
2010-07-18 07:13:43 +02:00
|
|
|
false
|
2010-06-02 16:56:36 +02:00
|
|
|
}
|
|
|
|
|
|
2009-12-11 16:37:44 +01:00
|
|
|
def start = if (!active) {
|
2010-07-18 07:13:43 +02:00
|
|
|
log.debug("Starting up %s\n\twith throughput [%d]", toString, throughput)
|
2009-12-11 16:37:44 +01:00
|
|
|
active = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def shutdown = if (active) {
|
2010-07-18 07:13:43 +02:00
|
|
|
log.debug("Shutting down %s", toString)
|
2009-12-11 16:37:44 +01:00
|
|
|
executor.shutdownNow
|
|
|
|
|
active = false
|
2010-08-27 12:12:33 +02:00
|
|
|
uuids.clear
|
2009-12-11 16:37:44 +01:00
|
|
|
}
|
2010-05-21 20:08:49 +02:00
|
|
|
|
2010-07-02 21:57:44 +02:00
|
|
|
def ensureNotActive(): Unit = if (active) throw new IllegalActorStateException(
|
2009-12-11 16:37:44 +01:00
|
|
|
"Can't build a new thread pool for a dispatcher that is already up and running")
|
2010-07-29 17:29:51 +02:00
|
|
|
|
2010-07-18 07:13:43 +02:00
|
|
|
override def toString = "ExecutorBasedEventDrivenDispatcher[" + name + "]"
|
2010-01-02 08:40:09 +01:00
|
|
|
|
2010-07-18 07:13:43 +02:00
|
|
|
// FIXME: should we have an unbounded queue and not bounded as default ????
|
2010-09-04 12:00:12 +02:00
|
|
|
private[akka] def init = {
|
|
|
|
|
withNewThreadPoolWithLinkedBlockingQueueWithUnboundedCapacity
|
|
|
|
|
config(this)
|
|
|
|
|
buildThreadPool
|
|
|
|
|
}
|
2010-03-16 12:25:58 +01:00
|
|
|
}
|