2010-05-03 19:32:40 +02:00
|
|
|
|
/**
|
2012-01-19 18:21:06 +01:00
|
|
|
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
2010-05-03 19:32:40 +02:00
|
|
|
|
*/
|
2010-10-26 12:49:25 +02:00
|
|
|
|
package akka.routing
|
2010-02-13 21:45:35 +01:00
|
|
|
|
|
2012-06-15 13:04:10 +02:00
|
|
|
|
import language.implicitConversions
|
|
|
|
|
|
import language.postfixOps
|
2011-07-28 15:48:03 +03:00
|
|
|
|
import akka.actor._
|
2012-06-29 13:33:20 +02:00
|
|
|
|
import scala.concurrent.util.Duration
|
|
|
|
|
|
import scala.concurrent.util.duration._
|
2012-05-16 17:04:13 +02:00
|
|
|
|
import akka.ConfigurationException
|
2012-02-01 14:04:01 +01:00
|
|
|
|
import akka.pattern.pipe
|
2012-02-03 18:08:43 +01:00
|
|
|
|
import com.typesafe.config.Config
|
2012-01-05 17:54:33 +01:00
|
|
|
|
import scala.collection.JavaConversions.iterableAsScalaIterable
|
2012-02-03 18:08:43 +01:00
|
|
|
|
import java.util.concurrent.atomic.{ AtomicLong, AtomicBoolean }
|
|
|
|
|
|
import java.util.concurrent.TimeUnit
|
2012-06-26 18:19:55 +02:00
|
|
|
|
import scala.concurrent.forkjoin.ThreadLocalRandom
|
2012-02-10 14:13:40 +01:00
|
|
|
|
import akka.dispatch.Dispatchers
|
2012-04-10 12:07:14 +02:00
|
|
|
|
import scala.annotation.tailrec
|
2012-08-08 15:57:30 +02:00
|
|
|
|
import concurrent.ExecutionContext
|
2012-09-14 10:08:40 +02:00
|
|
|
|
import scala.concurrent.util.FiniteDuration
|
2012-02-03 18:08:43 +01:00
|
|
|
|
|
2011-12-08 14:30:57 +01:00
|
|
|
|
/**
|
2011-12-11 22:34:38 +01:00
|
|
|
|
* A RoutedActorRef is an ActorRef that has a set of connected ActorRef and it uses a Router to
|
|
|
|
|
|
* send a message to on (or more) of these actors.
|
2011-12-08 14:30:57 +01:00
|
|
|
|
*/
|
|
|
|
|
|
private[akka] class RoutedActorRef(_system: ActorSystemImpl, _props: Props, _supervisor: InternalActorRef, _path: ActorPath)
|
2012-06-13 17:57:56 +02:00
|
|
|
|
extends RepointableActorRef(_system, _props, _supervisor, _path) {
|
2011-12-08 14:30:57 +01:00
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
// verify that a BalancingDispatcher is not used with a Router
|
|
|
|
|
|
if (_props.routerConfig != NoRouter && _system.dispatchers.isBalancingDispatcher(_props.routerConfig.routerDispatcher)) {
|
|
|
|
|
|
throw new ConfigurationException(
|
|
|
|
|
|
"Configuration for " + this +
|
|
|
|
|
|
" is invalid - you can not use a 'BalancingDispatcher' as a Router's dispatcher, you can however use it for the routees.")
|
2012-05-18 19:25:43 +02:00
|
|
|
|
}
|
2012-02-09 16:59:16 +01:00
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
_props.routerConfig.verifyConfig()
|
|
|
|
|
|
|
2012-08-09 16:43:12 +02:00
|
|
|
|
override def newCell(old: Cell): Cell = new RoutedActorCell(system, this, props, supervisor, old.asInstanceOf[UnstartedCell].uid)
|
2012-06-13 17:57:56 +02:00
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-09 16:43:12 +02:00
|
|
|
|
private[akka] class RoutedActorCell(_system: ActorSystemImpl, _ref: InternalActorRef, _props: Props, _supervisor: InternalActorRef, _uid: Int)
|
2012-06-13 17:57:56 +02:00
|
|
|
|
extends ActorCell(
|
|
|
|
|
|
_system,
|
|
|
|
|
|
_ref,
|
|
|
|
|
|
_props.copy(creator = () ⇒ _props.routerConfig.createActor(), dispatcher = _props.routerConfig.routerDispatcher),
|
2012-09-07 10:02:05 +02:00
|
|
|
|
_supervisor) {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
|
2012-02-09 16:59:16 +01:00
|
|
|
|
private[akka] val routerConfig = _props.routerConfig
|
2012-02-09 22:58:28 +01:00
|
|
|
|
private[akka] val resizeInProgress = new AtomicBoolean
|
2012-01-11 11:13:03 +01:00
|
|
|
|
private val resizeCounter = new AtomicLong
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
2011-12-13 16:05:56 +01:00
|
|
|
|
@volatile
|
2012-01-09 20:25:24 +01:00
|
|
|
|
private var _routees: IndexedSeq[ActorRef] = IndexedSeq.empty[ActorRef] // this MUST be initialized during createRoute
|
2011-12-13 16:05:56 +01:00
|
|
|
|
def routees = _routees
|
|
|
|
|
|
|
2012-02-09 22:58:28 +01:00
|
|
|
|
@volatile
|
|
|
|
|
|
private var _routeeProvider: RouteeProvider = _
|
|
|
|
|
|
def routeeProvider = _routeeProvider
|
|
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
val route = {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
val routeeProps = _props.copy(routerConfig = NoRouter)
|
|
|
|
|
|
_routeeProvider = routerConfig.createRouteeProvider(this, routeeProps)
|
|
|
|
|
|
val r = routerConfig.createRoute(routeeProvider)
|
2012-06-13 17:57:56 +02:00
|
|
|
|
// initial resize, before message send
|
2012-09-11 19:11:20 +02:00
|
|
|
|
routerConfig.resizer foreach { resizer ⇒
|
|
|
|
|
|
if (resizer.isTimeForResize(resizeCounter.getAndIncrement()))
|
|
|
|
|
|
resizer.resize(routeeProvider)
|
2012-02-10 14:13:40 +01:00
|
|
|
|
}
|
2012-06-13 17:57:56 +02:00
|
|
|
|
r
|
|
|
|
|
|
}
|
2012-02-09 16:59:16 +01:00
|
|
|
|
|
2012-08-09 16:43:12 +02:00
|
|
|
|
start(sendSupervise = false, _uid)
|
2012-06-13 17:57:56 +02:00
|
|
|
|
|
2012-02-09 16:59:16 +01:00
|
|
|
|
/*
|
|
|
|
|
|
* end of construction
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
def applyRoute(sender: ActorRef, message: Any): Iterable[Destination] = message match {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
case _: AutoReceivedMessage ⇒ Destination(self, self) :: Nil
|
2012-02-09 16:59:16 +01:00
|
|
|
|
case CurrentRoutees ⇒
|
|
|
|
|
|
sender ! RouterRoutees(_routees)
|
|
|
|
|
|
Nil
|
|
|
|
|
|
case _ ⇒
|
|
|
|
|
|
if (route.isDefinedAt(sender, message)) route(sender, message)
|
|
|
|
|
|
else Nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-01-17 15:53:12 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Adds the routees to existing routees.
|
|
|
|
|
|
* Adds death watch of the routees so that they are removed when terminated.
|
|
|
|
|
|
* Not thread safe, but intended to be called from protected points, such as
|
|
|
|
|
|
* `RouterConfig.createRoute` and `Resizer.resize`
|
|
|
|
|
|
*/
|
2012-08-28 20:54:16 +02:00
|
|
|
|
private[akka] def addRoutees(newRoutees: Iterable[ActorRef]): Unit = {
|
2012-01-09 20:25:24 +01:00
|
|
|
|
_routees = _routees ++ newRoutees
|
|
|
|
|
|
// subscribe to Terminated messages for all route destinations, to be handled by Router actor
|
2012-06-13 17:57:56 +02:00
|
|
|
|
newRoutees foreach watch
|
2012-01-09 20:25:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-01-17 15:53:12 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Adds the routees to existing routees.
|
|
|
|
|
|
* Removes death watch of the routees. Doesn't stop the routees.
|
|
|
|
|
|
* Not thread safe, but intended to be called from protected points, such as
|
|
|
|
|
|
* `Resizer.resize`
|
|
|
|
|
|
*/
|
2012-09-07 17:19:51 +02:00
|
|
|
|
private[akka] def removeRoutees(abandonedRoutees: Iterable[ActorRef]): Unit = {
|
2012-09-11 19:11:20 +02:00
|
|
|
|
_routees = abandonedRoutees.foldLeft(_routees) { (xs, x) ⇒ unwatch(x); xs.filterNot(_ == x) }
|
2012-01-09 20:25:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
override def tell(message: Any, sender: ActorRef): Unit = {
|
2012-01-11 11:13:03 +01:00
|
|
|
|
resize()
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
val s = if (sender eq null) system.deadLetters else sender
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
|
|
|
|
|
val msg = message match {
|
|
|
|
|
|
case Broadcast(m) ⇒ m
|
|
|
|
|
|
case m ⇒ m
|
2011-12-08 14:30:57 +01:00
|
|
|
|
}
|
2011-12-11 22:34:38 +01:00
|
|
|
|
|
2011-12-13 16:05:56 +01:00
|
|
|
|
applyRoute(s, message) match {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
case Destination(_, x) :: Nil if x == self ⇒ super.tell(message, s)
|
|
|
|
|
|
case refs ⇒
|
|
|
|
|
|
refs foreach (p ⇒
|
|
|
|
|
|
if (p.recipient == self) super.tell(msg, p.sender)
|
|
|
|
|
|
else p.recipient.!(msg)(p.sender))
|
2011-12-12 15:06:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
2012-02-09 22:58:28 +01:00
|
|
|
|
def resize(): Unit = {
|
2012-02-09 16:59:16 +01:00
|
|
|
|
for (r ← routerConfig.resizer) {
|
2012-02-09 22:58:28 +01:00
|
|
|
|
if (r.isTimeForResize(resizeCounter.getAndIncrement()) && resizeInProgress.compareAndSet(false, true))
|
2012-06-13 17:57:56 +02:00
|
|
|
|
super.tell(Router.Resize, self)
|
2012-01-11 11:13:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-11-11 20:05:53 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-08-12 10:03:33 +03:00
|
|
|
|
/**
|
2011-12-12 23:31:15 +01:00
|
|
|
|
* This trait represents a router factory: it produces the actual router actor
|
|
|
|
|
|
* and creates the routing table (a function which determines the recipients
|
|
|
|
|
|
* for each message which is to be dispatched). The resulting RoutedActorRef
|
|
|
|
|
|
* optimizes the sending of the message so that it does NOT go through the
|
|
|
|
|
|
* router’s mailbox unless the route returns an empty recipient set.
|
|
|
|
|
|
*
|
|
|
|
|
|
* '''Caution:''' This means
|
|
|
|
|
|
* that the route function is evaluated concurrently without protection by
|
|
|
|
|
|
* the RoutedActorRef: either provide a reentrant (i.e. pure) implementation or
|
|
|
|
|
|
* do the locking yourself!
|
2011-12-08 14:30:57 +01:00
|
|
|
|
*
|
2011-12-12 23:31:15 +01:00
|
|
|
|
* '''Caution:''' Please note that the [[akka.routing.Router]] which needs to
|
2012-01-11 11:30:32 +01:00
|
|
|
|
* be returned by `createActor()` should not send a message to itself in its
|
2011-12-12 23:31:15 +01:00
|
|
|
|
* constructor or `preStart()` or publish its self reference from there: if
|
|
|
|
|
|
* someone tries sending a message to that reference before the constructor of
|
|
|
|
|
|
* RoutedActorRef has returned, there will be a `NullPointerException`!
|
2011-08-27 08:10:25 +03:00
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
trait RouterConfig {
|
|
|
|
|
|
|
2012-09-11 19:11:20 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Implement the routing logic by returning a partial function of
|
|
|
|
|
|
* partial function from (sender, message) to a set of destinations.
|
|
|
|
|
|
* This `Route` will be applied for each incoming message.
|
|
|
|
|
|
*
|
|
|
|
|
|
* When `createRoute` is called the routees should also be registered,
|
|
|
|
|
|
* typically by using `createRoutees` or `registerRouteesFor` of the
|
|
|
|
|
|
* supplied `RouteeProvider`.
|
|
|
|
|
|
*/
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route
|
2012-01-17 08:45:07 +01:00
|
|
|
|
|
2012-09-11 19:11:20 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* The `RouteeProvider` responsible for creating or
|
|
|
|
|
|
* looking up routees. It's used in `createRoute` to register routees,
|
|
|
|
|
|
* and also from [[akka.routing.Resizer]].
|
|
|
|
|
|
*/
|
2012-09-07 10:02:05 +02:00
|
|
|
|
def createRouteeProvider(context: ActorContext, routeeProps: Props): RouteeProvider =
|
2012-08-29 19:33:19 +02:00
|
|
|
|
new RouteeProvider(context, routeeProps, resizer)
|
2011-12-13 01:09:05 +01:00
|
|
|
|
|
2012-09-11 19:11:20 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* The router "head" actor.
|
|
|
|
|
|
*/
|
2012-02-18 22:15:39 +01:00
|
|
|
|
def createActor(): Router = new Router {
|
|
|
|
|
|
override def supervisorStrategy: SupervisorStrategy = RouterConfig.this.supervisorStrategy
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* SupervisorStrategy for the created Router actor.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def supervisorStrategy: SupervisorStrategy
|
2011-12-12 23:31:15 +01:00
|
|
|
|
|
2012-02-10 14:13:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Dispatcher ID to use for running the “head” actor, i.e. the [[akka.routing.Router]].
|
|
|
|
|
|
*/
|
|
|
|
|
|
def routerDispatcher: String
|
|
|
|
|
|
|
2012-01-31 21:19:28 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Overridable merge strategy, by default completely prefers “this” (i.e. no merge).
|
|
|
|
|
|
*/
|
|
|
|
|
|
def withFallback(other: RouterConfig): RouterConfig = this
|
2011-12-12 23:31:15 +01:00
|
|
|
|
|
2012-05-24 12:12:45 +02:00
|
|
|
|
protected def toAll(sender: ActorRef, routees: Iterable[ActorRef]): Iterable[Destination] =
|
|
|
|
|
|
routees.map(Destination(sender, _))
|
2011-12-13 12:39:02 +01:00
|
|
|
|
|
2012-01-17 08:45:07 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Routers with dynamically resizable number of routees return the [[akka.routing.Resizer]]
|
|
|
|
|
|
* to use.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def resizer: Option[Resizer] = None
|
2011-12-13 16:05:56 +01:00
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Check that everything is there which is needed. Called in constructor of RoutedActorRef to fail early.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def verifyConfig(): Unit = {}
|
|
|
|
|
|
|
2012-01-17 08:45:07 +01:00
|
|
|
|
}
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
2012-01-17 08:45:07 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Factory and registry for routees of the router.
|
|
|
|
|
|
* Uses `context.actorOf` to create routees from nrOfInstances property
|
|
|
|
|
|
* and `context.actorFor` lookup routees from paths.
|
|
|
|
|
|
*/
|
2012-09-07 10:02:05 +02:00
|
|
|
|
class RouteeProvider(val context: ActorContext, val routeeProps: Props, val resizer: Option[Resizer]) {
|
2012-01-17 15:53:12 +01:00
|
|
|
|
|
2012-09-07 17:19:51 +02:00
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
|
|
2012-01-09 20:25:24 +01:00
|
|
|
|
/**
|
2012-01-17 15:53:12 +01:00
|
|
|
|
* Adds the routees to the router.
|
|
|
|
|
|
* Adds death watch of the routees so that they are removed when terminated.
|
|
|
|
|
|
* Not thread safe, but intended to be called from protected points, such as
|
|
|
|
|
|
* `RouterConfig.createRoute` and `Resizer.resize`.
|
2012-01-09 20:25:24 +01:00
|
|
|
|
*/
|
2012-08-28 20:54:16 +02:00
|
|
|
|
def registerRoutees(routees: Iterable[ActorRef]): Unit = routedCell.addRoutees(routees)
|
2011-12-13 16:05:56 +01:00
|
|
|
|
|
2012-01-17 08:45:07 +01:00
|
|
|
|
/**
|
2012-01-17 15:53:12 +01:00
|
|
|
|
* Adds the routees to the router.
|
|
|
|
|
|
* Adds death watch of the routees so that they are removed when terminated.
|
|
|
|
|
|
* Not thread safe, but intended to be called from protected points, such as
|
|
|
|
|
|
* `RouterConfig.createRoute` and `Resizer.resize`.
|
2012-01-17 08:45:07 +01:00
|
|
|
|
* Java API.
|
|
|
|
|
|
*/
|
2012-09-07 17:19:51 +02:00
|
|
|
|
def registerRoutees(routees: java.lang.Iterable[ActorRef]): Unit = registerRoutees(routees.asScala)
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Removes routees from the router. This method doesn't stop the routees.
|
|
|
|
|
|
* Removes death watch of the routees.
|
|
|
|
|
|
* Not thread safe, but intended to be called from protected points, such as
|
|
|
|
|
|
* `Resizer.resize`.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def unregisterRoutees(routees: Iterable[ActorRef]): Unit = routedCell.removeRoutees(routees)
|
2011-12-13 16:05:56 +01:00
|
|
|
|
|
2012-01-09 20:25:24 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Removes routees from the router. This method doesn't stop the routees.
|
2012-01-17 15:53:12 +01:00
|
|
|
|
* Removes death watch of the routees.
|
|
|
|
|
|
* Not thread safe, but intended to be called from protected points, such as
|
|
|
|
|
|
* `Resizer.resize`.
|
2012-09-07 17:19:51 +02:00
|
|
|
|
* JAVA API
|
2012-01-09 20:25:24 +01:00
|
|
|
|
*/
|
2012-09-07 17:19:51 +02:00
|
|
|
|
def unregisterRoutees(routees: java.lang.Iterable[ActorRef]): Unit = unregisterRoutees(routees.asScala)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
2012-08-28 20:54:16 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Looks up routes with specified paths and registers them.
|
|
|
|
|
|
*/
|
2012-09-11 19:11:20 +02:00
|
|
|
|
def registerRouteesFor(paths: Iterable[String]): Unit = registerRoutees(paths.map(context.actorFor(_)))
|
2012-01-17 08:45:07 +01:00
|
|
|
|
|
2012-09-07 17:19:51 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Looks up routes with specified paths and registers them.
|
|
|
|
|
|
* JAVA API
|
|
|
|
|
|
*/
|
2012-09-08 16:42:47 +02:00
|
|
|
|
def registerRouteesFor(paths: java.lang.Iterable[String]): Unit = registerRouteesFor(paths.asScala)
|
2012-09-07 17:19:51 +02:00
|
|
|
|
|
2012-08-28 20:54:16 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates new routees from specified `Props` and registers them.
|
|
|
|
|
|
*/
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoutees(nrOfInstances: Int): Unit = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
if (nrOfInstances <= 0) throw new IllegalArgumentException(
|
|
|
|
|
|
"Must specify nrOfInstances or routees for [%s]" format context.self.path.toString)
|
2012-09-11 19:11:20 +02:00
|
|
|
|
else
|
|
|
|
|
|
registerRoutees(IndexedSeq.fill(nrOfInstances)(context.actorOf(routeeProps)))
|
2012-08-28 20:54:16 +02:00
|
|
|
|
}
|
2012-01-17 08:45:07 +01:00
|
|
|
|
|
2012-08-28 15:36:40 +02:00
|
|
|
|
/**
|
2012-08-28 20:54:16 +02:00
|
|
|
|
* Remove specified number of routees by unregister them
|
|
|
|
|
|
* and sending [[akka.actor.PoisonPill]] after the specified delay.
|
2012-08-28 15:36:40 +02:00
|
|
|
|
* The reason for the delay is to give concurrent messages a chance to be
|
|
|
|
|
|
* placed in mailbox before sending PoisonPill.
|
|
|
|
|
|
*/
|
2012-08-28 20:54:16 +02:00
|
|
|
|
def removeRoutees(nrOfInstances: Int, stopDelay: Duration): Unit = {
|
|
|
|
|
|
if (nrOfInstances <= 0) {
|
|
|
|
|
|
throw new IllegalArgumentException("Expected positive nrOfInstances, got [%s]".format(nrOfInstances))
|
|
|
|
|
|
} else if (nrOfInstances > 0) {
|
2012-08-28 15:36:40 +02:00
|
|
|
|
val currentRoutees = routees
|
2012-09-11 19:11:20 +02:00
|
|
|
|
val abandon = currentRoutees.drop(currentRoutees.length - nrOfInstances)
|
2012-08-28 15:36:40 +02:00
|
|
|
|
unregisterRoutees(abandon)
|
|
|
|
|
|
delayedStop(context.system.scheduler, abandon, stopDelay)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Give concurrent messages a chance to be placed in mailbox before
|
|
|
|
|
|
* sending PoisonPill.
|
|
|
|
|
|
*/
|
2012-09-07 17:19:51 +02:00
|
|
|
|
protected def delayedStop(scheduler: Scheduler, abandon: Iterable[ActorRef], stopDelay: Duration): Unit = {
|
2012-08-28 15:36:40 +02:00
|
|
|
|
if (abandon.nonEmpty) {
|
|
|
|
|
|
if (stopDelay <= Duration.Zero) {
|
|
|
|
|
|
abandon foreach (_ ! PoisonPill)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
import context.dispatcher
|
2012-09-11 19:11:20 +02:00
|
|
|
|
// Iterable could potentially be mutable
|
|
|
|
|
|
val localAbandon = abandon.toIndexedSeq
|
2012-08-28 15:36:40 +02:00
|
|
|
|
scheduler.scheduleOnce(stopDelay) {
|
2012-09-11 19:11:20 +02:00
|
|
|
|
localAbandon foreach (_ ! PoisonPill)
|
2012-08-28 15:36:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-01-11 11:13:03 +01:00
|
|
|
|
/**
|
2012-01-17 08:45:07 +01:00
|
|
|
|
* All routees of the router
|
2012-01-11 11:13:03 +01:00
|
|
|
|
*/
|
2012-06-13 17:57:56 +02:00
|
|
|
|
def routees: IndexedSeq[ActorRef] = routedCell.routees
|
2012-01-17 15:53:12 +01:00
|
|
|
|
|
2012-09-07 17:19:51 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* All routees of the router
|
|
|
|
|
|
* JAVA API
|
|
|
|
|
|
*/
|
|
|
|
|
|
def getRoutees(): java.util.List[ActorRef] = routees.asJava
|
|
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
private def routedCell = context.asInstanceOf[RoutedActorCell]
|
2012-01-05 17:54:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for a custom router factory.
|
|
|
|
|
|
* @see akka.routing.RouterConfig
|
|
|
|
|
|
*/
|
|
|
|
|
|
abstract class CustomRouterConfig extends RouterConfig {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
override def createRoute(routeeProvider: RouteeProvider): Route = {
|
2012-01-11 11:42:03 +01:00
|
|
|
|
// as a bonus, this prevents closing of props and context in the returned Route PartialFunction
|
2012-08-29 19:33:19 +02:00
|
|
|
|
val customRoute = createCustomRoute(routeeProvider)
|
2012-01-05 17:54:33 +01:00
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
case (sender, message) ⇒ customRoute.destinationsFor(sender, message)
|
|
|
|
|
|
}
|
2011-12-13 16:05:56 +01:00
|
|
|
|
}
|
2012-01-05 17:54:33 +01:00
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createCustomRoute(routeeProvider: RouteeProvider): CustomRoute
|
2012-01-05 17:54:33 +01:00
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
trait CustomRoute {
|
|
|
|
|
|
def destinationsFor(sender: ActorRef, message: Any): java.lang.Iterable[Destination]
|
2011-12-08 14:30:57 +01:00
|
|
|
|
}
|
2011-08-27 08:10:25 +03:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-12-12 23:31:15 +01:00
|
|
|
|
* Base trait for `Router` actors. Override `receive` to handle custom
|
2012-07-25 13:26:33 +02:00
|
|
|
|
* messages which the corresponding [[akka.routing.RouterConfig]] lets
|
2011-12-12 23:31:15 +01:00
|
|
|
|
* through by returning an empty route.
|
2011-08-27 08:10:25 +03:00
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
trait Router extends Actor {
|
2011-12-13 16:05:56 +01:00
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
val ref = context match {
|
|
|
|
|
|
case x: RoutedActorCell ⇒ x
|
2012-08-02 14:30:38 +02:00
|
|
|
|
case _ ⇒ throw ActorInitializationException("Router actor can only be used in RoutedActorRef, not in " + context.getClass)
|
2012-02-09 22:58:28 +01:00
|
|
|
|
}
|
2012-02-09 16:59:16 +01:00
|
|
|
|
|
2011-12-13 16:05:56 +01:00
|
|
|
|
final def receive = ({
|
|
|
|
|
|
|
2012-02-09 16:59:16 +01:00
|
|
|
|
case Router.Resize ⇒
|
2012-05-18 19:25:43 +02:00
|
|
|
|
val ab = ref.resizeInProgress
|
2012-08-29 19:33:19 +02:00
|
|
|
|
if (ab.get) try ref.routerConfig.resizer foreach (_.resize(ref.routeeProvider)) finally ab.set(false)
|
2012-02-09 16:59:16 +01:00
|
|
|
|
|
2011-12-13 12:39:02 +01:00
|
|
|
|
case Terminated(child) ⇒
|
2012-01-09 20:25:24 +01:00
|
|
|
|
ref.removeRoutees(IndexedSeq(child))
|
2011-12-14 00:06:36 +01:00
|
|
|
|
if (ref.routees.isEmpty) context.stop(self)
|
2011-12-13 16:05:56 +01:00
|
|
|
|
|
|
|
|
|
|
}: Receive) orElse routerReceive
|
|
|
|
|
|
|
2012-03-14 10:42:18 +01:00
|
|
|
|
def routerReceive: Receive = Actor.emptyBehavior
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
override def preRestart(cause: Throwable, msg: Option[Any]): Unit = {
|
|
|
|
|
|
// do not scrap children
|
|
|
|
|
|
}
|
2011-07-28 15:48:03 +03:00
|
|
|
|
}
|
2010-03-04 19:02:23 +01:00
|
|
|
|
|
2012-05-18 19:25:43 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
|
*/
|
2012-02-09 22:58:28 +01:00
|
|
|
|
private object Router {
|
2012-07-31 12:54:31 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-02-09 16:59:16 +01:00
|
|
|
|
case object Resize
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
val defaultSupervisorStrategy: SupervisorStrategy = OneForOneStrategy() {
|
|
|
|
|
|
case _ ⇒ SupervisorStrategy.Escalate
|
|
|
|
|
|
}
|
2012-02-09 16:59:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Used to broadcast a message to all connections in a router; only the
|
|
|
|
|
|
* contained message will be forwarded, i.e. the `Broadcast(...)`
|
|
|
|
|
|
* envelope will be stripped off.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Router implementations may choose to handle this message differently.
|
|
|
|
|
|
*/
|
2012-07-31 12:54:31 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2011-12-12 23:31:15 +01:00
|
|
|
|
case class Broadcast(message: Any)
|
|
|
|
|
|
|
2011-12-19 13:47:49 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Sending this message to a router will make it send back its currently used routees.
|
2011-12-19 16:35:35 +01:00
|
|
|
|
* A RouterRoutees message is sent asynchronously to the "requester" containing information
|
|
|
|
|
|
* about what routees the router is routing over.
|
2011-12-19 13:47:49 +01:00
|
|
|
|
*/
|
2012-04-10 12:07:14 +02:00
|
|
|
|
abstract class CurrentRoutees
|
2012-07-31 12:54:31 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-04-10 12:07:14 +02:00
|
|
|
|
case object CurrentRoutees extends CurrentRoutees {
|
2012-04-10 16:49:29 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Java API: get the singleton instance
|
|
|
|
|
|
*/
|
|
|
|
|
|
def getInstance = this
|
2012-04-10 12:07:14 +02:00
|
|
|
|
}
|
2011-12-19 13:47:49 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Message used to carry information about what routees the router is currently using.
|
|
|
|
|
|
*/
|
2012-07-31 12:54:31 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2011-12-19 16:35:35 +01:00
|
|
|
|
case class RouterRoutees(routees: Iterable[ActorRef])
|
2011-12-19 13:47:49 +01:00
|
|
|
|
|
2011-12-13 17:53:59 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* For every message sent to a router, its route determines a set of destinations,
|
|
|
|
|
|
* where for each recipient a different sender may be specified; typically the
|
|
|
|
|
|
* sender should match the sender of the original request, but e.g. the scatter-
|
|
|
|
|
|
* gather router needs to receive the replies with an AskActorRef instead.
|
|
|
|
|
|
*/
|
2012-07-31 12:54:31 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2011-12-13 17:53:59 +01:00
|
|
|
|
case class Destination(sender: ActorRef, recipient: ActorRef)
|
|
|
|
|
|
|
2011-12-11 22:34:38 +01:00
|
|
|
|
/**
|
2012-01-31 21:19:28 +01:00
|
|
|
|
* Routing configuration that indicates no routing; this is also the default
|
|
|
|
|
|
* value which hence overrides the merge strategy in order to accept values
|
2012-02-14 19:50:01 +07:00
|
|
|
|
* from lower-precedence sources. The decision whether or not to create a
|
2012-01-31 21:19:28 +01:00
|
|
|
|
* router is taken in the LocalActorRefProvider based on Props.
|
2011-12-11 22:34:38 +01:00
|
|
|
|
*/
|
2012-07-30 13:26:12 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-04-10 12:07:14 +02:00
|
|
|
|
abstract class NoRouter extends RouterConfig
|
|
|
|
|
|
case object NoRouter extends NoRouter {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route = null // FIXME, null, really??
|
2012-02-10 14:13:40 +01:00
|
|
|
|
def routerDispatcher: String = ""
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def supervisorStrategy = null // FIXME null, really??
|
2012-01-31 21:19:28 +01:00
|
|
|
|
override def withFallback(other: RouterConfig): RouterConfig = other
|
2012-04-10 12:07:14 +02:00
|
|
|
|
|
2012-04-10 16:49:29 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Java API: get the singleton instance
|
|
|
|
|
|
*/
|
|
|
|
|
|
def getInstance = this
|
2011-12-11 22:34:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-29 17:11:21 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Router configuration which has no default, i.e. external configuration is required.
|
|
|
|
|
|
*/
|
2012-04-10 12:07:14 +02:00
|
|
|
|
case object FromConfig extends FromConfig {
|
2012-04-10 16:49:29 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Java API: get the singleton instance
|
|
|
|
|
|
*/
|
|
|
|
|
|
def getInstance = this
|
|
|
|
|
|
@inline final def apply(routerDispatcher: String = Dispatchers.DefaultDispatcherId) = new FromConfig(routerDispatcher)
|
|
|
|
|
|
@inline final def unapply(fc: FromConfig): Option[String] = Some(fc.routerDispatcher)
|
2011-12-29 17:11:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-30 00:12:49 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Java API: Router configuration which has no default, i.e. external configuration is required.
|
2012-02-10 14:13:40 +01:00
|
|
|
|
*
|
|
|
|
|
|
* This can be used when the dispatcher to be used for the head Router needs to be configured
|
|
|
|
|
|
* (defaults to default-dispatcher).
|
2011-12-30 00:12:49 +01:00
|
|
|
|
*/
|
2012-07-30 13:26:12 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-04-10 12:07:14 +02:00
|
|
|
|
class FromConfig(val routerDispatcher: String = Dispatchers.DefaultDispatcherId)
|
|
|
|
|
|
extends RouterConfig
|
2012-05-18 19:25:43 +02:00
|
|
|
|
with Serializable {
|
2012-02-10 14:55:40 +01:00
|
|
|
|
|
|
|
|
|
|
def this() = this(Dispatchers.DefaultDispatcherId)
|
|
|
|
|
|
|
2012-06-13 17:57:56 +02:00
|
|
|
|
override def verifyConfig(): Unit =
|
|
|
|
|
|
throw new ConfigurationException("router needs external configuration from file (e.g. application.conf)")
|
|
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route = null
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
def supervisorStrategy: SupervisorStrategy = Router.defaultSupervisorStrategy
|
2011-12-30 00:12:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
object RoundRobinRouter {
|
2012-05-24 12:12:45 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new RoundRobinRouter, routing to the specified routees
|
|
|
|
|
|
*/
|
|
|
|
|
|
def apply(routees: Iterable[ActorRef]): RoundRobinRouter =
|
|
|
|
|
|
new RoundRobinRouter(routees = routees map (_.path.toString))
|
2012-01-05 17:54:33 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API to create router with the supplied 'routees' actors.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def create(routees: java.lang.Iterable[ActorRef]): RoundRobinRouter = {
|
|
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
|
apply(routees.asScala)
|
|
|
|
|
|
}
|
2011-12-12 23:31:15 +01:00
|
|
|
|
}
|
2011-07-28 15:48:03 +03:00
|
|
|
|
/**
|
2011-12-08 14:30:57 +01:00
|
|
|
|
* A Router that uses round-robin to select a connection. For concurrent calls, round robin is just a best effort.
|
|
|
|
|
|
* <br>
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'routees' does not make logical sense as this means
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* that the router should both create new actors and use the 'routees' actor(s).
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'routees' will be used.
|
2011-12-08 14:30:57 +01:00
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* if you provide either 'nrOfInstances' or 'routees' during instantiation they will
|
|
|
|
|
|
* be ignored if the router is defined in the configuration file for the actor being used.
|
|
|
|
|
|
*
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* <h1>Supervision Setup</h1>
|
|
|
|
|
|
*
|
2012-02-18 22:32:41 +01:00
|
|
|
|
* The router creates a “head” actor which supervises and/or monitors the
|
|
|
|
|
|
* routees. Instances are created as children of this actor, hence the
|
|
|
|
|
|
* children are not supervised by the parent of the router. Common choices are
|
|
|
|
|
|
* to always escalate (meaning that fault handling is always applied to all
|
|
|
|
|
|
* children simultaneously; this is the default) or use the parent’s strategy,
|
|
|
|
|
|
* which will result in routed children being treated individually, but it is
|
|
|
|
|
|
* possible as well to use Routers to give different supervisor strategies to
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* different groups of children.
|
|
|
|
|
|
*
|
|
|
|
|
|
* {{{
|
|
|
|
|
|
* class MyActor extends Actor {
|
|
|
|
|
|
* override val supervisorStrategy = ...
|
|
|
|
|
|
*
|
|
|
|
|
|
* val poolAsAWhole = context.actorOf(Props[SomeActor].withRouter(RoundRobinRouter(5)))
|
|
|
|
|
|
*
|
|
|
|
|
|
* val poolIndividuals = context.actorOf(Props[SomeActor].withRouter(
|
|
|
|
|
|
* RoundRobinRouter(5, supervisorStrategy = this.supervisorStrategy)))
|
|
|
|
|
|
*
|
|
|
|
|
|
* val specialChild = context.actorOf(Props[SomeActor].withRouter(
|
|
|
|
|
|
* RoundRobinRouter(5, supervisorStrategy = OneForOneStrategy() {
|
|
|
|
|
|
* ...
|
|
|
|
|
|
* })))
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }}}
|
|
|
|
|
|
*
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routees string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-07-28 15:48:03 +03:00
|
|
|
|
*/
|
2012-07-30 13:26:12 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-02-10 14:13:40 +01:00
|
|
|
|
case class RoundRobinRouter(nrOfInstances: Int = 0, routees: Iterable[String] = Nil, override val resizer: Option[Resizer] = None,
|
2012-02-18 22:15:39 +01:00
|
|
|
|
val routerDispatcher: String = Dispatchers.DefaultDispatcherId,
|
|
|
|
|
|
val supervisorStrategy: SupervisorStrategy = Router.defaultSupervisorStrategy)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
extends RouterConfig with RoundRobinLike {
|
2011-07-28 15:48:03 +03:00
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets nrOfInstances to be created.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(nr: Int) = this(nrOfInstances = nr)
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Constructor that sets the routees to be used.
|
2011-12-12 15:06:40 +01:00
|
|
|
|
* Java API
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routeePaths string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-12-12 15:06:40 +01:00
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(routeePaths: java.lang.Iterable[String]) = this(routees = iterableAsScalaIterable(routeePaths))
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* Constructor that sets the resizer to be used.
|
2012-01-09 20:25:24 +01:00
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
def this(resizer: Resizer) = this(resizer = Some(resizer))
|
2012-02-10 14:13:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting routerDispatcher
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withDispatcher(dispatcherId: String): RoundRobinRouter = copy(routerDispatcher = dispatcherId)
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting the supervisor strategy to be used for the “head”
|
|
|
|
|
|
* Router actor.
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withSupervisorStrategy(strategy: SupervisorStrategy): RoundRobinRouter = copy(supervisorStrategy = strategy)
|
2012-08-27 14:47:32 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-08-31 11:16:57 +02:00
|
|
|
|
* Uses the resizer of the given Routerconfig if this RouterConfig
|
|
|
|
|
|
* doesn't have one, i.e. the resizer defined in code is used if
|
|
|
|
|
|
* resizer was not defined in config.
|
2012-08-27 14:47:32 +02:00
|
|
|
|
*/
|
|
|
|
|
|
override def withFallback(other: RouterConfig): RouterConfig = {
|
|
|
|
|
|
if (this.resizer.isEmpty && other.resizer.isDefined) copy(resizer = other.resizer)
|
|
|
|
|
|
else this
|
|
|
|
|
|
}
|
2011-12-13 01:09:05 +01:00
|
|
|
|
}
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
2011-12-13 01:09:05 +01:00
|
|
|
|
trait RoundRobinLike { this: RouterConfig ⇒
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def nrOfInstances: Int
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def routees: Iterable[String]
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
if (resizer.isEmpty) {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
if (routees.isEmpty) routeeProvider.createRoutees(nrOfInstances)
|
2012-08-28 20:54:16 +02:00
|
|
|
|
else routeeProvider.registerRouteesFor(routees)
|
|
|
|
|
|
}
|
2011-07-28 15:48:03 +03:00
|
|
|
|
|
2012-01-09 20:50:18 +01:00
|
|
|
|
val next = new AtomicLong(0)
|
2011-08-12 10:03:33 +03:00
|
|
|
|
|
2011-12-08 14:30:57 +01:00
|
|
|
|
def getNext(): ActorRef = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
val currentRoutees = routeeProvider.routees
|
|
|
|
|
|
if (currentRoutees.isEmpty) routeeProvider.context.system.deadLetters
|
|
|
|
|
|
else currentRoutees((next.getAndIncrement % currentRoutees.size).asInstanceOf[Int])
|
2011-12-08 14:30:57 +01:00
|
|
|
|
}
|
2011-08-18 11:35:14 +02:00
|
|
|
|
|
2011-12-13 12:39:02 +01:00
|
|
|
|
{
|
|
|
|
|
|
case (sender, message) ⇒
|
|
|
|
|
|
message match {
|
2012-01-17 08:45:07 +01:00
|
|
|
|
case Broadcast(msg) ⇒ toAll(sender, routeeProvider.routees)
|
2011-12-13 12:39:02 +01:00
|
|
|
|
case msg ⇒ List(Destination(sender, getNext()))
|
|
|
|
|
|
}
|
2011-07-28 15:48:03 +03:00
|
|
|
|
}
|
2011-08-12 10:03:33 +03:00
|
|
|
|
}
|
2011-12-11 22:34:38 +01:00
|
|
|
|
}
|
2011-08-12 10:03:33 +03:00
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
object RandomRouter {
|
2012-05-24 12:12:45 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new RandomRouter, routing to the specified routees
|
|
|
|
|
|
*/
|
|
|
|
|
|
def apply(routees: Iterable[ActorRef]): RandomRouter = new RandomRouter(routees = routees map (_.path.toString))
|
2012-01-05 17:54:33 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API to create router with the supplied 'routees' actors.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def create(routees: java.lang.Iterable[ActorRef]): RandomRouter = {
|
|
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
|
apply(routees.asScala)
|
|
|
|
|
|
}
|
2011-12-12 23:31:15 +01:00
|
|
|
|
}
|
2011-12-11 22:34:38 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* A Router that randomly selects one of the target connections to send a message to.
|
|
|
|
|
|
* <br>
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'routees' does not make logical sense as this means
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* that the router should both create new actors and use the 'routees' actor(s).
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'routees' will be used.
|
2011-12-11 22:34:38 +01:00
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* if you provide either 'nrOfInstances' or 'routees' during instantiation they will
|
|
|
|
|
|
* be ignored if the router is defined in the configuration file for the actor being used.
|
|
|
|
|
|
*
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* <h1>Supervision Setup</h1>
|
|
|
|
|
|
*
|
2012-02-18 22:32:41 +01:00
|
|
|
|
* The router creates a “head” actor which supervises and/or monitors the
|
|
|
|
|
|
* routees. Instances are created as children of this actor, hence the
|
|
|
|
|
|
* children are not supervised by the parent of the router. Common choices are
|
|
|
|
|
|
* to always escalate (meaning that fault handling is always applied to all
|
|
|
|
|
|
* children simultaneously; this is the default) or use the parent’s strategy,
|
|
|
|
|
|
* which will result in routed children being treated individually, but it is
|
|
|
|
|
|
* possible as well to use Routers to give different supervisor strategies to
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* different groups of children.
|
|
|
|
|
|
*
|
|
|
|
|
|
* {{{
|
|
|
|
|
|
* class MyActor extends Actor {
|
|
|
|
|
|
* override val supervisorStrategy = ...
|
|
|
|
|
|
*
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* val poolAsAWhole = context.actorOf(Props[SomeActor].withRouter(RandomRouter(5)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val poolIndividuals = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* RandomRouter(5, supervisorStrategy = this.supervisorStrategy)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val specialChild = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* RandomRouter(5, supervisorStrategy = OneForOneStrategy() {
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* ...
|
|
|
|
|
|
* })))
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }}}
|
|
|
|
|
|
*
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routees string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-12-11 22:34:38 +01:00
|
|
|
|
*/
|
2012-07-30 13:26:12 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-02-10 14:13:40 +01:00
|
|
|
|
case class RandomRouter(nrOfInstances: Int = 0, routees: Iterable[String] = Nil, override val resizer: Option[Resizer] = None,
|
2012-02-18 22:15:39 +01:00
|
|
|
|
val routerDispatcher: String = Dispatchers.DefaultDispatcherId,
|
|
|
|
|
|
val supervisorStrategy: SupervisorStrategy = Router.defaultSupervisorStrategy)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
extends RouterConfig with RandomLike {
|
2011-05-17 21:15:27 +02:00
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets nrOfInstances to be created.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(nr: Int) = this(nrOfInstances = nr)
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Constructor that sets the routees to be used.
|
2011-12-12 15:06:40 +01:00
|
|
|
|
* Java API
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routeePaths string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-12-12 15:06:40 +01:00
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(routeePaths: java.lang.Iterable[String]) = this(routees = iterableAsScalaIterable(routeePaths))
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* Constructor that sets the resizer to be used.
|
2012-01-09 20:25:24 +01:00
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
def this(resizer: Resizer) = this(resizer = Some(resizer))
|
2012-02-10 14:13:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting routerDispatcher
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withDispatcher(dispatcherId: String): RandomRouter = copy(routerDispatcher = dispatcherId)
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting the supervisor strategy to be used for the “head”
|
|
|
|
|
|
* Router actor.
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withSupervisorStrategy(strategy: SupervisorStrategy): RandomRouter = copy(supervisorStrategy = strategy)
|
2012-08-27 14:47:32 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-08-31 11:16:57 +02:00
|
|
|
|
* Uses the resizer of the given Routerconfig if this RouterConfig
|
|
|
|
|
|
* doesn't have one, i.e. the resizer defined in code is used if
|
|
|
|
|
|
* resizer was not defined in config.
|
2012-08-27 14:47:32 +02:00
|
|
|
|
*/
|
|
|
|
|
|
override def withFallback(other: RouterConfig): RouterConfig = {
|
|
|
|
|
|
if (this.resizer.isEmpty && other.resizer.isDefined) copy(resizer = other.resizer)
|
|
|
|
|
|
else this
|
|
|
|
|
|
}
|
2011-12-13 01:09:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
trait RandomLike { this: RouterConfig ⇒
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def nrOfInstances: Int
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def routees: Iterable[String]
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
if (resizer.isEmpty) {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
if (routees.isEmpty) routeeProvider.createRoutees(nrOfInstances)
|
2012-08-28 20:54:16 +02:00
|
|
|
|
else routeeProvider.registerRouteesFor(routees)
|
|
|
|
|
|
}
|
2011-05-21 15:37:09 +02:00
|
|
|
|
|
2011-12-11 22:34:38 +01:00
|
|
|
|
def getNext(): ActorRef = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
val currentRoutees = routeeProvider.routees
|
|
|
|
|
|
if (currentRoutees.isEmpty) routeeProvider.context.system.deadLetters
|
|
|
|
|
|
else currentRoutees(ThreadLocalRandom.current.nextInt(currentRoutees.size))
|
2011-12-11 22:34:38 +01:00
|
|
|
|
}
|
2011-08-12 10:03:33 +03:00
|
|
|
|
|
2011-12-13 12:39:02 +01:00
|
|
|
|
{
|
|
|
|
|
|
case (sender, message) ⇒
|
|
|
|
|
|
message match {
|
2012-01-17 08:45:07 +01:00
|
|
|
|
case Broadcast(msg) ⇒ toAll(sender, routeeProvider.routees)
|
2011-12-13 12:39:02 +01:00
|
|
|
|
case msg ⇒ List(Destination(sender, getNext()))
|
|
|
|
|
|
}
|
2011-08-12 10:03:33 +03:00
|
|
|
|
}
|
2011-05-17 21:15:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-08-17 10:21:27 +03:00
|
|
|
|
|
2012-01-11 13:56:38 +01:00
|
|
|
|
object SmallestMailboxRouter {
|
2012-05-24 12:12:45 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new SmallestMailboxRouter, routing to the specified routees
|
|
|
|
|
|
*/
|
|
|
|
|
|
def apply(routees: Iterable[ActorRef]): SmallestMailboxRouter =
|
|
|
|
|
|
new SmallestMailboxRouter(routees = routees map (_.path.toString))
|
2012-01-11 13:56:38 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API to create router with the supplied 'routees' actors.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def create(routees: java.lang.Iterable[ActorRef]): SmallestMailboxRouter = {
|
|
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
|
apply(routees.asScala)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* A Router that tries to send to the non-suspended routee with fewest messages in mailbox.
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* The selection is done in this order:
|
|
|
|
|
|
* <ul>
|
|
|
|
|
|
* <li>pick any idle routee (not processing message) with empty mailbox</li>
|
|
|
|
|
|
* <li>pick any routee with empty mailbox</li>
|
|
|
|
|
|
* <li>pick routee with fewest pending messages in mailbox</li>
|
|
|
|
|
|
* <li>pick any remote routee, remote actors are consider lowest priority,
|
|
|
|
|
|
* since their mailbox size is unknown</li>
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
*
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'routees' does not make logical sense as this means
|
|
|
|
|
|
* that the router should both create new actors and use the 'routees' actor(s).
|
|
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'routees' will be used.
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* if you provide either 'nrOfInstances' or 'routees' during instantiation they will
|
|
|
|
|
|
* be ignored if the router is defined in the configuration file for the actor being used.
|
|
|
|
|
|
*
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* <h1>Supervision Setup</h1>
|
|
|
|
|
|
*
|
2012-02-18 22:32:41 +01:00
|
|
|
|
* The router creates a “head” actor which supervises and/or monitors the
|
|
|
|
|
|
* routees. Instances are created as children of this actor, hence the
|
|
|
|
|
|
* children are not supervised by the parent of the router. Common choices are
|
|
|
|
|
|
* to always escalate (meaning that fault handling is always applied to all
|
|
|
|
|
|
* children simultaneously; this is the default) or use the parent’s strategy,
|
|
|
|
|
|
* which will result in routed children being treated individually, but it is
|
|
|
|
|
|
* possible as well to use Routers to give different supervisor strategies to
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* different groups of children.
|
|
|
|
|
|
*
|
|
|
|
|
|
* {{{
|
|
|
|
|
|
* class MyActor extends Actor {
|
|
|
|
|
|
* override val supervisorStrategy = ...
|
|
|
|
|
|
*
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* val poolAsAWhole = context.actorOf(Props[SomeActor].withRouter(SmallestMailboxRouter(5)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val poolIndividuals = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* SmallestMailboxRouter(5, supervisorStrategy = this.supervisorStrategy)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val specialChild = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* SmallestMailboxRouter(5, supervisorStrategy = OneForOneStrategy() {
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* ...
|
|
|
|
|
|
* })))
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }}}
|
|
|
|
|
|
*
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routees string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2012-01-11 13:56:38 +01:00
|
|
|
|
*/
|
2012-07-30 13:26:12 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-02-10 14:13:40 +01:00
|
|
|
|
case class SmallestMailboxRouter(nrOfInstances: Int = 0, routees: Iterable[String] = Nil, override val resizer: Option[Resizer] = None,
|
2012-02-18 22:15:39 +01:00
|
|
|
|
val routerDispatcher: String = Dispatchers.DefaultDispatcherId,
|
|
|
|
|
|
val supervisorStrategy: SupervisorStrategy = Router.defaultSupervisorStrategy)
|
2012-01-11 13:56:38 +01:00
|
|
|
|
extends RouterConfig with SmallestMailboxLike {
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets nrOfInstances to be created.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(nr: Int) = this(nrOfInstances = nr)
|
2012-01-11 13:56:38 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets the routees to be used.
|
|
|
|
|
|
* Java API
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routeePaths string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2012-01-11 13:56:38 +01:00
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(routeePaths: java.lang.Iterable[String]) = this(routees = iterableAsScalaIterable(routeePaths))
|
2012-01-11 13:56:38 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets the resizer to be used.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
|
|
|
|
|
def this(resizer: Resizer) = this(resizer = Some(resizer))
|
2012-02-10 14:13:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting routerDispatcher
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withDispatcher(dispatcherId: String): SmallestMailboxRouter = copy(routerDispatcher = dispatcherId)
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting the supervisor strategy to be used for the “head”
|
|
|
|
|
|
* Router actor.
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withSupervisorStrategy(strategy: SupervisorStrategy): SmallestMailboxRouter = copy(supervisorStrategy = strategy)
|
2012-08-27 14:47:32 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-08-31 11:16:57 +02:00
|
|
|
|
* Uses the resizer of the given Routerconfig if this RouterConfig
|
|
|
|
|
|
* doesn't have one, i.e. the resizer defined in code is used if
|
|
|
|
|
|
* resizer was not defined in config.
|
2012-08-27 14:47:32 +02:00
|
|
|
|
*/
|
|
|
|
|
|
override def withFallback(other: RouterConfig): RouterConfig = {
|
|
|
|
|
|
if (this.resizer.isEmpty && other.resizer.isDefined) copy(resizer = other.resizer)
|
|
|
|
|
|
else this
|
|
|
|
|
|
}
|
2012-01-11 13:56:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
trait SmallestMailboxLike { this: RouterConfig ⇒
|
|
|
|
|
|
def nrOfInstances: Int
|
|
|
|
|
|
|
|
|
|
|
|
def routees: Iterable[String]
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns true if the actor is currently processing a message.
|
|
|
|
|
|
* It will always return false for remote actors.
|
|
|
|
|
|
* Method is exposed to subclasses to be able to implement custom
|
|
|
|
|
|
* routers based on mailbox and actor internal state.
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected def isProcessingMessage(a: ActorRef): Boolean = a match {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
case x: ActorRefWithCell ⇒
|
|
|
|
|
|
x.underlying match {
|
|
|
|
|
|
case cell: ActorCell ⇒ cell.mailbox.isScheduled && cell.currentMessage != null
|
|
|
|
|
|
case _ ⇒ false
|
|
|
|
|
|
}
|
2012-01-11 13:56:38 +01:00
|
|
|
|
case _ ⇒ false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns true if the actor currently has any pending messages
|
|
|
|
|
|
* in the mailbox, i.e. the mailbox is not empty.
|
|
|
|
|
|
* It will always return false for remote actors.
|
|
|
|
|
|
* Method is exposed to subclasses to be able to implement custom
|
|
|
|
|
|
* routers based on mailbox and actor internal state.
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected def hasMessages(a: ActorRef): Boolean = a match {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
case x: ActorRefWithCell ⇒ x.underlying.hasMessages
|
|
|
|
|
|
case _ ⇒ false
|
2012-01-11 13:56:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-01-12 09:53:53 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Returns true if the actor is currently suspended.
|
|
|
|
|
|
* It will always return false for remote actors.
|
|
|
|
|
|
* Method is exposed to subclasses to be able to implement custom
|
|
|
|
|
|
* routers based on mailbox and actor internal state.
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected def isSuspended(a: ActorRef): Boolean = a match {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
case x: ActorRefWithCell ⇒
|
|
|
|
|
|
x.underlying match {
|
|
|
|
|
|
case cell: ActorCell ⇒ cell.mailbox.isSuspended
|
|
|
|
|
|
case _ ⇒ true
|
|
|
|
|
|
}
|
|
|
|
|
|
case _ ⇒ false
|
2012-01-12 09:53:53 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-01-11 13:56:38 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Returns the number of pending messages in the mailbox of the actor.
|
|
|
|
|
|
* It will always return 0 for remote actors.
|
|
|
|
|
|
* Method is exposed to subclasses to be able to implement custom
|
|
|
|
|
|
* routers based on mailbox and actor internal state.
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected def numberOfMessages(a: ActorRef): Int = a match {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
case x: ActorRefWithCell ⇒ x.underlying.numberOfMessages
|
|
|
|
|
|
case _ ⇒ 0
|
2012-01-11 13:56:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
if (resizer.isEmpty) {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
if (routees.isEmpty) routeeProvider.createRoutees(nrOfInstances)
|
2012-08-28 20:54:16 +02:00
|
|
|
|
else routeeProvider.registerRouteesFor(routees)
|
|
|
|
|
|
}
|
2011-05-21 15:37:09 +02:00
|
|
|
|
|
2012-03-14 12:00:47 +01:00
|
|
|
|
// Worst-case a 2-pass inspection with mailbox size checking done on second pass, and only until no one empty is found.
|
|
|
|
|
|
// Lowest score wins, score 0 is autowin
|
|
|
|
|
|
// If no actor with score 0 is found, it will return that, or if it is terminated, a random of the entire set.
|
|
|
|
|
|
// Why? Well, in case we had 0 viable actors and all we got was the default, which is the DeadLetters, anything else is better.
|
2012-03-14 12:52:15 +01:00
|
|
|
|
// Order of interest, in ascending priority:
|
|
|
|
|
|
// 1. The DeadLetterActorRef
|
|
|
|
|
|
// 2. A Suspended ActorRef
|
|
|
|
|
|
// 3. An ActorRef with unknown mailbox size but with one message being processed
|
|
|
|
|
|
// 4. An ActorRef with unknown mailbox size that isn't processing anything
|
|
|
|
|
|
// 5. An ActorRef with a known mailbox size
|
|
|
|
|
|
// 6. An ActorRef without any messages
|
2012-03-14 12:00:47 +01:00
|
|
|
|
@tailrec def getNext(targets: IndexedSeq[ActorRef] = routeeProvider.routees,
|
|
|
|
|
|
proposedTarget: ActorRef = routeeProvider.context.system.deadLetters,
|
|
|
|
|
|
currentScore: Long = Long.MaxValue,
|
|
|
|
|
|
at: Int = 0,
|
|
|
|
|
|
deep: Boolean = false): ActorRef =
|
2012-08-31 12:57:33 +02:00
|
|
|
|
if (targets.isEmpty)
|
|
|
|
|
|
routeeProvider.context.system.deadLetters
|
|
|
|
|
|
else if (at >= targets.size) {
|
2012-03-14 12:00:47 +01:00
|
|
|
|
if (deep) {
|
2012-05-03 21:14:47 +02:00
|
|
|
|
if (proposedTarget.isTerminated) targets(ThreadLocalRandom.current.nextInt(targets.size)) else proposedTarget
|
2012-03-14 12:00:47 +01:00
|
|
|
|
} else getNext(targets, proposedTarget, currentScore, 0, deep = true)
|
|
|
|
|
|
} else {
|
2012-03-14 12:27:28 +01:00
|
|
|
|
val target = targets(at)
|
|
|
|
|
|
val newScore: Long =
|
2012-03-14 12:46:22 +01:00
|
|
|
|
if (isSuspended(target)) Long.MaxValue - 1 else { //Just about better than the DeadLetters
|
2012-03-14 12:27:28 +01:00
|
|
|
|
(if (isProcessingMessage(target)) 1l else 0l) +
|
|
|
|
|
|
(if (!hasMessages(target)) 0l else { //Race between hasMessages and numberOfMessages here, unfortunate the numberOfMessages returns 0 if unknown
|
|
|
|
|
|
val noOfMsgs: Long = if (deep) numberOfMessages(target) else 0
|
2012-03-14 12:46:22 +01:00
|
|
|
|
if (noOfMsgs > 0) noOfMsgs else Long.MaxValue - 3 //Just better than a suspended actorref
|
2012-03-14 12:27:28 +01:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (newScore == 0) target
|
|
|
|
|
|
else if (newScore < 0 || newScore >= currentScore) getNext(targets, proposedTarget, currentScore, at + 1, deep)
|
|
|
|
|
|
else getNext(targets, target, newScore, at + 1, deep)
|
2012-01-11 13:56:38 +01:00
|
|
|
|
}
|
2012-03-14 12:27:28 +01:00
|
|
|
|
|
2011-12-13 12:39:02 +01:00
|
|
|
|
{
|
|
|
|
|
|
case (sender, message) ⇒
|
|
|
|
|
|
message match {
|
2012-01-17 08:45:07 +01:00
|
|
|
|
case Broadcast(msg) ⇒ toAll(sender, routeeProvider.routees)
|
2011-12-13 12:39:02 +01:00
|
|
|
|
case msg ⇒ List(Destination(sender, getNext()))
|
|
|
|
|
|
}
|
2011-08-12 10:03:33 +03:00
|
|
|
|
}
|
2011-05-17 21:15:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-08-17 10:21:27 +03:00
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
object BroadcastRouter {
|
2012-05-24 12:12:45 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new BroadcastRouter, routing to the specified routees
|
|
|
|
|
|
*/
|
|
|
|
|
|
def apply(routees: Iterable[ActorRef]): BroadcastRouter = new BroadcastRouter(routees = routees map (_.path.toString))
|
2012-01-05 17:54:33 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API to create router with the supplied 'routees' actors.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def create(routees: java.lang.Iterable[ActorRef]): BroadcastRouter = {
|
|
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
|
apply(routees.asScala)
|
|
|
|
|
|
}
|
2011-12-12 23:31:15 +01:00
|
|
|
|
}
|
2011-08-29 15:50:40 +02:00
|
|
|
|
/**
|
2011-12-11 22:34:38 +01:00
|
|
|
|
* A Router that uses broadcasts a message to all its connections.
|
|
|
|
|
|
* <br>
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'routees' does not make logical sense as this means
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* that the router should both create new actors and use the 'routees' actor(s).
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'routees' will be used.
|
2011-12-11 22:34:38 +01:00
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* if you provide either 'nrOfInstances' or 'routees' during instantiation they will
|
|
|
|
|
|
* be ignored if the router is defined in the configuration file for the actor being used.
|
|
|
|
|
|
*
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* <h1>Supervision Setup</h1>
|
|
|
|
|
|
*
|
2012-02-18 22:32:41 +01:00
|
|
|
|
* The router creates a “head” actor which supervises and/or monitors the
|
|
|
|
|
|
* routees. Instances are created as children of this actor, hence the
|
|
|
|
|
|
* children are not supervised by the parent of the router. Common choices are
|
|
|
|
|
|
* to always escalate (meaning that fault handling is always applied to all
|
|
|
|
|
|
* children simultaneously; this is the default) or use the parent’s strategy,
|
|
|
|
|
|
* which will result in routed children being treated individually, but it is
|
|
|
|
|
|
* possible as well to use Routers to give different supervisor strategies to
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* different groups of children.
|
|
|
|
|
|
*
|
|
|
|
|
|
* {{{
|
|
|
|
|
|
* class MyActor extends Actor {
|
|
|
|
|
|
* override val supervisorStrategy = ...
|
|
|
|
|
|
*
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* val poolAsAWhole = context.actorOf(Props[SomeActor].withRouter(BroadcastRouter(5)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val poolIndividuals = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* BroadcastRouter(5, supervisorStrategy = this.supervisorStrategy)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val specialChild = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* BroadcastRouter(5, supervisorStrategy = OneForOneStrategy() {
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* ...
|
|
|
|
|
|
* })))
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }}}
|
|
|
|
|
|
*
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routees string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-08-17 10:21:27 +03:00
|
|
|
|
*/
|
2012-07-30 13:26:12 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-02-10 14:13:40 +01:00
|
|
|
|
case class BroadcastRouter(nrOfInstances: Int = 0, routees: Iterable[String] = Nil, override val resizer: Option[Resizer] = None,
|
2012-02-18 22:15:39 +01:00
|
|
|
|
val routerDispatcher: String = Dispatchers.DefaultDispatcherId,
|
|
|
|
|
|
val supervisorStrategy: SupervisorStrategy = Router.defaultSupervisorStrategy)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
extends RouterConfig with BroadcastLike {
|
2011-08-17 10:21:27 +03:00
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets nrOfInstances to be created.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(nr: Int) = this(nrOfInstances = nr)
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Constructor that sets the routees to be used.
|
2011-12-12 15:06:40 +01:00
|
|
|
|
* Java API
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routeePaths string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-12-12 15:06:40 +01:00
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def this(routeePaths: java.lang.Iterable[String]) = this(routees = iterableAsScalaIterable(routeePaths))
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* Constructor that sets the resizer to be used.
|
2012-01-09 20:25:24 +01:00
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
def this(resizer: Resizer) = this(resizer = Some(resizer))
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
2012-02-10 14:13:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting routerDispatcher
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withDispatcher(dispatcherId: String): BroadcastRouter = copy(routerDispatcher = dispatcherId)
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting the supervisor strategy to be used for the “head”
|
|
|
|
|
|
* Router actor.
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def withSupervisorStrategy(strategy: SupervisorStrategy): BroadcastRouter = copy(supervisorStrategy = strategy)
|
2012-08-27 14:47:32 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-08-31 11:16:57 +02:00
|
|
|
|
* Uses the resizer of the given Routerconfig if this RouterConfig
|
|
|
|
|
|
* doesn't have one, i.e. the resizer defined in code is used if
|
|
|
|
|
|
* resizer was not defined in config.
|
2012-08-27 14:47:32 +02:00
|
|
|
|
*/
|
|
|
|
|
|
override def withFallback(other: RouterConfig): RouterConfig = {
|
|
|
|
|
|
if (this.resizer.isEmpty && other.resizer.isDefined) copy(resizer = other.resizer)
|
|
|
|
|
|
else this
|
|
|
|
|
|
}
|
2011-12-13 01:09:05 +01:00
|
|
|
|
}
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
2011-12-13 01:09:05 +01:00
|
|
|
|
trait BroadcastLike { this: RouterConfig ⇒
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def nrOfInstances: Int
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def routees: Iterable[String]
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
if (resizer.isEmpty) {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
if (routees.isEmpty) routeeProvider.createRoutees(nrOfInstances)
|
2012-08-28 20:54:16 +02:00
|
|
|
|
else routeeProvider.registerRouteesFor(routees)
|
|
|
|
|
|
}
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
2011-12-13 12:39:02 +01:00
|
|
|
|
{
|
2012-01-18 14:20:13 +01:00
|
|
|
|
case (sender, message) ⇒ toAll(sender, routeeProvider.routees)
|
2011-12-11 22:34:38 +01:00
|
|
|
|
}
|
2011-08-17 10:21:27 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
object ScatterGatherFirstCompletedRouter {
|
2012-05-24 12:12:45 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new ScatterGatherFirstCompletedRouter, routing to the specified routees, timing out after the specified Duration
|
|
|
|
|
|
*/
|
2012-09-14 10:08:40 +02:00
|
|
|
|
def apply(routees: Iterable[ActorRef], within: FiniteDuration): ScatterGatherFirstCompletedRouter =
|
2012-05-24 12:12:45 +02:00
|
|
|
|
new ScatterGatherFirstCompletedRouter(routees = routees map (_.path.toString), within = within)
|
2012-01-05 17:54:33 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API to create router with the supplied 'routees' actors.
|
|
|
|
|
|
*/
|
2012-09-14 10:08:40 +02:00
|
|
|
|
def create(routees: java.lang.Iterable[ActorRef], within: FiniteDuration): ScatterGatherFirstCompletedRouter = {
|
2012-01-05 17:54:33 +01:00
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
|
apply(routees.asScala, within)
|
|
|
|
|
|
}
|
2011-12-12 23:31:15 +01:00
|
|
|
|
}
|
2011-08-29 15:50:40 +02:00
|
|
|
|
/**
|
2011-12-12 15:06:40 +01:00
|
|
|
|
* Simple router that broadcasts the message to all routees, and replies with the first response.
|
2012-02-03 18:08:43 +01:00
|
|
|
|
* <br/>
|
|
|
|
|
|
* You have to defin the 'within: Duration' parameter (f.e: within = 10 seconds).
|
|
|
|
|
|
* <br/>
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'routees' does not make logical sense as this means
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* that the router should both create new actors and use the 'routees' actor(s).
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'routees' will be used.
|
2012-02-03 18:08:43 +01:00
|
|
|
|
* <br/>
|
2011-12-12 15:06:40 +01:00
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* if you provide either 'nrOfInstances' or 'routees' during instantiation they will
|
|
|
|
|
|
* be ignored if the router is defined in the configuration file for the actor being used.
|
|
|
|
|
|
*
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* <h1>Supervision Setup</h1>
|
|
|
|
|
|
*
|
2012-02-18 22:32:41 +01:00
|
|
|
|
* The router creates a “head” actor which supervises and/or monitors the
|
|
|
|
|
|
* routees. Instances are created as children of this actor, hence the
|
|
|
|
|
|
* children are not supervised by the parent of the router. Common choices are
|
|
|
|
|
|
* to always escalate (meaning that fault handling is always applied to all
|
|
|
|
|
|
* children simultaneously; this is the default) or use the parent’s strategy,
|
|
|
|
|
|
* which will result in routed children being treated individually, but it is
|
|
|
|
|
|
* possible as well to use Routers to give different supervisor strategies to
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* different groups of children.
|
|
|
|
|
|
*
|
|
|
|
|
|
* {{{
|
|
|
|
|
|
* class MyActor extends Actor {
|
|
|
|
|
|
* override val supervisorStrategy = ...
|
|
|
|
|
|
*
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* val poolAsAWhole = context.actorOf(Props[SomeActor].withRouter(ScatterGatherFirstCompletedRouter(5)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val poolIndividuals = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* ScatterGatherFirstCompletedRouter(5, supervisorStrategy = this.supervisorStrategy)))
|
2012-02-18 22:15:39 +01:00
|
|
|
|
*
|
|
|
|
|
|
* val specialChild = context.actorOf(Props[SomeActor].withRouter(
|
2012-09-10 15:05:21 +02:00
|
|
|
|
* ScatterGatherFirstCompletedRouter(5, supervisorStrategy = OneForOneStrategy() {
|
2012-02-18 22:15:39 +01:00
|
|
|
|
* ...
|
|
|
|
|
|
* })))
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }}}
|
|
|
|
|
|
*
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routees string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-08-17 10:21:27 +03:00
|
|
|
|
*/
|
2012-07-30 13:26:12 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-09-14 10:08:40 +02:00
|
|
|
|
case class ScatterGatherFirstCompletedRouter(nrOfInstances: Int = 0, routees: Iterable[String] = Nil, within: FiniteDuration,
|
2012-02-10 14:13:40 +01:00
|
|
|
|
override val resizer: Option[Resizer] = None,
|
2012-02-18 22:15:39 +01:00
|
|
|
|
val routerDispatcher: String = Dispatchers.DefaultDispatcherId,
|
|
|
|
|
|
val supervisorStrategy: SupervisorStrategy = Router.defaultSupervisorStrategy)
|
2011-12-13 01:09:05 +01:00
|
|
|
|
extends RouterConfig with ScatterGatherFirstCompletedLike {
|
2011-12-11 22:34:38 +01:00
|
|
|
|
|
2012-02-05 09:19:57 +01:00
|
|
|
|
if (within <= Duration.Zero) throw new IllegalArgumentException(
|
2012-02-04 17:50:11 +01:00
|
|
|
|
"[within: Duration] can not be zero or negative, was [" + within + "]")
|
|
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets nrOfInstances to be created.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-09-14 10:08:40 +02:00
|
|
|
|
def this(nr: Int, w: FiniteDuration) = this(nrOfInstances = nr, within = w)
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-12-17 16:33:29 +01:00
|
|
|
|
* Constructor that sets the routees to be used.
|
2011-12-12 15:06:40 +01:00
|
|
|
|
* Java API
|
2012-01-12 09:53:53 +01:00
|
|
|
|
* @param routeePaths string representation of the actor paths of the routees that will be looked up
|
|
|
|
|
|
* using `actorFor` in [[akka.actor.ActorRefProvider]]
|
2011-12-12 15:06:40 +01:00
|
|
|
|
*/
|
2012-09-14 10:08:40 +02:00
|
|
|
|
def this(routeePaths: java.lang.Iterable[String], w: FiniteDuration) =
|
2012-01-12 09:53:53 +01:00
|
|
|
|
this(routees = iterableAsScalaIterable(routeePaths), within = w)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* Constructor that sets the resizer to be used.
|
2012-01-09 20:25:24 +01:00
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2012-09-14 10:08:40 +02:00
|
|
|
|
def this(resizer: Resizer, w: FiniteDuration) = this(resizer = Some(resizer), within = w)
|
2012-02-10 14:13:40 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting routerDispatcher
|
|
|
|
|
|
*/
|
|
|
|
|
|
def withDispatcher(dispatcherId: String) = copy(routerDispatcher = dispatcherId)
|
2012-02-18 22:15:39 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API for setting the supervisor strategy to be used for the “head”
|
|
|
|
|
|
* Router actor.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def withSupervisorStrategy(strategy: SupervisorStrategy) = copy(supervisorStrategy = strategy)
|
2012-08-27 14:47:32 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-08-31 11:16:57 +02:00
|
|
|
|
* Uses the resizer of the given Routerconfig if this RouterConfig
|
|
|
|
|
|
* doesn't have one, i.e. the resizer defined in code is used if
|
|
|
|
|
|
* resizer was not defined in config.
|
2012-08-27 14:47:32 +02:00
|
|
|
|
*/
|
|
|
|
|
|
override def withFallback(other: RouterConfig): RouterConfig = {
|
|
|
|
|
|
if (this.resizer.isEmpty && other.resizer.isDefined) copy(resizer = other.resizer)
|
|
|
|
|
|
else this
|
|
|
|
|
|
}
|
2011-12-13 01:09:05 +01:00
|
|
|
|
}
|
2011-12-12 15:06:40 +01:00
|
|
|
|
|
2011-12-13 01:09:05 +01:00
|
|
|
|
trait ScatterGatherFirstCompletedLike { this: RouterConfig ⇒
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def nrOfInstances: Int
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2011-12-21 11:46:39 +01:00
|
|
|
|
def routees: Iterable[String]
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2012-09-14 10:08:40 +02:00
|
|
|
|
def within: FiniteDuration
|
2011-12-21 10:03:26 +01:00
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def createRoute(routeeProvider: RouteeProvider): Route = {
|
2012-08-28 20:54:16 +02:00
|
|
|
|
if (resizer.isEmpty) {
|
2012-08-29 19:33:19 +02:00
|
|
|
|
if (routees.isEmpty) routeeProvider.createRoutees(nrOfInstances)
|
2012-08-28 20:54:16 +02:00
|
|
|
|
else routeeProvider.registerRouteesFor(routees)
|
|
|
|
|
|
}
|
2011-12-11 22:34:38 +01:00
|
|
|
|
|
2011-12-13 12:39:02 +01:00
|
|
|
|
{
|
|
|
|
|
|
case (sender, message) ⇒
|
2012-01-18 14:20:13 +01:00
|
|
|
|
val provider: ActorRefProvider = routeeProvider.context.asInstanceOf[ActorCell].systemImpl.provider
|
2012-08-30 13:12:23 +02:00
|
|
|
|
import routeeProvider.context.dispatcher
|
2012-03-23 21:35:52 +01:00
|
|
|
|
val asker = akka.pattern.PromiseActorRef(provider, within)
|
2012-07-04 15:25:30 +02:00
|
|
|
|
asker.result.future.pipeTo(sender)
|
2012-01-18 14:20:13 +01:00
|
|
|
|
toAll(asker, routeeProvider.routees)
|
2011-12-11 22:34:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-08-17 10:21:27 +03:00
|
|
|
|
}
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* Routers with dynamically resizable number of routees is implemented by providing a Resizer
|
|
|
|
|
|
* implementation in [[akka.routing.RouterConfig]].
|
2012-01-09 20:25:24 +01:00
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
trait Resizer {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Is it time for resizing. Typically implemented with modulo of nth message, but
|
|
|
|
|
|
* could be based on elapsed time or something else. The messageCounter starts with 0
|
|
|
|
|
|
* for the initial resize and continues with 1 for the first message. Make sure to perform
|
|
|
|
|
|
* initial resize before first message (messageCounter == 0), because there is no guarantee
|
|
|
|
|
|
* that resize will be done when concurrent messages are in play.
|
2012-02-09 16:59:16 +01:00
|
|
|
|
*
|
|
|
|
|
|
* CAUTION: this method is invoked from the thread which tries to send a
|
|
|
|
|
|
* message to the pool, i.e. the ActorRef.!() method, hence it may be called
|
|
|
|
|
|
* concurrently.
|
2012-01-10 15:53:27 +01:00
|
|
|
|
*/
|
|
|
|
|
|
def isTimeForResize(messageCounter: Long): Boolean
|
2012-02-09 16:59:16 +01:00
|
|
|
|
|
2012-01-10 15:53:27 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Decide if the capacity of the router need to be changed. Will be invoked when `isTimeForResize`
|
|
|
|
|
|
* returns true and no other resize is in progress.
|
2012-01-17 08:45:07 +01:00
|
|
|
|
* Create and register more routees with `routeeProvider.registerRoutees(newRoutees)
|
|
|
|
|
|
* or remove routees with `routeeProvider.unregisterRoutees(abandonedRoutees)` and
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* sending [[akka.actor.PoisonPill]] to them.
|
2012-02-09 16:59:16 +01:00
|
|
|
|
*
|
|
|
|
|
|
* This method is invoked only in the context of the Router actor in order to safely
|
|
|
|
|
|
* create/stop children.
|
2012-01-10 15:53:27 +01:00
|
|
|
|
*/
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def resize(routeeProvider: RouteeProvider): Unit
|
2012-01-09 20:25:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-01-12 16:37:08 +01:00
|
|
|
|
case object DefaultResizer {
|
2012-05-24 12:12:45 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Creates a new DefaultResizer from the given configuration
|
|
|
|
|
|
*/
|
2012-01-12 16:37:08 +01:00
|
|
|
|
def apply(resizerConfig: Config): DefaultResizer =
|
|
|
|
|
|
DefaultResizer(
|
|
|
|
|
|
lowerBound = resizerConfig.getInt("lower-bound"),
|
|
|
|
|
|
upperBound = resizerConfig.getInt("upper-bound"),
|
|
|
|
|
|
pressureThreshold = resizerConfig.getInt("pressure-threshold"),
|
|
|
|
|
|
rampupRate = resizerConfig.getDouble("rampup-rate"),
|
|
|
|
|
|
backoffThreshold = resizerConfig.getDouble("backoff-threshold"),
|
|
|
|
|
|
backoffRate = resizerConfig.getDouble("backoff-rate"),
|
|
|
|
|
|
stopDelay = Duration(resizerConfig.getMilliseconds("stop-delay"), TimeUnit.MILLISECONDS),
|
|
|
|
|
|
messagesPerResize = resizerConfig.getInt("messages-per-resize"))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-05-18 19:25:43 +02:00
|
|
|
|
//FIXME DOCUMENT ME
|
2012-07-31 12:54:31 +02:00
|
|
|
|
@SerialVersionUID(1L)
|
2012-01-10 15:53:27 +01:00
|
|
|
|
case class DefaultResizer(
|
2012-01-09 20:25:24 +01:00
|
|
|
|
/**
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* The fewest number of routees the router should ever have.
|
2012-01-09 20:25:24 +01:00
|
|
|
|
*/
|
|
|
|
|
|
lowerBound: Int = 1,
|
|
|
|
|
|
/**
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* The most number of routees the router should ever have.
|
|
|
|
|
|
* Must be greater than or equal to `lowerBound`.
|
|
|
|
|
|
*/
|
2012-01-09 20:25:24 +01:00
|
|
|
|
upperBound: Int = 10,
|
|
|
|
|
|
/**
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* Threshold to evaluate if routee is considered to be busy (under pressure).
|
|
|
|
|
|
* Implementation depends on this value (default is 1).
|
|
|
|
|
|
* <ul>
|
|
|
|
|
|
* <li> 0: number of routees currently processing a message.</li>
|
|
|
|
|
|
* <li> 1: number of routees currently processing a message has
|
|
|
|
|
|
* some messages in mailbox.</li>
|
|
|
|
|
|
* <li> > 1: number of routees with at least the configured `pressureThreshold`
|
|
|
|
|
|
* messages in their mailbox. Note that estimating mailbox size of
|
|
|
|
|
|
* default UnboundedMailbox is O(N) operation.</li>
|
|
|
|
|
|
* </ul>
|
|
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
pressureThreshold: Int = 1,
|
2012-01-09 20:25:24 +01:00
|
|
|
|
/**
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* Percentage to increase capacity whenever all routees are busy.
|
|
|
|
|
|
* For example, 0.2 would increase 20% (rounded up), i.e. if current
|
|
|
|
|
|
* capacity is 6 it will request an increase of 2 more routees.
|
|
|
|
|
|
*/
|
2012-01-09 20:25:24 +01:00
|
|
|
|
rampupRate: Double = 0.2,
|
|
|
|
|
|
/**
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* Minimum fraction of busy routees before backing off.
|
|
|
|
|
|
* For example, if this is 0.3, then we'll remove some routees only when
|
|
|
|
|
|
* less than 30% of routees are busy, i.e. if current capacity is 10 and
|
|
|
|
|
|
* 3 are busy then the capacity is unchanged, but if 2 or less are busy
|
|
|
|
|
|
* the capacity is decreased.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Use 0.0 or negative to avoid removal of routees.
|
|
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
backoffThreshold: Double = 0.3,
|
2012-01-09 20:25:24 +01:00
|
|
|
|
/**
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* Fraction of routees to be removed when the resizer reaches the
|
|
|
|
|
|
* backoffThreshold.
|
|
|
|
|
|
* For example, 0.1 would decrease 10% (rounded up), i.e. if current
|
|
|
|
|
|
* capacity is 9 it will request an decrease of 1 routee.
|
|
|
|
|
|
*/
|
2012-01-09 20:25:24 +01:00
|
|
|
|
backoffRate: Double = 0.1,
|
|
|
|
|
|
/**
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* When the resizer reduce the capacity the abandoned routee actors are stopped
|
|
|
|
|
|
* with PoisonPill after this delay. The reason for the delay is to give concurrent
|
|
|
|
|
|
* messages a chance to be placed in mailbox before sending PoisonPill.
|
|
|
|
|
|
* Use 0 seconds to skip delay.
|
|
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
stopDelay: Duration = 1.second,
|
|
|
|
|
|
/**
|
2012-01-11 13:56:38 +01:00
|
|
|
|
* Number of messages between resize operation.
|
|
|
|
|
|
* Use 1 to resize before each message.
|
|
|
|
|
|
*/
|
2012-01-11 11:13:03 +01:00
|
|
|
|
messagesPerResize: Int = 10) extends Resizer {
|
2012-01-10 15:53:27 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Java API constructor for default values except bounds.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def this(lower: Int, upper: Int) = this(lowerBound = lower, upperBound = upper)
|
|
|
|
|
|
|
|
|
|
|
|
if (lowerBound < 0) throw new IllegalArgumentException("lowerBound must be >= 0, was: [%s]".format(lowerBound))
|
|
|
|
|
|
if (upperBound < 0) throw new IllegalArgumentException("upperBound must be >= 0, was: [%s]".format(upperBound))
|
|
|
|
|
|
if (upperBound < lowerBound) throw new IllegalArgumentException("upperBound must be >= lowerBound, was: [%s] < [%s]".format(upperBound, lowerBound))
|
|
|
|
|
|
if (rampupRate < 0.0) throw new IllegalArgumentException("rampupRate must be >= 0.0, was [%s]".format(rampupRate))
|
|
|
|
|
|
if (backoffThreshold > 1.0) throw new IllegalArgumentException("backoffThreshold must be <= 1.0, was [%s]".format(backoffThreshold))
|
|
|
|
|
|
if (backoffRate < 0.0) throw new IllegalArgumentException("backoffRate must be >= 0.0, was [%s]".format(backoffRate))
|
2012-01-11 11:13:03 +01:00
|
|
|
|
if (messagesPerResize <= 0) throw new IllegalArgumentException("messagesPerResize must be > 0, was [%s]".format(messagesPerResize))
|
2012-01-10 15:53:27 +01:00
|
|
|
|
|
2012-01-11 11:13:03 +01:00
|
|
|
|
def isTimeForResize(messageCounter: Long): Boolean = (messageCounter % messagesPerResize == 0)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
def resize(routeeProvider: RouteeProvider): Unit = {
|
2012-01-17 08:45:07 +01:00
|
|
|
|
val currentRoutees = routeeProvider.routees
|
2012-01-09 20:25:24 +01:00
|
|
|
|
val requestedCapacity = capacity(currentRoutees)
|
|
|
|
|
|
|
2012-08-29 19:33:19 +02:00
|
|
|
|
if (requestedCapacity > 0) routeeProvider.createRoutees(requestedCapacity)
|
2012-08-28 20:54:16 +02:00
|
|
|
|
else if (requestedCapacity < 0) routeeProvider.removeRoutees(-requestedCapacity, stopDelay)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* Returns the overall desired change in resizer capacity. Positive value will
|
|
|
|
|
|
* add routees to the resizer. Negative value will remove routees from the
|
|
|
|
|
|
* resizer.
|
|
|
|
|
|
* @param routees The current actor in the resizer
|
|
|
|
|
|
* @return the number of routees by which the resizer should be adjusted (positive, negative or zero)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
*/
|
|
|
|
|
|
def capacity(routees: IndexedSeq[ActorRef]): Int = {
|
|
|
|
|
|
val currentSize = routees.size
|
2012-02-13 14:43:20 +01:00
|
|
|
|
val press = pressure(routees)
|
|
|
|
|
|
val delta = filter(press, currentSize)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
val proposed = currentSize + delta
|
|
|
|
|
|
|
|
|
|
|
|
if (proposed < lowerBound) delta + (lowerBound - proposed)
|
|
|
|
|
|
else if (proposed > upperBound) delta - (proposed - upperBound)
|
|
|
|
|
|
else delta
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Number of routees considered busy, or above 'pressure level'.
|
|
|
|
|
|
*
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* Implementation depends on the value of `pressureThreshold`
|
|
|
|
|
|
* (default is 1).
|
|
|
|
|
|
* <ul>
|
|
|
|
|
|
* <li> 0: number of routees currently processing a message.</li>
|
|
|
|
|
|
* <li> 1: number of routees currently processing a message has
|
|
|
|
|
|
* some messages in mailbox.</li>
|
|
|
|
|
|
* <li> > 1: number of routees with at least the configured `pressureThreshold`
|
|
|
|
|
|
* messages in their mailbox. Note that estimating mailbox size of
|
|
|
|
|
|
* default UnboundedMailbox is O(N) operation.</li>
|
|
|
|
|
|
* </ul>
|
2012-01-09 20:25:24 +01:00
|
|
|
|
*
|
2012-01-10 15:53:27 +01:00
|
|
|
|
* @param routees the current resizer of routees
|
2012-01-09 20:25:24 +01:00
|
|
|
|
* @return number of busy routees, between 0 and routees.size
|
|
|
|
|
|
*/
|
2012-01-10 15:53:27 +01:00
|
|
|
|
def pressure(routees: IndexedSeq[ActorRef]): Int = {
|
2012-01-11 11:13:03 +01:00
|
|
|
|
routees count {
|
2012-06-13 17:57:56 +02:00
|
|
|
|
case a: ActorRefWithCell ⇒
|
|
|
|
|
|
a.underlying match {
|
|
|
|
|
|
case cell: ActorCell ⇒
|
|
|
|
|
|
pressureThreshold match {
|
|
|
|
|
|
case 1 ⇒ cell.mailbox.isScheduled && cell.mailbox.hasMessages
|
|
|
|
|
|
case i if i < 1 ⇒ cell.mailbox.isScheduled && cell.currentMessage != null
|
|
|
|
|
|
case threshold ⇒ cell.mailbox.numberOfMessages >= threshold
|
|
|
|
|
|
}
|
|
|
|
|
|
case cell ⇒
|
|
|
|
|
|
pressureThreshold match {
|
|
|
|
|
|
case 1 ⇒ cell.hasMessages
|
|
|
|
|
|
case i if i < 1 ⇒ true // unstarted cells are always busy, for example
|
|
|
|
|
|
case threshold ⇒ cell.numberOfMessages >= threshold
|
|
|
|
|
|
}
|
2012-01-11 11:13:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
case x ⇒
|
|
|
|
|
|
false
|
2012-01-09 20:25:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* This method can be used to smooth the capacity delta by considering
|
|
|
|
|
|
* the current pressure and current capacity.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param pressure current number of busy routees
|
|
|
|
|
|
* @param capacity current number of routees
|
|
|
|
|
|
* @return proposed change in the capacity
|
|
|
|
|
|
*/
|
2012-05-18 19:25:43 +02:00
|
|
|
|
def filter(pressure: Int, capacity: Int): Int = rampup(pressure, capacity) + backoff(pressure, capacity)
|
2012-01-09 20:25:24 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Computes a proposed positive (or zero) capacity delta using
|
|
|
|
|
|
* the configured `rampupRate`.
|
|
|
|
|
|
* @param pressure the current number of busy routees
|
|
|
|
|
|
* @param capacity the current number of total routees
|
|
|
|
|
|
* @return proposed increase in capacity
|
|
|
|
|
|
*/
|
|
|
|
|
|
def rampup(pressure: Int, capacity: Int): Int =
|
|
|
|
|
|
if (pressure < capacity) 0 else math.ceil(rampupRate * capacity) toInt
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Computes a proposed negative (or zero) capacity delta using
|
|
|
|
|
|
* the configured `backoffThreshold` and `backoffRate`
|
|
|
|
|
|
* @param pressure the current number of busy routees
|
|
|
|
|
|
* @param capacity the current number of total routees
|
|
|
|
|
|
* @return proposed decrease in capacity (as a negative number)
|
|
|
|
|
|
*/
|
|
|
|
|
|
def backoff(pressure: Int, capacity: Int): Int =
|
|
|
|
|
|
if (backoffThreshold > 0.0 && backoffRate > 0.0 && capacity > 0 && pressure.toDouble / capacity < backoffThreshold)
|
2012-01-10 15:53:27 +01:00
|
|
|
|
math.floor(-1.0 * backoffRate * capacity) toInt
|
2012-01-09 20:25:24 +01:00
|
|
|
|
else 0
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|