2010-05-03 19:32:40 +02:00
|
|
|
|
/**
|
2011-07-14 16:03:08 +02:00
|
|
|
|
* Copyright (C) 2009-2011 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
|
|
|
|
|
2011-07-28 15:48:03 +03:00
|
|
|
|
import akka.actor._
|
2011-08-27 08:10:25 +03:00
|
|
|
|
|
2011-11-25 14:49:09 +01:00
|
|
|
|
import akka.japi.Creator
|
2011-12-08 14:30:57 +01:00
|
|
|
|
import java.lang.reflect.InvocationTargetException
|
|
|
|
|
|
import akka.config.ConfigurationException
|
|
|
|
|
|
import java.util.concurrent.atomic.AtomicInteger
|
2011-12-12 15:06:40 +01:00
|
|
|
|
import akka.util.ReflectiveAccess
|
2011-12-11 22:34:38 +01:00
|
|
|
|
import akka.AkkaException
|
2011-12-12 15:06:40 +01:00
|
|
|
|
import scala.collection.JavaConversions._
|
|
|
|
|
|
import java.util.concurrent.TimeUnit
|
2011-08-27 08:10:25 +03: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)
|
|
|
|
|
|
extends LocalActorRef(
|
|
|
|
|
|
_system,
|
2011-12-12 23:31:15 +01:00
|
|
|
|
_props.copy(creator = () ⇒ _props.routerConfig.createActor()),
|
2011-12-08 14:30:57 +01:00
|
|
|
|
_supervisor,
|
|
|
|
|
|
_path) {
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
val route: Route = _props.routerConfig.createRoute(_props.creator, actorContext)
|
2011-12-08 14:30:57 +01:00
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
override def !(message: Any)(implicit sender: ActorRef = null): Unit = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
val s = if (sender eq null) underlying.system.deadLetters else sender
|
|
|
|
|
|
|
|
|
|
|
|
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-12 15:06:40 +01:00
|
|
|
|
route(s, message) match {
|
|
|
|
|
|
case Nil ⇒ super.!(message)(s)
|
|
|
|
|
|
case refs ⇒ refs foreach (p ⇒ p.recipient.!(msg)(p.sender))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
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
|
|
|
|
|
|
* be returned by `apply()` should not send a message to itself in its
|
|
|
|
|
|
* 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 {
|
|
|
|
|
|
|
|
|
|
|
|
def createActor(): Router = new Router {}
|
|
|
|
|
|
|
|
|
|
|
|
def adaptFromDeploy(deploy: Option[Deploy]): RouterConfig = {
|
|
|
|
|
|
deploy match {
|
|
|
|
|
|
case Some(Deploy(_, _, _, NoRouter, _)) ⇒ this
|
|
|
|
|
|
case Some(Deploy(_, _, _, r, _)) ⇒ r
|
|
|
|
|
|
case _ ⇒ this
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def createRoute(creator: () ⇒ Actor, actorContext: ActorContext): Route
|
|
|
|
|
|
|
|
|
|
|
|
protected def createRoutees(props: Props, context: ActorContext, nrOfInstances: Int, targets: Iterable[String]): Vector[ActorRef] = (nrOfInstances, targets) match {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
case (0, Nil) ⇒ throw new IllegalArgumentException("Insufficient information - missing configuration.")
|
|
|
|
|
|
case (x, Nil) ⇒ (1 to x).map(_ ⇒ context.actorOf(props))(scala.collection.breakOut)
|
2011-12-12 23:31:15 +01:00
|
|
|
|
case (_, xs) ⇒ Vector.empty[ActorRef] ++ xs.map(context.actorFor(_))
|
2011-12-12 15:06:40 +01:00
|
|
|
|
}
|
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
|
|
|
|
|
|
* messages which the corresponding [[akka.actor.RouterConfig]] lets
|
|
|
|
|
|
* 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 {
|
|
|
|
|
|
def receive = {
|
|
|
|
|
|
case _ ⇒
|
2011-07-28 15:48:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-03-04 19:02:23 +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.
|
|
|
|
|
|
*/
|
|
|
|
|
|
case class Broadcast(message: Any)
|
|
|
|
|
|
|
2011-12-11 22:34:38 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Routing configuration that indicates no routing.
|
|
|
|
|
|
* Oxymoron style.
|
|
|
|
|
|
*/
|
|
|
|
|
|
case object NoRouter extends RouterConfig {
|
|
|
|
|
|
def createRoute(creator: () ⇒ Actor, actorContext: ActorContext) = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
object RoundRobinRouter {
|
|
|
|
|
|
def apply(targets: Iterable[ActorRef]) = new RoundRobinRouter(targets = targets map (_.path.toString))
|
|
|
|
|
|
}
|
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>
|
|
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means
|
|
|
|
|
|
* that the round robin should both create new actors and use the 'targets' actor(s).
|
|
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'targets' will be used.
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
|
|
|
|
|
* if you provide either 'nrOfInstances' or 'targets' to during instantiation they will
|
|
|
|
|
|
* be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used.
|
2011-07-28 15:48:03 +03:00
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
case class RoundRobinRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) extends RouterConfig {
|
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
|
|
|
|
|
|
*/
|
|
|
|
|
|
def this(nr: Int) = {
|
|
|
|
|
|
this(nrOfInstances = nr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets the targets to be used.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def this(t: java.util.Collection[String]) = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
this(targets = collectionAsScalaIterable(t))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def createRoute(creator: () ⇒ Actor, context: ActorContext): Route = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
val routees: Vector[ActorRef] =
|
|
|
|
|
|
createRoutees(context.props.copy(creator = creator, routerConfig = NoRouter), context, nrOfInstances, targets)
|
2011-07-28 15:48:03 +03:00
|
|
|
|
|
2011-12-08 14:30:57 +01:00
|
|
|
|
val next = new AtomicInteger(0)
|
2011-08-12 10:03:33 +03:00
|
|
|
|
|
2011-12-08 14:30:57 +01:00
|
|
|
|
def getNext(): ActorRef = {
|
|
|
|
|
|
routees(next.getAndIncrement % routees.size)
|
|
|
|
|
|
}
|
2011-08-18 11:35:14 +02:00
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
{ (sender, message) ⇒
|
|
|
|
|
|
message match {
|
|
|
|
|
|
case msg: AutoReceivedMessage ⇒ Nil
|
|
|
|
|
|
case Broadcast(msg) ⇒ routees map (Destination(sender, _))
|
|
|
|
|
|
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 {
|
|
|
|
|
|
def apply(targets: Iterable[ActorRef]) = new RandomRouter(targets = targets map (_.path.toString))
|
|
|
|
|
|
}
|
2011-12-11 22:34:38 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* A Router that randomly selects one of the target connections to send a message to.
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means
|
|
|
|
|
|
* that the random router should both create new actors and use the 'targets' actor(s).
|
|
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'targets' will be used.
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
|
|
|
|
|
* if you provide either 'nrOfInstances' or 'targets' to during instantiation they will
|
|
|
|
|
|
* be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used.
|
|
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
case class RandomRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) extends RouterConfig {
|
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
|
|
|
|
|
|
*/
|
|
|
|
|
|
def this(nr: Int) = {
|
|
|
|
|
|
this(nrOfInstances = nr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets the targets to be used.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def this(t: java.util.Collection[String]) = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
this(targets = collectionAsScalaIterable(t))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-11 22:34:38 +01:00
|
|
|
|
import java.security.SecureRandom
|
2011-08-12 10:03:33 +03:00
|
|
|
|
|
2011-12-11 22:34:38 +01:00
|
|
|
|
private val random = new ThreadLocal[SecureRandom] {
|
|
|
|
|
|
override def initialValue = SecureRandom.getInstance("SHA1PRNG")
|
|
|
|
|
|
}
|
2011-08-12 10:03:33 +03:00
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def createRoute(creator: () ⇒ Actor, context: ActorContext): Route = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
val routees: Vector[ActorRef] =
|
|
|
|
|
|
createRoutees(context.props.copy(creator = creator, routerConfig = NoRouter), context, nrOfInstances, targets)
|
2011-05-21 15:37:09 +02:00
|
|
|
|
|
2011-12-11 22:34:38 +01:00
|
|
|
|
def getNext(): ActorRef = {
|
|
|
|
|
|
routees(random.get.nextInt(routees.size))
|
|
|
|
|
|
}
|
2011-08-12 10:03:33 +03:00
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
{ (sender, message) ⇒
|
|
|
|
|
|
message match {
|
|
|
|
|
|
case msg: AutoReceivedMessage ⇒ Nil
|
|
|
|
|
|
case Broadcast(msg) ⇒ routees map (Destination(sender, _))
|
|
|
|
|
|
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 {
|
|
|
|
|
|
def apply(targets: Iterable[ActorRef]) = new BroadcastRouter(targets = targets map (_.path.toString))
|
|
|
|
|
|
}
|
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>
|
|
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means
|
|
|
|
|
|
* that the random router should both create new actors and use the 'targets' actor(s).
|
|
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'targets' will be used.
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
|
|
|
|
|
* if you provide either 'nrOfInstances' or 'targets' to during instantiation they will
|
|
|
|
|
|
* be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used.
|
2011-08-17 10:21:27 +03:00
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
case class BroadcastRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) extends RouterConfig {
|
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
|
|
|
|
|
|
*/
|
|
|
|
|
|
def this(nr: Int) = {
|
|
|
|
|
|
this(nrOfInstances = nr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets the targets to be used.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def this(t: java.util.Collection[String]) = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
this(targets = collectionAsScalaIterable(t))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def createRoute(creator: () ⇒ Actor, context: ActorContext): Route = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
val routees: Vector[ActorRef] =
|
|
|
|
|
|
createRoutees(context.props.copy(creator = creator, routerConfig = NoRouter), context, nrOfInstances, targets)
|
|
|
|
|
|
|
|
|
|
|
|
{ (sender, message) ⇒
|
|
|
|
|
|
message match {
|
|
|
|
|
|
case msg: AutoReceivedMessage ⇒ Nil
|
|
|
|
|
|
case Broadcast(msg) ⇒ routees map (Destination(sender, _))
|
|
|
|
|
|
case msg ⇒ routees map (Destination(sender, _))
|
|
|
|
|
|
}
|
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 {
|
|
|
|
|
|
def apply(targets: Iterable[ActorRef]) = new ScatterGatherFirstCompletedRouter(targets = targets map (_.path.toString))
|
|
|
|
|
|
}
|
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.
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* Please note that providing both 'nrOfInstances' and 'targets' does not make logical sense as this means
|
|
|
|
|
|
* that the random router should both create new actors and use the 'targets' actor(s).
|
|
|
|
|
|
* In this case the 'nrOfInstances' will be ignored and the 'targets' will be used.
|
|
|
|
|
|
* <br>
|
|
|
|
|
|
* <b>The</b> configuration parameter trumps the constructor arguments. This means that
|
|
|
|
|
|
* if you provide either 'nrOfInstances' or 'targets' to during instantiation they will
|
|
|
|
|
|
* be ignored if the 'nrOfInstances' is defined in the configuration file for the actor being used.
|
2011-08-17 10:21:27 +03:00
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
case class ScatterGatherFirstCompletedRouter(nrOfInstances: Int = 0, targets: Iterable[String] = Nil) extends RouterConfig {
|
2011-12-11 22:34:38 +01:00
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets nrOfInstances to be created.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
|
|
|
|
|
def this(nr: Int) = {
|
|
|
|
|
|
this(nrOfInstances = nr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constructor that sets the targets to be used.
|
|
|
|
|
|
* Java API
|
|
|
|
|
|
*/
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def this(t: java.util.Collection[String]) = {
|
2011-12-12 15:06:40 +01:00
|
|
|
|
this(targets = collectionAsScalaIterable(t))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-12-12 23:31:15 +01:00
|
|
|
|
def createRoute(creator: () ⇒ Actor, context: ActorContext): Route = {
|
|
|
|
|
|
val routees: Vector[ActorRef] =
|
|
|
|
|
|
createRoutees(context.props.copy(creator = creator, routerConfig = NoRouter), context, nrOfInstances, targets)
|
2011-12-11 22:34:38 +01:00
|
|
|
|
|
2011-12-12 15:06:40 +01:00
|
|
|
|
{ (sender, message) ⇒
|
|
|
|
|
|
val asker = context.asInstanceOf[ActorCell].systemImpl.provider.ask(Timeout(5, TimeUnit.SECONDS)).get // FIXME, NO REALLY FIXME!
|
|
|
|
|
|
asker.result.pipeTo(sender)
|
|
|
|
|
|
message match {
|
|
|
|
|
|
case msg: AutoReceivedMessage ⇒ Nil
|
|
|
|
|
|
case Broadcast(msg) ⇒ routees map (Destination(asker, _))
|
|
|
|
|
|
case msg ⇒ routees map (Destination(asker, _))
|
2011-12-11 22:34:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-12-12 15:06:40 +01:00
|
|
|
|
}
|