Merge with Peter's work (i.e. merging master into tame-globals)
This commit is contained in:
commit
44b94643e5
60 changed files with 2078 additions and 1700 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -42,6 +42,7 @@ run-codefellow
|
||||||
.cache
|
.cache
|
||||||
.idea
|
.idea
|
||||||
.scala_dependencies
|
.scala_dependencies
|
||||||
|
.cache
|
||||||
multiverse.log
|
multiverse.log
|
||||||
.eprj
|
.eprj
|
||||||
.*.swp
|
.*.swp
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,12 @@ import static org.junit.Assert.*;
|
||||||
public class JavaAPI {
|
public class JavaAPI {
|
||||||
|
|
||||||
private AkkaApplication app = new AkkaApplication();
|
private AkkaApplication app = new AkkaApplication();
|
||||||
|
|
||||||
@Test void mustBeAbleToUseUntypedActor() {
|
@Test void mustBeAbleToUseUntypedActor() {
|
||||||
final RemoteSupport remote = app.remote();
|
final RemoteSupport remote = app.remote();
|
||||||
assertNotNull(remote);
|
assertNotNull(remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test void mustInteractWithActorRegistry() {
|
|
||||||
final ActorRegistry registry = app.registry();
|
|
||||||
assertNotNull(registry);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test void mustBeAbleToCreateActorRefFromClass() {
|
@Test void mustBeAbleToCreateActorRefFromClass() {
|
||||||
ActorRef ref = app.createActor(JavaAPITestActor.class);
|
ActorRef ref = app.createActor(JavaAPITestActor.class);
|
||||||
assertNotNull(ref);
|
assertNotNull(ref);
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,7 @@ class ActorRefSpec extends AkkaSpec {
|
||||||
val latch = new CountDownLatch(1)
|
val latch = new CountDownLatch(1)
|
||||||
val a = createActor(new InnerActor {
|
val a = createActor(new InnerActor {
|
||||||
override def postStop {
|
override def postStop {
|
||||||
app.registry.unregister(self)
|
// app.registry.unregister(self)
|
||||||
latch.countDown
|
latch.countDown
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import java.util.concurrent.atomic._
|
||||||
class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSender {
|
class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSender {
|
||||||
|
|
||||||
"The Death Watch" must {
|
"The Death Watch" must {
|
||||||
def expectTerminationOf(actorRef: ActorRef) = expectMsgPF(5 seconds, "stopped") {
|
def expectTerminationOf(actorRef: ActorRef) = expectMsgPF(2 seconds, "stopped") {
|
||||||
case Terminated(`actorRef`, ex: ActorKilledException) if ex.getMessage == "Stopped" ⇒ true
|
case Terminated(`actorRef`, ex: ActorKilledException) if ex.getMessage == "Stopped" ⇒ true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +26,6 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
|
||||||
expectTerminationOf(terminal)
|
expectTerminationOf(terminal)
|
||||||
|
|
||||||
terminal.stop()
|
terminal.stop()
|
||||||
expectNoMsg(2 seconds) //Shouldn't get more terminations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"notify with all monitors with one Terminated message when an Actor is stopped" in {
|
"notify with all monitors with one Terminated message when an Actor is stopped" in {
|
||||||
|
|
@ -46,7 +45,6 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
|
||||||
terminal.stop()
|
terminal.stop()
|
||||||
monitor1.stop()
|
monitor1.stop()
|
||||||
monitor2.stop()
|
monitor2.stop()
|
||||||
expectNoMsg(2 seconds) //Shouldn't get more terminations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"notify with _current_ monitors with one Terminated message when an Actor is stopped" in {
|
"notify with _current_ monitors with one Terminated message when an Actor is stopped" in {
|
||||||
|
|
@ -67,7 +65,6 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
|
||||||
terminal.stop()
|
terminal.stop()
|
||||||
monitor1.stop()
|
monitor1.stop()
|
||||||
monitor2.stop()
|
monitor2.stop()
|
||||||
expectNoMsg(2 seconds) //Shouldn't get more terminations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"notify with a Terminated message once when an Actor is stopped but not when restarted" in {
|
"notify with a Terminated message once when an Actor is stopped but not when restarted" in {
|
||||||
|
|
@ -79,16 +76,12 @@ class DeathWatchSpec extends AkkaSpec with BeforeAndAfterEach with ImplicitSende
|
||||||
|
|
||||||
terminal ! Kill
|
terminal ! Kill
|
||||||
terminal ! Kill
|
terminal ! Kill
|
||||||
terminal ! "foo"
|
(terminal ? "foo").as[String] must be === Some("foo")
|
||||||
|
|
||||||
expectMsg("foo") //Make sure that it's still alive
|
|
||||||
|
|
||||||
terminal ! Kill
|
terminal ! Kill
|
||||||
|
|
||||||
expectTerminationOf(terminal)
|
expectTerminationOf(terminal)
|
||||||
|
|
||||||
terminal.stop()
|
terminal.stop()
|
||||||
expectNoMsg(2 seconds) //Shouldn't get more terminations
|
|
||||||
supervisor.stop()
|
supervisor.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
package akka.actor
|
package akka.actor
|
||||||
|
|
||||||
import akka.testkit.AkkaSpec
|
import akka.testkit.AkkaSpec
|
||||||
|
import akka.util.duration._
|
||||||
import DeploymentConfig._
|
import DeploymentConfig._
|
||||||
|
|
||||||
class DeployerSpec extends AkkaSpec {
|
class DeployerSpec extends AkkaSpec {
|
||||||
|
|
@ -18,9 +19,9 @@ class DeployerSpec extends AkkaSpec {
|
||||||
Deploy(
|
Deploy(
|
||||||
"service-ping",
|
"service-ping",
|
||||||
None,
|
None,
|
||||||
LeastCPU,
|
RoundRobin,
|
||||||
NrOfInstances(3),
|
NrOfInstances(3),
|
||||||
BannagePeriodFailureDetector(10),
|
BannagePeriodFailureDetector(10 seconds),
|
||||||
app.deployer.deploymentConfig.RemoteScope(List(
|
app.deployer.deploymentConfig.RemoteScope(List(
|
||||||
RemoteAddress("wallace", 2552), RemoteAddress("gromit", 2552))))))
|
RemoteAddress("wallace", 2552), RemoteAddress("gromit", 2552))))))
|
||||||
// ClusterScope(
|
// ClusterScope(
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll {
|
||||||
case Ping ⇒ countDownLatch.countDown()
|
case Ping ⇒ countDownLatch.countDown()
|
||||||
case Crash ⇒ throw new Exception("Crashing...")
|
case Crash ⇒ throw new Exception("Crashing...")
|
||||||
}
|
}
|
||||||
|
|
||||||
override def postRestart(reason: Throwable) = {
|
override def postRestart(reason: Throwable) = {
|
||||||
if (!restartLatch.isOpen)
|
if (!restartLatch.isOpen)
|
||||||
restartLatch.open
|
restartLatch.open
|
||||||
|
|
@ -60,17 +61,17 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll {
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
|
|
||||||
// test restart and post restart ping
|
// test restart and post restart ping
|
||||||
assert(restartLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(restartLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
// now crash again... should not restart
|
// now crash again... should not restart
|
||||||
slave ! Crash
|
slave ! Crash
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
|
|
||||||
assert(secondRestartLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(secondRestartLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
assert(countDownLatch.await(1, TimeUnit.SECONDS))
|
assert(countDownLatch.await(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
slave ! Crash
|
slave ! Crash
|
||||||
assert(stopLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(stopLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
}
|
}
|
||||||
|
|
||||||
"ensure that slave is immortal without max restarts and time range" in {
|
"ensure that slave is immortal without max restarts and time range" in {
|
||||||
|
|
@ -133,14 +134,14 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll {
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
slave ! Crash
|
slave ! Crash
|
||||||
|
|
||||||
assert(restartLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(restartLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
assert(pingLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(pingLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
slave ! Crash
|
slave ! Crash
|
||||||
|
|
||||||
assert(secondRestartLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(secondRestartLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
assert(secondPingLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(secondPingLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
// sleep to go out of the restart strategy's time range
|
// sleep to go out of the restart strategy's time range
|
||||||
sleep(700L)
|
sleep(700L)
|
||||||
|
|
@ -187,7 +188,7 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll {
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
|
|
||||||
// test restart and post restart ping
|
// test restart and post restart ping
|
||||||
assert(restartLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(restartLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
assert(!slave.isShutdown)
|
assert(!slave.isShutdown)
|
||||||
|
|
||||||
|
|
@ -195,13 +196,13 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll {
|
||||||
slave ! Crash
|
slave ! Crash
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
|
|
||||||
assert(secondRestartLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(secondRestartLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
assert(countDownLatch.await(1, TimeUnit.SECONDS))
|
assert(countDownLatch.await(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
sleep(700L)
|
sleep(700L)
|
||||||
|
|
||||||
slave ! Crash
|
slave ! Crash
|
||||||
assert(stopLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(stopLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
sleep(500L)
|
sleep(500L)
|
||||||
assert(slave.isShutdown)
|
assert(slave.isShutdown)
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +238,7 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll {
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
|
|
||||||
// test restart and post restart ping
|
// test restart and post restart ping
|
||||||
assert(restartLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(restartLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
assert(!slave.isShutdown)
|
assert(!slave.isShutdown)
|
||||||
|
|
||||||
|
|
@ -246,14 +247,14 @@ class RestartStrategySpec extends AkkaSpec with BeforeAndAfterAll {
|
||||||
|
|
||||||
// may not be running
|
// may not be running
|
||||||
slave ! Ping
|
slave ! Ping
|
||||||
assert(countDownLatch.await(1, TimeUnit.SECONDS))
|
assert(countDownLatch.await(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
// may not be running
|
// may not be running
|
||||||
slave ! Crash
|
slave ! Crash
|
||||||
|
|
||||||
assert(stopLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(stopLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
|
|
||||||
assert(maxNoOfRestartsLatch.tryAwait(1, TimeUnit.SECONDS))
|
assert(maxNoOfRestartsLatch.tryAwait(10, TimeUnit.SECONDS))
|
||||||
sleep(500L)
|
sleep(500L)
|
||||||
assert(slave.isShutdown)
|
assert(slave.isShutdown)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ class SchedulerSpec extends AkkaSpec with BeforeAndAfterEach {
|
||||||
|
|
||||||
override def afterEach {
|
override def afterEach {
|
||||||
while (futures.peek() ne null) { Option(futures.poll()).foreach(_.cancel(true)) }
|
while (futures.peek() ne null) { Option(futures.poll()).foreach(_.cancel(true)) }
|
||||||
app.registry.local.shutdownAll
|
|
||||||
app.eventHandler.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"A Scheduler" must {
|
"A Scheduler" must {
|
||||||
|
|
@ -66,18 +64,19 @@ class SchedulerSpec extends AkkaSpec with BeforeAndAfterEach {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ticket #372
|
* ticket #372
|
||||||
|
* FIXME rewrite the test so that registry is not used
|
||||||
*/
|
*/
|
||||||
"not create actors" in {
|
// "not create actors" in {
|
||||||
object Ping
|
// object Ping
|
||||||
val ticks = new CountDownLatch(1000)
|
// val ticks = new CountDownLatch(1000)
|
||||||
val actor = createActor(new Actor {
|
// val actor = createActor(new Actor {
|
||||||
def receive = { case Ping ⇒ ticks.countDown }
|
// def receive = { case Ping ⇒ ticks.countDown }
|
||||||
})
|
// })
|
||||||
val numActors = app.registry.local.actors.length
|
// val numActors = app.registry.local.actors.length
|
||||||
(1 to 1000).foreach(_ ⇒ collectFuture(Scheduler.scheduleOnce(actor, Ping, 1, TimeUnit.MILLISECONDS)))
|
// (1 to 1000).foreach(_ ⇒ collectFuture(Scheduler.scheduleOnce(actor, Ping, 1, TimeUnit.MILLISECONDS)))
|
||||||
assert(ticks.await(10, TimeUnit.SECONDS))
|
// assert(ticks.await(10, TimeUnit.SECONDS))
|
||||||
assert(app.registry.local.actors.length === numActors)
|
// assert(app.registry.local.actors.length === numActors)
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ticket #372
|
* ticket #372
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,6 @@ class Ticket669Spec extends AkkaSpec with BeforeAndAfterAll with ImplicitSender
|
||||||
|
|
||||||
override def beforeAll = Thread.interrupted() //remove interrupted status.
|
override def beforeAll = Thread.interrupted() //remove interrupted status.
|
||||||
|
|
||||||
override def afterAll = {
|
|
||||||
app.registry.local.shutdownAll
|
|
||||||
}
|
|
||||||
|
|
||||||
"A supervised actor with lifecycle PERMANENT" should {
|
"A supervised actor with lifecycle PERMANENT" should {
|
||||||
"be able to reply on failure during preRestart" in {
|
"be able to reply on failure during preRestart" in {
|
||||||
filterEvents(EventFilter[Exception]("test")) {
|
filterEvents(EventFilter[Exception]("test")) {
|
||||||
|
|
|
||||||
|
|
@ -408,17 +408,18 @@ abstract class ActorModelSpec extends AkkaSpec {
|
||||||
} catch {
|
} catch {
|
||||||
case e ⇒
|
case e ⇒
|
||||||
|
|
||||||
app.registry.local.foreach {
|
// FIXME: registry has been removed
|
||||||
case actor: LocalActorRef ⇒
|
// app.registry.local.foreach {
|
||||||
val cell = actor.underlying
|
// case actor: LocalActorRef ⇒
|
||||||
val mbox = cell.mailbox
|
// val cell = actor.underlying
|
||||||
System.err.println("Left in the registry: " + actor.address + " => " + cell + " => " + mbox.hasMessages + " " + mbox.hasSystemMessages + " " + mbox.numberOfMessages + " " + mbox.isScheduled)
|
// val mbox = cell.mailbox
|
||||||
var message = mbox.dequeue()
|
// System.err.println("Left in the registry: " + actor.address + " => " + cell + " => " + mbox.hasMessages + " " + mbox.hasSystemMessages + " " + mbox.numberOfMessages + " " + mbox.isScheduled)
|
||||||
while (message ne null) {
|
// var message = mbox.dequeue()
|
||||||
System.err.println("Lingering message for " + cell + " " + message)
|
// while (message ne null) {
|
||||||
message = mbox.dequeue()
|
// System.err.println("Lingering message for " + cell + " " + message)
|
||||||
}
|
// message = mbox.dequeue()
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import java.util.concurrent.{ TimeUnit, CountDownLatch, BlockingQueue }
|
||||||
import java.util.{ Queue }
|
import java.util.{ Queue }
|
||||||
import akka.util._
|
import akka.util._
|
||||||
import akka.util.Duration._
|
import akka.util.Duration._
|
||||||
import akka.actor.{ LocalActorRef, Actor, ActorRegistry, NullChannel }
|
import akka.actor.{ LocalActorRef, Actor, NullChannel }
|
||||||
import akka.testkit.AkkaSpec
|
import akka.testkit.AkkaSpec
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
@RunWith(classOf[JUnitRunner])
|
||||||
|
|
|
||||||
223
akka-actor-tests/src/test/scala/akka/event/EventBusSpec.scala
Normal file
223
akka-actor-tests/src/test/scala/akka/event/EventBusSpec.scala
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2011 Scalable Solutions AB <http://scalablesolutions.se>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.event
|
||||||
|
|
||||||
|
import org.scalatest.BeforeAndAfterEach
|
||||||
|
import akka.testkit._
|
||||||
|
import akka.util.duration._
|
||||||
|
import java.util.concurrent.atomic._
|
||||||
|
import akka.actor.{ Props, Actor, ActorRef }
|
||||||
|
import java.util.Comparator
|
||||||
|
import akka.japi.{ Procedure, Function }
|
||||||
|
|
||||||
|
object EventBusSpec {
|
||||||
|
class TestActorWrapperActor(testActor: ActorRef) extends Actor {
|
||||||
|
def receive = {
|
||||||
|
case x ⇒ testActor forward x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class EventBusSpec(busName: String) extends AkkaSpec with BeforeAndAfterEach {
|
||||||
|
import EventBusSpec._
|
||||||
|
type BusType <: EventBus
|
||||||
|
|
||||||
|
def createNewEventBus(): BusType
|
||||||
|
|
||||||
|
def createEvents(numberOfEvents: Int): Iterable[BusType#Event]
|
||||||
|
|
||||||
|
def createSubscriber(pipeTo: ActorRef): BusType#Subscriber
|
||||||
|
|
||||||
|
def classifierFor(event: BusType#Event): BusType#Classifier
|
||||||
|
|
||||||
|
def disposeSubscriber(subscriber: BusType#Subscriber): Unit
|
||||||
|
|
||||||
|
busName must {
|
||||||
|
|
||||||
|
def createNewSubscriber() = createSubscriber(testActor).asInstanceOf[bus.Subscriber]
|
||||||
|
def getClassifierFor(event: BusType#Event) = classifierFor(event).asInstanceOf[bus.Classifier]
|
||||||
|
def createNewEvents(numberOfEvents: Int): Iterable[bus.Event] = createEvents(numberOfEvents).asInstanceOf[Iterable[bus.Event]]
|
||||||
|
|
||||||
|
val bus = createNewEventBus()
|
||||||
|
val events = createNewEvents(100)
|
||||||
|
val event = events.head
|
||||||
|
val classifier = getClassifierFor(event)
|
||||||
|
val subscriber = createNewSubscriber()
|
||||||
|
|
||||||
|
"allow subscribers" in {
|
||||||
|
bus.subscribe(subscriber, classifier) must be === true
|
||||||
|
}
|
||||||
|
|
||||||
|
"allow to unsubscribe already existing subscriber" in {
|
||||||
|
bus.unsubscribe(subscriber, classifier) must be === true
|
||||||
|
}
|
||||||
|
|
||||||
|
"not allow to unsubscribe non-existing subscriber" in {
|
||||||
|
val sub = createNewSubscriber()
|
||||||
|
bus.unsubscribe(sub, classifier) must be === false
|
||||||
|
disposeSubscriber(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
"not allow for the same subscriber to subscribe to the same channel twice" in {
|
||||||
|
bus.subscribe(subscriber, classifier) must be === true
|
||||||
|
bus.subscribe(subscriber, classifier) must be === false
|
||||||
|
bus.unsubscribe(subscriber, classifier) must be === true
|
||||||
|
}
|
||||||
|
|
||||||
|
"not allow for the same subscriber to unsubscribe to the same channel twice" in {
|
||||||
|
bus.subscribe(subscriber, classifier) must be === true
|
||||||
|
bus.unsubscribe(subscriber, classifier) must be === true
|
||||||
|
bus.unsubscribe(subscriber, classifier) must be === false
|
||||||
|
}
|
||||||
|
|
||||||
|
"allow to add multiple subscribers" in {
|
||||||
|
val subscribers = (1 to 10) map { _ ⇒ createNewSubscriber() }
|
||||||
|
val events = createEvents(10)
|
||||||
|
val classifiers = events map getClassifierFor
|
||||||
|
subscribers.zip(classifiers) forall { case (s, c) ⇒ bus.subscribe(s, c) } must be === true
|
||||||
|
subscribers.zip(classifiers) forall { case (s, c) ⇒ bus.unsubscribe(s, c) } must be === true
|
||||||
|
|
||||||
|
subscribers foreach disposeSubscriber
|
||||||
|
}
|
||||||
|
|
||||||
|
"publishing events without any subscribers shouldn't be a problem" in {
|
||||||
|
bus.publish(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
"publish the given event to the only subscriber" in {
|
||||||
|
bus.subscribe(subscriber, classifier)
|
||||||
|
bus.publish(event)
|
||||||
|
expectMsg(event)
|
||||||
|
expectNoMsg(1 second)
|
||||||
|
bus.unsubscribe(subscriber, classifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
"publish to the only subscriber multiple times" in {
|
||||||
|
bus.subscribe(subscriber, classifier)
|
||||||
|
bus.publish(event)
|
||||||
|
bus.publish(event)
|
||||||
|
bus.publish(event)
|
||||||
|
expectMsg(event)
|
||||||
|
expectMsg(event)
|
||||||
|
expectMsg(event)
|
||||||
|
expectNoMsg(1 second)
|
||||||
|
bus.unsubscribe(subscriber, classifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
"publish the given event to all intended subscribers" in {
|
||||||
|
val range = 0 until 10
|
||||||
|
val subscribers = range map (_ ⇒ createNewSubscriber())
|
||||||
|
subscribers foreach { s ⇒ bus.subscribe(s, classifier) must be === true }
|
||||||
|
bus.publish(event)
|
||||||
|
range foreach { _ ⇒ expectMsg(event) }
|
||||||
|
subscribers foreach { s ⇒ bus.unsubscribe(s, classifier) must be === true; disposeSubscriber(s) }
|
||||||
|
}
|
||||||
|
|
||||||
|
"not publish the given event to any other subscribers than the intended ones" in {
|
||||||
|
val otherSubscriber = createNewSubscriber()
|
||||||
|
val otherClassifier = getClassifierFor(events.drop(1).head)
|
||||||
|
bus.subscribe(subscriber, classifier)
|
||||||
|
bus.subscribe(otherSubscriber, otherClassifier)
|
||||||
|
bus.publish(event)
|
||||||
|
expectMsg(event)
|
||||||
|
bus.unsubscribe(subscriber, classifier)
|
||||||
|
bus.unsubscribe(otherSubscriber, otherClassifier)
|
||||||
|
expectNoMsg(1 second)
|
||||||
|
}
|
||||||
|
|
||||||
|
"not publish the given event to a former subscriber" in {
|
||||||
|
bus.subscribe(subscriber, classifier)
|
||||||
|
bus.unsubscribe(subscriber, classifier)
|
||||||
|
bus.publish(event)
|
||||||
|
expectNoMsg(1 second)
|
||||||
|
}
|
||||||
|
|
||||||
|
"cleanup subscriber" in {
|
||||||
|
disposeSubscriber(subscriber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ActorEventBusSpec {
|
||||||
|
class ComposedActorEventBus extends ActorEventBus with LookupClassification {
|
||||||
|
type Event = Int
|
||||||
|
type Classifier = String
|
||||||
|
|
||||||
|
def classify(event: Event) = event.toString
|
||||||
|
protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = a compareTo b
|
||||||
|
protected def mapSize = 32
|
||||||
|
def publish(event: Event, subscriber: Subscriber) = subscriber ! event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActorEventBusSpec extends EventBusSpec("ActorEventBus") {
|
||||||
|
import akka.event.ActorEventBusSpec._
|
||||||
|
import EventBusSpec.TestActorWrapperActor
|
||||||
|
|
||||||
|
type BusType = ComposedActorEventBus
|
||||||
|
def createNewEventBus(): BusType = new ComposedActorEventBus
|
||||||
|
|
||||||
|
def createEvents(numberOfEvents: Int) = (0 until numberOfEvents)
|
||||||
|
|
||||||
|
def createSubscriber(pipeTo: ActorRef) = createActor(Props(new TestActorWrapperActor(pipeTo)))
|
||||||
|
|
||||||
|
def classifierFor(event: BusType#Event) = event.toString
|
||||||
|
|
||||||
|
def disposeSubscriber(subscriber: BusType#Subscriber): Unit = subscriber.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
object ScanningEventBusSpec {
|
||||||
|
import akka.event.japi.ScanningEventBus
|
||||||
|
|
||||||
|
class MyScanningEventBus extends ScanningEventBus[Int, akka.japi.Procedure[Int], String] {
|
||||||
|
protected def compareClassifiers(a: Classifier, b: Classifier): Int = a compareTo b
|
||||||
|
protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = System.identityHashCode(a) - System.identityHashCode(b)
|
||||||
|
|
||||||
|
protected def matches(classifier: Classifier, event: Event): Boolean = event.toString == classifier
|
||||||
|
|
||||||
|
protected def publish(event: Event, subscriber: Subscriber): Unit = subscriber(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanningEventBusSpec extends EventBusSpec("ScanningEventBus") {
|
||||||
|
import ScanningEventBusSpec._
|
||||||
|
|
||||||
|
type BusType = MyScanningEventBus
|
||||||
|
|
||||||
|
def createNewEventBus(): BusType = new MyScanningEventBus
|
||||||
|
|
||||||
|
def createEvents(numberOfEvents: Int) = (0 until numberOfEvents)
|
||||||
|
|
||||||
|
def createSubscriber(pipeTo: ActorRef) = new Procedure[Int] { def apply(i: Int) = pipeTo ! i }
|
||||||
|
|
||||||
|
def classifierFor(event: BusType#Event) = event.toString
|
||||||
|
|
||||||
|
def disposeSubscriber(subscriber: BusType#Subscriber): Unit = ()
|
||||||
|
}
|
||||||
|
|
||||||
|
object LookupEventBusSpec {
|
||||||
|
class MyLookupEventBus extends akka.event.japi.LookupEventBus[Int, akka.japi.Procedure[Int], String] {
|
||||||
|
protected def classify(event: Event): Classifier = event.toString
|
||||||
|
protected def compareSubscribers(a: Subscriber, b: Subscriber): Int = System.identityHashCode(a) - System.identityHashCode(b)
|
||||||
|
protected def mapSize = 32
|
||||||
|
protected def publish(event: Event, subscriber: Subscriber): Unit = subscriber(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LookupEventBusSpec extends EventBusSpec("LookupEventBus") {
|
||||||
|
import LookupEventBusSpec._
|
||||||
|
|
||||||
|
type BusType = MyLookupEventBus
|
||||||
|
|
||||||
|
def createNewEventBus(): BusType = new MyLookupEventBus
|
||||||
|
|
||||||
|
def createEvents(numberOfEvents: Int) = (0 until numberOfEvents)
|
||||||
|
|
||||||
|
def createSubscriber(pipeTo: ActorRef) = new Procedure[Int] { def apply(i: Int) = pipeTo ! i }
|
||||||
|
|
||||||
|
def classifierFor(event: BusType#Event) = event.toString
|
||||||
|
|
||||||
|
def disposeSubscriber(subscriber: BusType#Subscriber): Unit = ()
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ abstract class AkkaPerformanceTest(val app: AkkaApplication) extends BenchmarkSc
|
||||||
val start = System.nanoTime
|
val start = System.nanoTime
|
||||||
val clients = (for (i ← 0 until numberOfClients) yield {
|
val clients = (for (i ← 0 until numberOfClients) yield {
|
||||||
val receiver = receivers(i % receivers.size)
|
val receiver = receivers(i % receivers.size)
|
||||||
Props(new Client(receiver, orders, latch, repeatsPerClient + (if (i < oddRepeats) 1 else 0), delayMs)).withDispatcher(clientDispatcher)
|
Props(new Client(receiver, orders, latch, repeatsPerClient + (if (i < oddRepeats) 1 else 0), sampling, delayMs)).withDispatcher(clientDispatcher)
|
||||||
}).toList.map(app.createActor(_))
|
}).toList.map(app.createActor(_))
|
||||||
|
|
||||||
clients.foreach(_ ! "run")
|
clients.foreach(_ ! "run")
|
||||||
|
|
@ -46,28 +46,35 @@ abstract class AkkaPerformanceTest(val app: AkkaApplication) extends BenchmarkSc
|
||||||
clients.foreach(_ ! PoisonPill)
|
clients.foreach(_ ! PoisonPill)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Client(orderReceiver: ActorRef, orders: List[Order], latch: CountDownLatch, repeat: Int, delayMs: Int) extends Actor {
|
class Client(
|
||||||
def this(orderReceiver: ActorRef, orders: List[Order], latch: CountDownLatch, repeat: Int) {
|
orderReceiver: ActorRef,
|
||||||
this(orderReceiver, orders, latch, repeat, 0)
|
orders: List[Order],
|
||||||
}
|
latch: CountDownLatch,
|
||||||
|
repeat: Int,
|
||||||
|
sampling: Int,
|
||||||
|
delayMs: Int = 0) extends Actor {
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case "run" ⇒
|
case "run" ⇒
|
||||||
(1 to repeat).foreach(i ⇒
|
var n = 0
|
||||||
{
|
for (r ← 1 to repeat; o ← orders) {
|
||||||
for (o ← orders) {
|
n += 1
|
||||||
|
val rsp =
|
||||||
|
if (n % sampling == 0) {
|
||||||
val t0 = System.nanoTime
|
val t0 = System.nanoTime
|
||||||
val rsp = placeOrder(orderReceiver, o)
|
val rsp = placeOrder(orderReceiver, o)
|
||||||
val duration = System.nanoTime - t0
|
val duration = System.nanoTime - t0
|
||||||
stat.addValue(duration)
|
stat.addValue(duration)
|
||||||
if (!rsp.status) {
|
rsp
|
||||||
app.eventHandler.error(this, "Invalid rsp")
|
} else {
|
||||||
}
|
placeOrder(orderReceiver, o)
|
||||||
delay(delayMs)
|
|
||||||
}
|
}
|
||||||
})
|
if (!rsp.status) {
|
||||||
|
app.eventHandler.error(this, "Invalid rsp")
|
||||||
|
}
|
||||||
|
delay(delayMs)
|
||||||
|
}
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,10 @@ trait PerformanceTest extends JUnitSuite {
|
||||||
System.getProperty("benchmark.timeDilation", "1").toLong
|
System.getProperty("benchmark.timeDilation", "1").toLong
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def sampling = {
|
||||||
|
System.getProperty("benchmark.sampling", "100").toInt
|
||||||
|
}
|
||||||
|
|
||||||
var stat: DescriptiveStatistics = _
|
var stat: DescriptiveStatistics = _
|
||||||
|
|
||||||
val resultRepository = BenchResultRepository(app)
|
val resultRepository = BenchResultRepository(app)
|
||||||
|
|
@ -113,16 +117,18 @@ trait PerformanceTest extends JUnitSuite {
|
||||||
75 -> (stat.getPercentile(75.0) / 1000).toLong,
|
75 -> (stat.getPercentile(75.0) / 1000).toLong,
|
||||||
95 -> (stat.getPercentile(95.0) / 1000).toLong)
|
95 -> (stat.getPercentile(95.0) / 1000).toLong)
|
||||||
|
|
||||||
|
val n = stat.getN * sampling
|
||||||
|
|
||||||
val stats = Stats(
|
val stats = Stats(
|
||||||
name,
|
name,
|
||||||
load = numberOfClients,
|
load = numberOfClients,
|
||||||
timestamp = TestStart.startTime,
|
timestamp = TestStart.startTime,
|
||||||
durationNanos = durationNs,
|
durationNanos = durationNs,
|
||||||
n = stat.getN,
|
n = n,
|
||||||
min = (stat.getMin / 1000).toLong,
|
min = (stat.getMin / 1000).toLong,
|
||||||
max = (stat.getMax / 1000).toLong,
|
max = (stat.getMax / 1000).toLong,
|
||||||
mean = (stat.getMean / 1000).toLong,
|
mean = (stat.getMean / 1000).toLong,
|
||||||
tps = (stat.getN.toDouble / durationS),
|
tps = (n.toDouble / durationS),
|
||||||
percentiles)
|
percentiles)
|
||||||
|
|
||||||
resultRepository.add(stats)
|
resultRepository.add(stats)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec {
|
||||||
None,
|
None,
|
||||||
RoundRobin,
|
RoundRobin,
|
||||||
NrOfInstances(5),
|
NrOfInstances(5),
|
||||||
RemoveConnectionOnFirstFailureLocalFailureDetector,
|
NoOpFailureDetector,
|
||||||
LocalScope))
|
LocalScope))
|
||||||
|
|
||||||
val helloLatch = new CountDownLatch(5)
|
val helloLatch = new CountDownLatch(5)
|
||||||
|
|
@ -57,7 +57,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec {
|
||||||
None,
|
None,
|
||||||
RoundRobin,
|
RoundRobin,
|
||||||
NrOfInstances(10),
|
NrOfInstances(10),
|
||||||
RemoveConnectionOnFirstFailureLocalFailureDetector,
|
NoOpFailureDetector,
|
||||||
LocalScope))
|
LocalScope))
|
||||||
|
|
||||||
val connectionCount = 10
|
val connectionCount = 10
|
||||||
|
|
@ -102,7 +102,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec {
|
||||||
None,
|
None,
|
||||||
RoundRobin,
|
RoundRobin,
|
||||||
NrOfInstances(5),
|
NrOfInstances(5),
|
||||||
RemoveConnectionOnFirstFailureLocalFailureDetector,
|
NoOpFailureDetector,
|
||||||
LocalScope))
|
LocalScope))
|
||||||
|
|
||||||
val helloLatch = new CountDownLatch(5)
|
val helloLatch = new CountDownLatch(5)
|
||||||
|
|
@ -137,7 +137,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec {
|
||||||
None,
|
None,
|
||||||
Random,
|
Random,
|
||||||
NrOfInstances(7),
|
NrOfInstances(7),
|
||||||
RemoveConnectionOnFirstFailureLocalFailureDetector,
|
NoOpFailureDetector,
|
||||||
LocalScope))
|
LocalScope))
|
||||||
|
|
||||||
val stopLatch = new CountDownLatch(7)
|
val stopLatch = new CountDownLatch(7)
|
||||||
|
|
@ -171,7 +171,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec {
|
||||||
None,
|
None,
|
||||||
Random,
|
Random,
|
||||||
NrOfInstances(10),
|
NrOfInstances(10),
|
||||||
RemoveConnectionOnFirstFailureLocalFailureDetector,
|
NoOpFailureDetector,
|
||||||
LocalScope))
|
LocalScope))
|
||||||
|
|
||||||
val connectionCount = 10
|
val connectionCount = 10
|
||||||
|
|
@ -216,7 +216,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec {
|
||||||
None,
|
None,
|
||||||
Random,
|
Random,
|
||||||
NrOfInstances(6),
|
NrOfInstances(6),
|
||||||
RemoveConnectionOnFirstFailureLocalFailureDetector,
|
NoOpFailureDetector,
|
||||||
LocalScope))
|
LocalScope))
|
||||||
|
|
||||||
val helloLatch = new CountDownLatch(6)
|
val helloLatch = new CountDownLatch(6)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package akka.routing
|
package akka.routing
|
||||||
|
|
||||||
import akka.routing._
|
import akka.routing._
|
||||||
|
import akka.config.ConfigurationException
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import akka.actor.{ ActorRef, Actor }
|
import akka.actor.{ ActorRef, Actor }
|
||||||
import collection.mutable.LinkedList
|
import collection.mutable.LinkedList
|
||||||
import akka.routing.Routing.Broadcast
|
import akka.routing.Routing.Broadcast
|
||||||
import java.util.concurrent.{ CountDownLatch, TimeUnit }
|
import java.util.concurrent.{ CountDownLatch, TimeUnit }
|
||||||
import akka.testkit.AkkaSpec
|
import akka.testkit._
|
||||||
|
|
||||||
object RoutingSpec {
|
object RoutingSpec {
|
||||||
|
|
||||||
|
|
@ -26,18 +27,18 @@ class RoutingSpec extends AkkaSpec {
|
||||||
"be started when constructed" in {
|
"be started when constructed" in {
|
||||||
val actor1 = createActor[TestActor]
|
val actor1 = createActor[TestActor]
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new DirectRouter, List(actor1))
|
val props = RoutedProps().withDirectRouter.withLocalConnections(List(actor1))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
actor.isShutdown must be(false)
|
actor.isShutdown must be(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"throw IllegalArgumentException at construction when no connections" in {
|
"throw ConfigurationException at construction when no connections" in {
|
||||||
try {
|
try {
|
||||||
val props = RoutedProps(() ⇒ new DirectRouter, List())
|
val props = RoutedProps().withDirectRouter
|
||||||
app.routing.actorOf(props, "foo")
|
app.createActor(props, "foo")
|
||||||
fail()
|
fail()
|
||||||
} catch {
|
} catch {
|
||||||
case e: IllegalArgumentException ⇒
|
case e: ConfigurationException ⇒
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,8 +53,8 @@ class RoutingSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new DirectRouter, List(connection1))
|
val props = RoutedProps().withDirectRouter.withLocalConnections(List(connection1))
|
||||||
val routedActor = app.routing.actorOf(props, "foo")
|
val routedActor = app.createActor(props, "foo")
|
||||||
routedActor ! "hello"
|
routedActor ! "hello"
|
||||||
routedActor ! "end"
|
routedActor ! "end"
|
||||||
|
|
||||||
|
|
@ -73,8 +74,8 @@ class RoutingSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new DirectRouter, List(connection1))
|
val props = RoutedProps().withDirectRouter.withLocalConnections(List(connection1))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
actor ! Broadcast(1)
|
actor ! Broadcast(1)
|
||||||
actor ! "end"
|
actor ! "end"
|
||||||
|
|
@ -90,18 +91,18 @@ class RoutingSpec extends AkkaSpec {
|
||||||
"be started when constructed" in {
|
"be started when constructed" in {
|
||||||
val actor1 = createActor[TestActor]
|
val actor1 = createActor[TestActor]
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new RoundRobinRouter, List(actor1))
|
val props = RoutedProps().withRoundRobinRouter.withLocalConnections(List(actor1))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
actor.isShutdown must be(false)
|
actor.isShutdown must be(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"throw IllegalArgumentException at construction when no connections" in {
|
"throw ConfigurationException at construction when no connections" in {
|
||||||
try {
|
try {
|
||||||
val props = RoutedProps(() ⇒ new RoundRobinRouter, List())
|
val props = RoutedProps().withRoundRobinRouter
|
||||||
app.routing.actorOf(props, "foo")
|
app.createActor(props, "foo")
|
||||||
fail()
|
fail()
|
||||||
} catch {
|
} catch {
|
||||||
case e: IllegalArgumentException ⇒
|
case e: ConfigurationException ⇒
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,8 +131,8 @@ class RoutingSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
//create the routed actor.
|
//create the routed actor.
|
||||||
val props = RoutedProps(() ⇒ new RoundRobinRouter, connections)
|
val props = RoutedProps().withRoundRobinRouter.withLocalConnections(connections)
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
//send messages to the actor.
|
//send messages to the actor.
|
||||||
for (i ← 0 until iterationCount) {
|
for (i ← 0 until iterationCount) {
|
||||||
|
|
@ -169,8 +170,8 @@ class RoutingSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new RoundRobinRouter, List(connection1, connection2))
|
val props = RoutedProps().withRoundRobinRouter.withLocalConnections(List(connection1, connection2))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
actor ! Broadcast(1)
|
actor ! Broadcast(1)
|
||||||
actor ! Broadcast("end")
|
actor ! Broadcast("end")
|
||||||
|
|
@ -192,8 +193,8 @@ class RoutingSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new RoundRobinRouter, List(connection1))
|
val props = RoutedProps().withRoundRobinRouter.withLocalConnections(List(connection1))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
actor ? Broadcast(1)
|
actor ? Broadcast(1)
|
||||||
|
|
@ -214,18 +215,18 @@ class RoutingSpec extends AkkaSpec {
|
||||||
|
|
||||||
val actor1 = createActor[TestActor]
|
val actor1 = createActor[TestActor]
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new RandomRouter, List(actor1))
|
val props = RoutedProps().withRandomRouter.withLocalConnections(List(actor1))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
actor.isShutdown must be(false)
|
actor.isShutdown must be(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"throw IllegalArgumentException at construction when no connections" in {
|
"throw ConfigurationException at construction when no connections" in {
|
||||||
try {
|
try {
|
||||||
val props = RoutedProps(() ⇒ new RandomRouter, List())
|
val props = RoutedProps().withRandomRouter
|
||||||
app.routing.actorOf(props, "foo")
|
app.createActor(props, "foo")
|
||||||
fail()
|
fail()
|
||||||
} catch {
|
} catch {
|
||||||
case e: IllegalArgumentException ⇒
|
case e: ConfigurationException ⇒
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,8 +253,8 @@ class RoutingSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new RandomRouter, List(connection1, connection2))
|
val props = RoutedProps().withRandomRouter.withLocalConnections(List(connection1, connection2))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
actor ! Broadcast(1)
|
actor ! Broadcast(1)
|
||||||
actor ! Broadcast("end")
|
actor ! Broadcast("end")
|
||||||
|
|
@ -275,8 +276,8 @@ class RoutingSpec extends AkkaSpec {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val props = RoutedProps(() ⇒ new RandomRouter, List(connection1))
|
val props = RoutedProps().withRandomRouter.withLocalConnections(List(connection1))
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
actor ? Broadcast(1)
|
actor ? Broadcast(1)
|
||||||
|
|
@ -290,4 +291,179 @@ class RoutingSpec extends AkkaSpec {
|
||||||
counter1.get must be(0)
|
counter1.get must be(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Scatter-gather router" must {
|
||||||
|
|
||||||
|
"return response, even if one of the connections has stopped" in {
|
||||||
|
|
||||||
|
val shutdownLatch = new TestLatch(1)
|
||||||
|
|
||||||
|
val props = RoutedProps()
|
||||||
|
.withLocalConnections(List(newActor(0, Some(shutdownLatch)), newActor(1, Some(shutdownLatch))))
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
|
||||||
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
|
actor ! Broadcast(Stop(Some(0)))
|
||||||
|
|
||||||
|
shutdownLatch.await
|
||||||
|
|
||||||
|
(actor ? Broadcast(0)).get.asInstanceOf[Int] must be(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
"throw an exception, if all the connections have stopped" in {
|
||||||
|
|
||||||
|
val shutdownLatch = new TestLatch(2)
|
||||||
|
|
||||||
|
val props = RoutedProps()
|
||||||
|
.withLocalConnections(List(newActor(0, Some(shutdownLatch)), newActor(1, Some(shutdownLatch))))
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
|
||||||
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
|
actor ! Broadcast(Stop())
|
||||||
|
|
||||||
|
shutdownLatch.await
|
||||||
|
|
||||||
|
(intercept[RoutingException] {
|
||||||
|
actor ? Broadcast(0)
|
||||||
|
}) must not be (null)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"return the first response from connections, when all of them replied" in {
|
||||||
|
|
||||||
|
val props = RoutedProps()
|
||||||
|
.withLocalConnections(List(newActor(0), newActor(1)))
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
|
||||||
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
|
(actor ? Broadcast("Hi!")).get.asInstanceOf[Int] must be(0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"return the first response from connections, when some of them failed to reply" in {
|
||||||
|
val props = RoutedProps()
|
||||||
|
.withLocalConnections(List(newActor(0), newActor(1)))
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
|
||||||
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
|
(actor ? Broadcast(0)).get.asInstanceOf[Int] must be(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
"be started when constructed" in {
|
||||||
|
val props = RoutedProps()
|
||||||
|
.withLocalConnections(List(newActor(0)))
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
|
actor.isShutdown must be(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"throw ConfigurationException at construction when no connections" in {
|
||||||
|
val props = RoutedProps()
|
||||||
|
.withLocalConnections(List())
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
|
||||||
|
try {
|
||||||
|
app.createActor(props, "foo")
|
||||||
|
fail()
|
||||||
|
} catch {
|
||||||
|
case e: ConfigurationException ⇒
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"deliver one-way messages in a round robin fashion" in {
|
||||||
|
val connectionCount = 10
|
||||||
|
val iterationCount = 10
|
||||||
|
val doneLatch = new TestLatch(connectionCount)
|
||||||
|
|
||||||
|
var connections = new LinkedList[ActorRef]
|
||||||
|
var counters = new LinkedList[AtomicInteger]
|
||||||
|
for (i ← 0 until connectionCount) {
|
||||||
|
counters = counters :+ new AtomicInteger()
|
||||||
|
|
||||||
|
val connection = app.createActor(new Actor {
|
||||||
|
def receive = {
|
||||||
|
case "end" ⇒ doneLatch.countDown()
|
||||||
|
case msg: Int ⇒ counters.get(i).get.addAndGet(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
connections = connections :+ connection
|
||||||
|
}
|
||||||
|
|
||||||
|
val props = RoutedProps()
|
||||||
|
.withLocalConnections(connections)
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
|
||||||
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
|
for (i ← 0 until iterationCount) {
|
||||||
|
for (k ← 0 until connectionCount) {
|
||||||
|
actor ! (k + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actor ! Broadcast("end")
|
||||||
|
|
||||||
|
doneLatch.await
|
||||||
|
|
||||||
|
for (i ← 0 until connectionCount) {
|
||||||
|
val counter = counters.get(i).get
|
||||||
|
counter.get must be((iterationCount * (i + 1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"deliver a broadcast message using the !" in {
|
||||||
|
val doneLatch = new TestLatch(2)
|
||||||
|
|
||||||
|
val counter1 = new AtomicInteger
|
||||||
|
val connection1 = app.createActor(new Actor {
|
||||||
|
def receive = {
|
||||||
|
case "end" ⇒ doneLatch.countDown()
|
||||||
|
case msg: Int ⇒ counter1.addAndGet(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val counter2 = new AtomicInteger
|
||||||
|
val connection2 = app.createActor(new Actor {
|
||||||
|
def receive = {
|
||||||
|
case "end" ⇒ doneLatch.countDown()
|
||||||
|
case msg: Int ⇒ counter2.addAndGet(msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val props = RoutedProps.apply()
|
||||||
|
.withLocalConnections(List(connection1, connection2))
|
||||||
|
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
||||||
|
|
||||||
|
val actor = app.createActor(props, "foo")
|
||||||
|
|
||||||
|
actor ! Broadcast(1)
|
||||||
|
actor ! Broadcast("end")
|
||||||
|
|
||||||
|
doneLatch.await
|
||||||
|
|
||||||
|
counter1.get must be(1)
|
||||||
|
counter2.get must be(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Stop(id: Option[Int] = None)
|
||||||
|
|
||||||
|
def newActor(id: Int, shudownLatch: Option[TestLatch] = None) = app.createActor(new Actor {
|
||||||
|
def receive = {
|
||||||
|
case Stop(None) ⇒ self.stop()
|
||||||
|
case Stop(Some(_id)) if (_id == id) ⇒ self.stop()
|
||||||
|
case _id: Int if (_id == id) ⇒
|
||||||
|
case _ ⇒ Thread sleep 100 * id; tryReply(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def postStop = {
|
||||||
|
shudownLatch foreach (_.countDown())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
package akka.ticket
|
|
||||||
|
|
||||||
import akka.routing._
|
|
||||||
import akka.actor.{ ActorRef, Actor }
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import collection.mutable.LinkedList
|
|
||||||
import akka.routing.Routing.Broadcast
|
|
||||||
import akka.testkit._
|
|
||||||
|
|
||||||
class Ticket1111Spec extends AkkaSpec {
|
|
||||||
|
|
||||||
"Scatter-gather router" must {
|
|
||||||
|
|
||||||
"return response, even if one of the connections has stopped" in {
|
|
||||||
|
|
||||||
val shutdownLatch = new TestLatch(1)
|
|
||||||
|
|
||||||
val props = RoutedProps()
|
|
||||||
.withConnections(List(newActor(0, Some(shutdownLatch)), newActor(1, Some(shutdownLatch))))
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
|
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
|
||||||
|
|
||||||
actor ! Broadcast(Stop(Some(0)))
|
|
||||||
|
|
||||||
shutdownLatch.await
|
|
||||||
|
|
||||||
(actor ? Broadcast(0)).get.asInstanceOf[Int] must be(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
"throw an exception, if all the connections have stopped" in {
|
|
||||||
|
|
||||||
val shutdownLatch = new TestLatch(2)
|
|
||||||
|
|
||||||
val props = RoutedProps()
|
|
||||||
.withConnections(List(newActor(0, Some(shutdownLatch)), newActor(1, Some(shutdownLatch))))
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
|
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
|
||||||
|
|
||||||
actor ! Broadcast(Stop())
|
|
||||||
|
|
||||||
shutdownLatch.await
|
|
||||||
|
|
||||||
(intercept[RoutingException] {
|
|
||||||
actor ? Broadcast(0)
|
|
||||||
}) must not be (null)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
"return the first response from connections, when all of them replied" in {
|
|
||||||
|
|
||||||
val props = RoutedProps()
|
|
||||||
.withConnections(List(newActor(0), newActor(1)))
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
|
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
|
||||||
|
|
||||||
(actor ? Broadcast("Hi!")).get.asInstanceOf[Int] must be(0)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
"return the first response from connections, when some of them failed to reply" in {
|
|
||||||
val props = RoutedProps()
|
|
||||||
.withConnections(List(newActor(0), newActor(1)))
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
|
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
|
||||||
|
|
||||||
(actor ? Broadcast(0)).get.asInstanceOf[Int] must be(1)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
"be started when constructed" in {
|
|
||||||
val props = RoutedProps()
|
|
||||||
.withConnections(List(newActor(0)))
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
|
||||||
|
|
||||||
actor.isShutdown must be(false)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
"throw IllegalArgumentException at construction when no connections" in {
|
|
||||||
val props = RoutedProps()
|
|
||||||
.withConnections(List())
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
|
|
||||||
try {
|
|
||||||
app.routing.actorOf(props, "foo")
|
|
||||||
fail()
|
|
||||||
} catch {
|
|
||||||
case e: IllegalArgumentException ⇒
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"deliver one-way messages in a round robin fashion" in {
|
|
||||||
val connectionCount = 10
|
|
||||||
val iterationCount = 10
|
|
||||||
val doneLatch = new TestLatch(connectionCount)
|
|
||||||
|
|
||||||
var connections = new LinkedList[ActorRef]
|
|
||||||
var counters = new LinkedList[AtomicInteger]
|
|
||||||
for (i ← 0 until connectionCount) {
|
|
||||||
counters = counters :+ new AtomicInteger()
|
|
||||||
|
|
||||||
val connection = createActor(new Actor {
|
|
||||||
def receive = {
|
|
||||||
case "end" ⇒ doneLatch.countDown()
|
|
||||||
case msg: Int ⇒ counters.get(i).get.addAndGet(msg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
connections = connections :+ connection
|
|
||||||
}
|
|
||||||
|
|
||||||
val props = RoutedProps()
|
|
||||||
.withConnections(connections)
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
|
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
|
||||||
|
|
||||||
for (i ← 0 until iterationCount) {
|
|
||||||
for (k ← 0 until connectionCount) {
|
|
||||||
actor ! (k + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actor ! Broadcast("end")
|
|
||||||
|
|
||||||
doneLatch.await
|
|
||||||
|
|
||||||
for (i ← 0 until connectionCount) {
|
|
||||||
val counter = counters.get(i).get
|
|
||||||
counter.get must be((iterationCount * (i + 1)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"deliver a broadcast message using the !" in {
|
|
||||||
val doneLatch = new TestLatch(2)
|
|
||||||
|
|
||||||
val counter1 = new AtomicInteger
|
|
||||||
val connection1 = createActor(new Actor {
|
|
||||||
def receive = {
|
|
||||||
case "end" ⇒ doneLatch.countDown()
|
|
||||||
case msg: Int ⇒ counter1.addAndGet(msg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
val counter2 = new AtomicInteger
|
|
||||||
val connection2 = createActor(new Actor {
|
|
||||||
def receive = {
|
|
||||||
case "end" ⇒ doneLatch.countDown()
|
|
||||||
case msg: Int ⇒ counter2.addAndGet(msg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
val props = RoutedProps.apply()
|
|
||||||
.withConnections(List(connection1, connection2))
|
|
||||||
.withRouter(() ⇒ new ScatterGatherFirstCompletedRouter())
|
|
||||||
|
|
||||||
val actor = app.routing.actorOf(props, "foo")
|
|
||||||
|
|
||||||
actor ! Broadcast(1)
|
|
||||||
actor ! Broadcast("end")
|
|
||||||
|
|
||||||
doneLatch.await
|
|
||||||
|
|
||||||
counter1.get must be(1)
|
|
||||||
counter2.get must be(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Stop(id: Option[Int] = None)
|
|
||||||
|
|
||||||
def newActor(id: Int, shudownLatch: Option[TestLatch] = None) = createActor(new Actor {
|
|
||||||
def receive = {
|
|
||||||
case Stop(None) ⇒ self.stop()
|
|
||||||
case Stop(Some(_id)) if (_id == id) ⇒ self.stop()
|
|
||||||
case _id: Int if (_id == id) ⇒
|
|
||||||
case _ ⇒ Thread sleep 100 * id; tryReply(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def postStop = {
|
|
||||||
shudownLatch foreach (_.countDown())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,6 @@ import com.eaio.uuid.UUID;
|
||||||
* - locating actors
|
* - locating actors
|
||||||
*/
|
*/
|
||||||
public class Actors {
|
public class Actors {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message that is sent when an Actor gets a receive timeout.
|
* The message that is sent when an Actor gets a receive timeout.
|
||||||
* <pre>
|
* <pre>
|
||||||
|
|
|
||||||
|
|
@ -163,12 +163,12 @@ class AkkaApplication(val name: String, val config: Configuration) extends Actor
|
||||||
* Handle to the ActorRegistry.
|
* Handle to the ActorRegistry.
|
||||||
* TODO: delete me!
|
* TODO: delete me!
|
||||||
*/
|
*/
|
||||||
val registry = new ActorRegistry
|
// val registry = new ActorRegistry
|
||||||
|
|
||||||
// TODO check memory consistency issues
|
// TODO check memory consistency issues
|
||||||
val reflective = new ReflectiveAccess(this)
|
val reflective = new ReflectiveAccess(this)
|
||||||
|
|
||||||
val routing = new Routing(this)
|
// val routing = new Routing(this)
|
||||||
|
|
||||||
val remote = reflective.RemoteModule.defaultRemoteSupport map (_.apply) getOrElse null
|
val remote = reflective.RemoteModule.defaultRemoteSupport map (_.apply) getOrElse null
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,6 @@ object Actor {
|
||||||
case _ ⇒ new LoggingReceive(source, r)
|
case _ ⇒ new LoggingReceive(source, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,6 @@ private[akka] class ActorCell(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.registry.register(self)
|
|
||||||
dispatcher.attach(this)
|
dispatcher.attach(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,7 +385,6 @@ private[akka] class ActorCell(
|
||||||
receiveTimeout = None
|
receiveTimeout = None
|
||||||
cancelReceiveTimeout
|
cancelReceiveTimeout
|
||||||
app.provider.evict(self.address)
|
app.provider.evict(self.address)
|
||||||
app.registry.unregister(self)
|
|
||||||
dispatcher.detach(this)
|
dispatcher.detach(this)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -406,7 +404,7 @@ private[akka] class ActorCell(
|
||||||
|
|
||||||
if (supervisor.isDefined) supervisor.get ! ChildTerminated(self, cause)
|
if (supervisor.isDefined) supervisor.get ! ChildTerminated(self, cause)
|
||||||
|
|
||||||
InVMMonitoring.signal(Terminated(self, cause))
|
InVMMonitoring.publish(Terminated(self, cause))
|
||||||
|
|
||||||
currentMessage = null
|
currentMessage = null
|
||||||
clearActorContext()
|
clearActorContext()
|
||||||
|
|
@ -428,10 +426,10 @@ private[akka] class ActorCell(
|
||||||
case Create ⇒ create()
|
case Create ⇒ create()
|
||||||
case Recreate(cause) ⇒ recreate(cause)
|
case Recreate(cause) ⇒ recreate(cause)
|
||||||
case Link(subject) ⇒
|
case Link(subject) ⇒
|
||||||
akka.event.InVMMonitoring.link(self, subject)
|
akka.event.InVMMonitoring.subscribe(self, subject)
|
||||||
if (app.AkkaConfig.DebugLifecycle) app.eventHandler.debug(self, "now monitoring " + subject)
|
if (app.AkkaConfig.DebugLifecycle) app.eventHandler.debug(self, "now monitoring " + subject)
|
||||||
case Unlink(subject) ⇒
|
case Unlink(subject) ⇒
|
||||||
akka.event.InVMMonitoring.unlink(self, subject)
|
akka.event.InVMMonitoring.unsubscribe(self, subject)
|
||||||
if (app.AkkaConfig.DebugLifecycle) app.eventHandler.debug(self, "stopped monitoring " + subject)
|
if (app.AkkaConfig.DebugLifecycle) app.eventHandler.debug(self, "stopped monitoring " + subject)
|
||||||
case Suspend ⇒ suspend()
|
case Suspend ⇒ suspend()
|
||||||
case Resume ⇒ resume()
|
case Resume ⇒ resume()
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import java.lang.{ UnsupportedOperationException, IllegalStateException }
|
||||||
import akka.AkkaApplication
|
import akka.AkkaApplication
|
||||||
import akka.remote.RemoteSupport
|
import akka.remote.RemoteSupport
|
||||||
import scala.util.DynamicVariable
|
import scala.util.DynamicVariable
|
||||||
|
import akka.event.{ EventHandler, InVMMonitoring }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ActorRef is an immutable and serializable handle to an Actor.
|
* ActorRef is an immutable and serializable handle to an Actor.
|
||||||
|
|
@ -263,7 +264,6 @@ object RemoteActorSystemMessage {
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
*/
|
*/
|
||||||
private[akka] case class RemoteActorRef private[akka] (
|
private[akka] case class RemoteActorRef private[akka] (
|
||||||
val application: AkkaApplication,
|
|
||||||
val remote: RemoteSupport,
|
val remote: RemoteSupport,
|
||||||
val remoteAddress: InetSocketAddress,
|
val remoteAddress: InetSocketAddress,
|
||||||
val address: String,
|
val address: String,
|
||||||
|
|
@ -275,8 +275,6 @@ private[akka] case class RemoteActorRef private[akka] (
|
||||||
|
|
||||||
def isShutdown: Boolean = !running
|
def isShutdown: Boolean = !running
|
||||||
|
|
||||||
application.reflective.RemoteModule.ensureEnabled()
|
|
||||||
|
|
||||||
def postMessageToMailbox(message: Any, channel: UntypedChannel) {
|
def postMessageToMailbox(message: Any, channel: UntypedChannel) {
|
||||||
val chSender = if (channel.isInstanceOf[ActorRef]) Some(channel.asInstanceOf[ActorRef]) else None
|
val chSender = if (channel.isInstanceOf[ActorRef]) Some(channel.asInstanceOf[ActorRef]) else None
|
||||||
remote.send[Any](message, chSender, None, remoteAddress, true, this, loader)
|
remote.send[Any](message, chSender, None, remoteAddress, true, this, loader)
|
||||||
|
|
@ -392,13 +390,14 @@ case class SerializedActorRef(uuid: Uuid,
|
||||||
if (application.value eq null) throw new IllegalStateException(
|
if (application.value eq null) throw new IllegalStateException(
|
||||||
"Trying to deserialize a serialized ActorRef without an AkkaApplication in scope." +
|
"Trying to deserialize a serialized ActorRef without an AkkaApplication in scope." +
|
||||||
" Use akka.serialization.Serialization.application.withValue(akkaApplication) { ... }")
|
" Use akka.serialization.Serialization.application.withValue(akkaApplication) { ... }")
|
||||||
application.value.registry.local.actorFor(uuid) match {
|
application.value.provider.actorFor(address) match {
|
||||||
case Some(actor) ⇒ actor
|
case Some(actor) ⇒ actor
|
||||||
case None ⇒
|
case None ⇒
|
||||||
//TODO FIXME Add case for when hostname+port == remote.address.hostname+port, should return a DeadActorRef or something
|
// TODO FIXME Add case for when hostname+port == remote.address.hostname+port, should return a DeadActorRef or something
|
||||||
|
// TODO FIXME the remote should only be in the remote actor ref provider
|
||||||
val remote = application.value.reflective.RemoteModule
|
val remote = application.value.reflective.RemoteModule
|
||||||
if (remote.isEnabled)
|
if (remote.isEnabled)
|
||||||
RemoteActorRef(application.value, remote.defaultRemoteSupport.get(), new InetSocketAddress(hostname, port), address, None)
|
RemoteActorRef(remote.defaultRemoteSupport.get(), new InetSocketAddress(hostname, port), address, None)
|
||||||
else
|
else
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Trying to deserialize ActorRef [" + this +
|
"Trying to deserialize ActorRef [" + this +
|
||||||
|
|
@ -424,3 +423,23 @@ trait UnsupportedActorRef extends ActorRef with ScalaActorRef {
|
||||||
|
|
||||||
private def unsupported = throw new UnsupportedOperationException("Not supported for %s".format(getClass.getName))
|
private def unsupported = throw new UnsupportedOperationException("Not supported for %s".format(getClass.getName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DeadLetterActorRef(app: AkkaApplication) extends UnsupportedActorRef {
|
||||||
|
val brokenPromise = new KeptPromise[Any](Left(new ActorKilledException("In DeadLetterActorRef, promises are always broken.")))(app.dispatcher)
|
||||||
|
val address: String = "akka:internal:DeadLetterActorRef"
|
||||||
|
|
||||||
|
override def link(actorRef: ActorRef): ActorRef = actorRef
|
||||||
|
|
||||||
|
override def unlink(actorRef: ActorRef): ActorRef = actorRef
|
||||||
|
|
||||||
|
def isShutdown(): Boolean = true
|
||||||
|
|
||||||
|
def stop(): Unit = ()
|
||||||
|
|
||||||
|
protected[akka] def postMessageToMailbox(message: Any, channel: UntypedChannel): Unit = app.eventHandler.debug(this, message)
|
||||||
|
|
||||||
|
protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout(
|
||||||
|
message: Any,
|
||||||
|
timeout: Timeout,
|
||||||
|
channel: UntypedChannel): Future[Any] = { app.eventHandler.debug(this, message); brokenPromise }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,16 @@
|
||||||
|
|
||||||
package akka.actor
|
package akka.actor
|
||||||
|
|
||||||
import DeploymentConfig._
|
|
||||||
import akka.event.EventHandler
|
import akka.event.EventHandler
|
||||||
import akka.AkkaException
|
import akka.config.ConfigurationException
|
||||||
|
import akka.util.ReflectiveAccess
|
||||||
import akka.routing._
|
import akka.routing._
|
||||||
import akka.AkkaApplication
|
import akka.AkkaApplication
|
||||||
import akka.dispatch.MessageDispatcher
|
import akka.dispatch.MessageDispatcher
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import akka.dispatch.Promise
|
import akka.dispatch.Promise
|
||||||
import com.eaio.uuid.UUID
|
import com.eaio.uuid.UUID
|
||||||
|
import akka.AkkaException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for all ActorRef providers to implement.
|
* Interface for all ActorRef providers to implement.
|
||||||
|
|
@ -21,7 +22,9 @@ trait ActorRefProvider {
|
||||||
|
|
||||||
def actorOf(props: Props, address: String): Option[ActorRef]
|
def actorOf(props: Props, address: String): Option[ActorRef]
|
||||||
|
|
||||||
def findActorRef(address: String): Option[ActorRef]
|
def actorOf(props: RoutedProps, address: String): Option[ActorRef]
|
||||||
|
|
||||||
|
def actorFor(address: String): Option[ActorRef]
|
||||||
|
|
||||||
private[akka] def evict(address: String): Boolean
|
private[akka] def evict(address: String): Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +55,11 @@ trait ActorRefFactory {
|
||||||
|
|
||||||
def createActor(creator: UntypedActorFactory): ActorRef = createActor(Props(() ⇒ creator.create()))
|
def createActor(creator: UntypedActorFactory): ActorRef = createActor(Props(() ⇒ creator.create()))
|
||||||
|
|
||||||
def findActor(address: String): Option[ActorRef] = provider.findActorRef(address)
|
def createActor(props: RoutedProps): ActorRef = createActor(props, new UUID().toString)
|
||||||
|
|
||||||
|
def createActor(props: RoutedProps, address: String): ActorRef = provider.actorOf(props, address).get
|
||||||
|
|
||||||
|
def findActor(address: String): Option[ActorRef] = provider.actorFor(address)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +81,10 @@ class LocalActorRefProvider(val application: AkkaApplication, val deployer: Depl
|
||||||
|
|
||||||
def actorOf(props: Props, address: String): Option[ActorRef] = actorOf(props, address, false)
|
def actorOf(props: Props, address: String): Option[ActorRef] = actorOf(props, address, false)
|
||||||
|
|
||||||
def findActorRef(address: String): Option[ActorRef] = application.registry.local.actorFor(address)
|
def actorFor(address: String): Option[ActorRef] = actors.get(address) match {
|
||||||
|
case null ⇒ None
|
||||||
|
case future ⇒ future.await.resultOrException.getOrElse(None)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the actor was in the provider's cache and evicted successfully, else false.
|
* Returns true if the actor was in the provider's cache and evicted successfully, else false.
|
||||||
|
|
@ -90,6 +100,8 @@ class LocalActorRefProvider(val application: AkkaApplication, val deployer: Depl
|
||||||
else
|
else
|
||||||
props
|
props
|
||||||
|
|
||||||
|
val defaultTimeout = application.AkkaConfig.ActorTimeout
|
||||||
|
|
||||||
val newFuture = Promise[Option[ActorRef]](5000)(application.dispatcher) // FIXME is this proper timeout?
|
val newFuture = Promise[Option[ActorRef]](5000)(application.dispatcher) // FIXME is this proper timeout?
|
||||||
val oldFuture = actors.putIfAbsent(address, newFuture)
|
val oldFuture = actors.putIfAbsent(address, newFuture)
|
||||||
|
|
||||||
|
|
@ -99,30 +111,28 @@ class LocalActorRefProvider(val application: AkkaApplication, val deployer: Depl
|
||||||
deployer.lookupDeploymentFor(address) match { // see if the deployment already exists, if so use it, if not create actor
|
deployer.lookupDeploymentFor(address) match { // see if the deployment already exists, if so use it, if not create actor
|
||||||
|
|
||||||
// create a local actor
|
// create a local actor
|
||||||
case None | Some(Deploy(_, _, Direct, _, _, LocalScope)) ⇒
|
case None | Some(DeploymentConfig.Deploy(_, _, DeploymentConfig.Direct, _, _, DeploymentConfig.LocalScope)) ⇒
|
||||||
Some(new LocalActorRef(application, localProps, address, systemService)) // create a local actor
|
Some(new LocalActorRef(application, localProps, address, systemService)) // create a local actor
|
||||||
|
|
||||||
// create a routed actor ref
|
// create a routed actor ref
|
||||||
case deploy @ Some(Deploy(_, _, router, nrOfInstances, _, LocalScope)) ⇒
|
case deploy @ Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, _, DeploymentConfig.LocalScope)) ⇒
|
||||||
val routerType = DeploymentConfig.routerTypeFor(router)
|
val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match {
|
||||||
|
case RouterType.Direct ⇒ () ⇒ new DirectRouter
|
||||||
val routerFactory: () ⇒ Router = routerType match {
|
case RouterType.Random ⇒ () ⇒ new RandomRouter
|
||||||
case RouterType.Direct ⇒ () ⇒ new DirectRouter
|
case RouterType.RoundRobin ⇒ () ⇒ new RoundRobinRouter
|
||||||
case RouterType.Random ⇒ () ⇒ new RandomRouter
|
case RouterType.ScatterGather ⇒ () ⇒ new ScatterGatherFirstCompletedRouter()(props.dispatcher, defaultTimeout)
|
||||||
case RouterType.RoundRobin ⇒ () ⇒ new RoundRobinRouter
|
case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet")
|
||||||
case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet")
|
case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet")
|
||||||
case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet")
|
case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet")
|
||||||
case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet")
|
case RouterType.Custom(implClass) ⇒ () ⇒ Routing.createCustomRouter(implClass)
|
||||||
case RouterType.Custom ⇒ sys.error("Router Custom not supported yet")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val connections: Iterable[ActorRef] =
|
val connections: Iterable[ActorRef] =
|
||||||
if (nrOfInstances.factor > 0)
|
if (nrOfInstances.factor > 0)
|
||||||
Vector.fill(nrOfInstances.factor)(new LocalActorRef(application, localProps, new UUID().toString, systemService))
|
Vector.fill(nrOfInstances.factor)(new LocalActorRef(application, localProps, new UUID().toString, systemService))
|
||||||
else Nil
|
else Nil
|
||||||
|
|
||||||
Some(application.routing.actorOf(RoutedProps(
|
actorOf(RoutedProps(routerFactory = routerFactory, connectionManager = new LocalConnectionManager(connections)), address)
|
||||||
routerFactory = routerFactory,
|
|
||||||
connections = connections)))
|
|
||||||
|
|
||||||
case _ ⇒ None // non-local actor - pass it on
|
case _ ⇒ None // non-local actor - pass it on
|
||||||
}
|
}
|
||||||
|
|
@ -132,8 +142,6 @@ class LocalActorRefProvider(val application: AkkaApplication, val deployer: Depl
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
actor foreach application.registry.register // only for ActorRegistry backward compat, will be removed later
|
|
||||||
|
|
||||||
newFuture completeWithResult actor
|
newFuture completeWithResult actor
|
||||||
actor
|
actor
|
||||||
|
|
||||||
|
|
@ -141,4 +149,21 @@ class LocalActorRefProvider(val application: AkkaApplication, val deployer: Depl
|
||||||
oldFuture.await.resultOrException.getOrElse(None)
|
oldFuture.await.resultOrException.getOrElse(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates (or fetches) a routed actor reference, configured by the 'props: RoutedProps' configuration.
|
||||||
|
*/
|
||||||
|
def actorOf(props: RoutedProps, address: String): Option[ActorRef] = {
|
||||||
|
//FIXME clustering should be implemented by cluster actor ref provider
|
||||||
|
//TODO Implement support for configuring by deployment ID etc
|
||||||
|
//TODO If address matches an already created actor (Ahead-of-time deployed) return that actor
|
||||||
|
//TODO If address exists in config, it will override the specified Props (should we attempt to merge?)
|
||||||
|
//TODO If the actor deployed uses a different config, then ignore or throw exception?
|
||||||
|
if (props.connectionManager.size == 0) throw new ConfigurationException("RoutedProps used for creating actor [" + address + "] has zero connections configured; can't create a router")
|
||||||
|
// val clusteringEnabled = ReflectiveAccess.ClusterModule.isEnabled
|
||||||
|
// val localOnly = props.localOnly
|
||||||
|
// if (clusteringEnabled && !props.localOnly) ReflectiveAccess.ClusterModule.newClusteredActorRef(props)
|
||||||
|
// else new RoutedActorRef(props, address)
|
||||||
|
Some(new RoutedActorRef(props, address))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,299 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.actor
|
|
||||||
|
|
||||||
import scala.collection.mutable.ListBuffer
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
import akka.util.ListenerManagement
|
|
||||||
import reflect.BeanProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base trait for ActorRegistry events, allows listen to when an actor is added and removed from the ActorRegistry.
|
|
||||||
*
|
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
||||||
*/
|
|
||||||
sealed trait ActorRegistryEvent
|
|
||||||
case class ActorRegistered(@BeanProperty address: String, @BeanProperty actor: ActorRef) extends ActorRegistryEvent
|
|
||||||
case class ActorUnregistered(@BeanProperty address: String, @BeanProperty actor: ActorRef) extends ActorRegistryEvent
|
|
||||||
case class TypedActorRegistered(@BeanProperty address: String, @BeanProperty actor: ActorRef, @BeanProperty proxy: AnyRef) extends ActorRegistryEvent
|
|
||||||
case class TypedActorUnregistered(@BeanProperty address: String, @BeanProperty actor: ActorRef, @BeanProperty proxy: AnyRef) extends ActorRegistryEvent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registry holding all Actor instances in the whole system.
|
|
||||||
* Mapped by address which is a unique string.
|
|
||||||
*
|
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
||||||
*/
|
|
||||||
private[akka] final class ActorRegistry private[akka] () extends ListenerManagement {
|
|
||||||
private val actorsByAddress = new ConcurrentHashMap[String, ActorRef]
|
|
||||||
private val actorsByUuid = new ConcurrentHashMap[Uuid, ActorRef]
|
|
||||||
private val typedActorsByUuid = new ConcurrentHashMap[Uuid, AnyRef]
|
|
||||||
|
|
||||||
val local = new LocalActorRegistry(actorsByAddress, actorsByUuid, typedActorsByUuid)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the actor that has a specific address.
|
|
||||||
*/
|
|
||||||
def actorFor(address: String): Option[ActorRef] = Option(actorsByAddress.get(address))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the typed actors that have a specific address.
|
|
||||||
*/
|
|
||||||
def typedActorFor(address: String): Option[AnyRef] =
|
|
||||||
actorFor(address) map (typedActorFor(_))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an actor in the ActorRegistry.
|
|
||||||
*/
|
|
||||||
private[akka] def register(actor: ActorRef) {
|
|
||||||
val address = actor.address
|
|
||||||
|
|
||||||
// FIXME: this check is nice but makes serialization/deserialization specs break
|
|
||||||
//if (actorsByAddress.containsKey(address) || registeredInCluster(address))
|
|
||||||
// throw new IllegalStateException("Actor 'address' [" + address + "] is already in use, can't register actor [" + actor + "]")
|
|
||||||
actorsByAddress.put(address, actor)
|
|
||||||
actorsByUuid.put(actor.uuid, actor)
|
|
||||||
notifyListeners(ActorRegistered(address, actor))
|
|
||||||
}
|
|
||||||
|
|
||||||
private[akka] def registerTypedActor(actorRef: ActorRef, proxy: AnyRef) {
|
|
||||||
if (typedActorsByUuid.putIfAbsent(actorRef.uuid, proxy) eq null)
|
|
||||||
notifyListeners(TypedActorRegistered(actorRef.address, actorRef, proxy))
|
|
||||||
}
|
|
||||||
|
|
||||||
private[akka] def unregisterTypedActor(actorRef: ActorRef, proxy: AnyRef) {
|
|
||||||
if (typedActorsByUuid.remove(actorRef.uuid, proxy))
|
|
||||||
notifyListeners(TypedActorUnregistered(actorRef.address, actorRef, proxy))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters an actor in the ActorRegistry.
|
|
||||||
*/
|
|
||||||
private[akka] def unregister(address: String) {
|
|
||||||
val actor = actorsByAddress remove address
|
|
||||||
actorsByUuid remove actor.uuid
|
|
||||||
notifyListeners(ActorUnregistered(address, actor))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters an actor in the ActorRegistry.
|
|
||||||
*/
|
|
||||||
private[akka] def unregister(actor: ActorRef) {
|
|
||||||
val address = actor.address
|
|
||||||
actorsByAddress remove address
|
|
||||||
actorsByUuid remove actor.uuid
|
|
||||||
notifyListeners(ActorUnregistered(address, actor))
|
|
||||||
|
|
||||||
//Safe cleanup (if called from the outside)
|
|
||||||
val proxy = typedActorsByUuid.remove(actor.uuid)
|
|
||||||
if (proxy ne null)
|
|
||||||
notifyListeners(TypedActorUnregistered(address, actor, proxy))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an actor in the Cluster ActorRegistry.
|
|
||||||
*/
|
|
||||||
// private[akka] def registerInCluster[T <: Actor](
|
|
||||||
// address: String, actorRef: ActorRef, replicas: Int, serializeMailbox: Boolean = false)(implicit format: Serializer) {
|
|
||||||
// // FIXME: implement ActorRegistry.registerInCluster(..)
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters an actor in the Cluster ActorRegistry.
|
|
||||||
*/
|
|
||||||
// private[akka] def unregisterInCluster(address: String) {
|
|
||||||
// ClusterModule.node.remove(address)
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the typed actor proxy for a given typed actor ref.
|
|
||||||
*/
|
|
||||||
private def typedActorFor(actorRef: ActorRef): Option[AnyRef] =
|
|
||||||
Option(typedActorsByUuid.get(actorRef.uuid))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Projection over the local actor registry.
|
|
||||||
*/
|
|
||||||
class LocalActorRegistry(
|
|
||||||
private val actorsByAddress: ConcurrentHashMap[String, ActorRef],
|
|
||||||
private val actorsByUuid: ConcurrentHashMap[Uuid, ActorRef],
|
|
||||||
private val typedActorsByUuid: ConcurrentHashMap[Uuid, AnyRef]) {
|
|
||||||
|
|
||||||
// NOTE: currently ClusterActorRef's are only taken into account in 'actorFor(..)' - not in 'find', 'filter' etc.
|
|
||||||
private val clusterActorRefsByAddress = new ConcurrentHashMap[String, ActorRef]
|
|
||||||
private val clusterActorRefsByUuid = new ConcurrentHashMap[Uuid, ActorRef]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of actors in the system.
|
|
||||||
*/
|
|
||||||
def size: Int = actorsByAddress.size
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shuts down and unregisters all actors in the system.
|
|
||||||
*/
|
|
||||||
def shutdownAll() {
|
|
||||||
foreach(_.stop)
|
|
||||||
actorsByAddress.clear()
|
|
||||||
actorsByUuid.clear()
|
|
||||||
typedActorsByUuid.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
//============== ACTORS ==============
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the actor that have a specific address.
|
|
||||||
*
|
|
||||||
* If a ClusterActorRef exists in the registry, then return that before we look after a LocalActorRef.
|
|
||||||
*/
|
|
||||||
def actorFor(address: String): Option[ActorRef] = {
|
|
||||||
if (clusterActorRefsByAddress.containsKey(address)) Some(clusterActorRefsByAddress.get(address))
|
|
||||||
else if (actorsByAddress.containsKey(address)) Some(actorsByAddress.get(address))
|
|
||||||
else None
|
|
||||||
}
|
|
||||||
|
|
||||||
private[akka] def actorFor(uuid: Uuid): Option[ActorRef] =
|
|
||||||
if (clusterActorRefsByUuid.containsKey(uuid)) Some(clusterActorRefsByUuid.get(uuid))
|
|
||||||
else if (actorsByUuid.containsKey(uuid)) Some(actorsByUuid.get(uuid))
|
|
||||||
else None
|
|
||||||
|
|
||||||
// By-passes checking the registry for ClusterActorRef and only returns possible LocalActorRefs
|
|
||||||
private[akka] def localActorRefFor(address: String): Option[ActorRef] = {
|
|
||||||
if (actorsByAddress.containsKey(address)) Some(actorsByAddress.get(address))
|
|
||||||
else None
|
|
||||||
}
|
|
||||||
|
|
||||||
// By-passes checking the registry for ClusterActorRef and only returns possible LocalActorRefs
|
|
||||||
private[akka] def localActorRefFor(uuid: Uuid): Option[ActorRef] =
|
|
||||||
if (actorsByUuid.containsKey(uuid)) Some(actorsByUuid.get(uuid))
|
|
||||||
else None
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the typed actor that have a specific address.
|
|
||||||
*/
|
|
||||||
def typedActorFor(address: String): Option[AnyRef] =
|
|
||||||
actorFor(address) map (typedActorFor(_)) getOrElse None
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the typed actor that have a specific uuid.
|
|
||||||
*/
|
|
||||||
private[akka] def typedActorFor(uuid: Uuid): Option[AnyRef] =
|
|
||||||
Option(typedActorsByUuid.get(uuid))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all actors in the system.
|
|
||||||
*/
|
|
||||||
def actors: Array[ActorRef] = filter(_ ⇒ true)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes a function for all actors.
|
|
||||||
*/
|
|
||||||
def foreach(f: (ActorRef) ⇒ Unit) = {
|
|
||||||
val elements = actorsByAddress.elements
|
|
||||||
while (elements.hasMoreElements) f(elements.nextElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the function on all known actors until it returns Some
|
|
||||||
* Returns None if the function never returns Some
|
|
||||||
*/
|
|
||||||
def find[T](f: PartialFunction[ActorRef, T]): Option[T] = {
|
|
||||||
val elements = actorsByAddress.elements
|
|
||||||
while (elements.hasMoreElements) {
|
|
||||||
val element = elements.nextElement
|
|
||||||
if (f isDefinedAt element) return Some(f(element))
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds all actors that satisfy a predicate.
|
|
||||||
*/
|
|
||||||
def filter(p: ActorRef ⇒ Boolean): Array[ActorRef] = {
|
|
||||||
val all = new ListBuffer[ActorRef]
|
|
||||||
val elements = actorsByAddress.elements
|
|
||||||
while (elements.hasMoreElements) {
|
|
||||||
val actorId = elements.nextElement
|
|
||||||
if (p(actorId)) all += actorId
|
|
||||||
}
|
|
||||||
all.toArray
|
|
||||||
}
|
|
||||||
|
|
||||||
//============== TYPED ACTORS ==============
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all typed actors in the system.
|
|
||||||
*/
|
|
||||||
def typedActors: Array[AnyRef] = filterTypedActors(_ ⇒ true)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes a function for all typed actors.
|
|
||||||
*/
|
|
||||||
def foreachTypedActor(f: (AnyRef) ⇒ Unit) = {
|
|
||||||
val i = typedActorsByUuid.values.iterator
|
|
||||||
while (i.hasNext)
|
|
||||||
f(i.next)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the function on all known typed actors until it returns Some
|
|
||||||
* Returns None if the function never returns Some
|
|
||||||
*/
|
|
||||||
def findTypedActor[T](f: PartialFunction[AnyRef, T]): Option[T] = {
|
|
||||||
val i = typedActorsByUuid.values.iterator
|
|
||||||
while (i.hasNext) {
|
|
||||||
val proxy = i.next
|
|
||||||
if (f isDefinedAt proxy) return Some(f(proxy))
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds all typed actors that satisfy a predicate.
|
|
||||||
*/
|
|
||||||
def filterTypedActors(p: AnyRef ⇒ Boolean): Array[AnyRef] = {
|
|
||||||
val all = new ListBuffer[AnyRef]
|
|
||||||
val i = typedActorsByUuid.values.iterator
|
|
||||||
while (i.hasNext) {
|
|
||||||
val proxy = i.next
|
|
||||||
if (p(proxy)) all += proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
all.toArray
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the typed actor proxy for a given typed actor ref.
|
|
||||||
*/
|
|
||||||
private def typedActorFor(actorRef: ActorRef): Option[AnyRef] =
|
|
||||||
typedActorFor(actorRef.uuid)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers an ClusterActorRef in the ActorRegistry.
|
|
||||||
*/
|
|
||||||
private[akka] def registerClusterActorRef(actor: ActorRef) {
|
|
||||||
val address = actor.address
|
|
||||||
clusterActorRefsByAddress.put(address, actor)
|
|
||||||
clusterActorRefsByUuid.put(actor.uuid, actor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters an ClusterActorRef in the ActorRegistry.
|
|
||||||
*/
|
|
||||||
private[akka] def unregisterClusterActorRef(address: String) {
|
|
||||||
val actor = clusterActorRefsByAddress remove address
|
|
||||||
clusterActorRefsByUuid remove actor.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters an ClusterActorRef in the ActorRegistry.
|
|
||||||
*/
|
|
||||||
private[akka] def unregisterClusterActorRef(actor: ActorRef) {
|
|
||||||
unregisterClusterActorRef(actor.address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -58,7 +58,9 @@ trait BootableActorLoaderService extends Bootable {
|
||||||
|
|
||||||
abstract override def onUnload() = {
|
abstract override def onUnload() = {
|
||||||
super.onUnload()
|
super.onUnload()
|
||||||
app.registry.local.shutdownAll
|
|
||||||
|
// FIXME shutdown all actors
|
||||||
|
// app.registry.local.shutdownAll
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import akka.event.EventHandler
|
||||||
import akka.actor.DeploymentConfig._
|
import akka.actor.DeploymentConfig._
|
||||||
import akka.{ AkkaException, AkkaApplication }
|
import akka.{ AkkaException, AkkaApplication }
|
||||||
import akka.config.{ Configuration, ConfigurationException }
|
import akka.config.{ Configuration, ConfigurationException }
|
||||||
|
import akka.util.Duration
|
||||||
|
|
||||||
trait ActorDeployer {
|
trait ActorDeployer {
|
||||||
private[akka] def init(deployments: Seq[Deploy]): Unit
|
private[akka] def init(deployments: Seq[Deploy]): Unit
|
||||||
|
|
@ -127,7 +128,7 @@ class Deployer(val app: AkkaApplication) extends ActorDeployer {
|
||||||
val addressPath = "akka.actor.deployment." + address
|
val addressPath = "akka.actor.deployment." + address
|
||||||
configuration.getSection(addressPath) match {
|
configuration.getSection(addressPath) match {
|
||||||
case None ⇒
|
case None ⇒
|
||||||
Some(Deploy(address, None, Direct, NrOfInstances(1), RemoveConnectionOnFirstFailureLocalFailureDetector, LocalScope))
|
Some(Deploy(address, None, Direct, NrOfInstances(1), NoOpFailureDetector, LocalScope))
|
||||||
|
|
||||||
case Some(addressConfig) ⇒
|
case Some(addressConfig) ⇒
|
||||||
|
|
||||||
|
|
@ -138,15 +139,11 @@ class Deployer(val app: AkkaApplication) extends ActorDeployer {
|
||||||
case "direct" ⇒ Direct
|
case "direct" ⇒ Direct
|
||||||
case "round-robin" ⇒ RoundRobin
|
case "round-robin" ⇒ RoundRobin
|
||||||
case "random" ⇒ Random
|
case "random" ⇒ Random
|
||||||
|
case "scatter-gather" ⇒ ScatterGather
|
||||||
case "least-cpu" ⇒ LeastCPU
|
case "least-cpu" ⇒ LeastCPU
|
||||||
case "least-ram" ⇒ LeastRAM
|
case "least-ram" ⇒ LeastRAM
|
||||||
case "least-messages" ⇒ LeastMessages
|
case "least-messages" ⇒ LeastMessages
|
||||||
case customRouterClassName ⇒
|
case routerClassName ⇒ CustomRouter(routerClassName)
|
||||||
createInstance[AnyRef](customRouterClassName, emptyParams, emptyArguments).fold(
|
|
||||||
e ⇒ throw new ConfigurationException(
|
|
||||||
"Config option [" + addressPath + ".router] needs to be one of " +
|
|
||||||
"[\"direct\", \"round-robin\", \"random\", \"least-cpu\", \"least-ram\", \"least-messages\" or the fully qualified name of Router class]", e),
|
|
||||||
CustomRouter(_))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
@ -174,7 +171,7 @@ class Deployer(val app: AkkaApplication) extends ActorDeployer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
// akka.actor.deployment.<address>.failure-detector.xxx
|
// akka.actor.deployment.<address>.failure-detector.<detector>
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
val failureDetectorOption: Option[FailureDetector] = addressConfig.getSection("failure-detector") match {
|
val failureDetectorOption: Option[FailureDetector] = addressConfig.getSection("failure-detector") match {
|
||||||
case Some(failureDetectorConfig) ⇒
|
case Some(failureDetectorConfig) ⇒
|
||||||
|
|
@ -182,22 +179,27 @@ class Deployer(val app: AkkaApplication) extends ActorDeployer {
|
||||||
case Nil ⇒ None
|
case Nil ⇒ None
|
||||||
case detector :: Nil ⇒
|
case detector :: Nil ⇒
|
||||||
detector match {
|
detector match {
|
||||||
case "remove-connection-on-first-local-failure" ⇒
|
case "no-op" ⇒
|
||||||
Some(RemoveConnectionOnFirstFailureLocalFailureDetector)
|
Some(NoOpFailureDetector)
|
||||||
|
|
||||||
case "remove-connection-on-first-failure" ⇒
|
case "remove-connection-on-first-failure" ⇒
|
||||||
Some(RemoveConnectionOnFirstFailureFailureDetector)
|
Some(RemoveConnectionOnFirstFailureFailureDetector)
|
||||||
|
|
||||||
case "bannage-period" ⇒
|
case "bannage-period" ⇒
|
||||||
|
throw new ConfigurationException(
|
||||||
|
"Configuration for [" + addressPath + ".failure-detector.bannage-period] must have a 'time-to-ban' option defined")
|
||||||
|
|
||||||
|
case "bannage-period.time-to-ban" ⇒
|
||||||
failureDetectorConfig.getSection("bannage-period") map { section ⇒
|
failureDetectorConfig.getSection("bannage-period") map { section ⇒
|
||||||
BannagePeriodFailureDetector(section.getInt("time-to-ban", 10))
|
val timeToBan = Duration(section.getInt("time-to-ban", 60), app.AkkaConfig.DefaultTimeUnit)
|
||||||
|
BannagePeriodFailureDetector(timeToBan)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "custom" ⇒
|
case "custom" ⇒
|
||||||
failureDetectorConfig.getSection("custom") map { section ⇒
|
failureDetectorConfig.getSection("custom") map { section ⇒
|
||||||
val implementationClass = section.getString("class").getOrElse(throw new ConfigurationException(
|
val implementationClass = section.getString("class").getOrElse(throw new ConfigurationException(
|
||||||
"Configuration for [" + addressPath +
|
"Configuration for [" + addressPath +
|
||||||
"failure-detector.custom] must have a 'class' element with the fully qualified name of the failure detector class"))
|
".failure-detector.custom] must have a 'class' element with the fully qualified name of the failure detector class"))
|
||||||
CustomFailureDetector(implementationClass)
|
CustomFailureDetector(implementationClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,11 +208,11 @@ class Deployer(val app: AkkaApplication) extends ActorDeployer {
|
||||||
case detectors ⇒
|
case detectors ⇒
|
||||||
throw new ConfigurationException(
|
throw new ConfigurationException(
|
||||||
"Configuration for [" + addressPath +
|
"Configuration for [" + addressPath +
|
||||||
"failure-detector] can not have multiple sections - found [" + detectors.mkString(", ") + "]")
|
".failure-detector] can not have multiple sections - found [" + detectors.mkString(", ") + "]")
|
||||||
}
|
}
|
||||||
case None ⇒ None
|
case None ⇒ None
|
||||||
}
|
}
|
||||||
val failureDetector = failureDetectorOption getOrElse { BannagePeriodFailureDetector(10) } // fall back to default failure detector
|
val failureDetector = failureDetectorOption getOrElse { NoOpFailureDetector } // fall back to default failure detector
|
||||||
|
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
// akka.actor.deployment.<address>.create-as
|
// akka.actor.deployment.<address>.create-as
|
||||||
|
|
@ -267,7 +269,7 @@ class Deployer(val app: AkkaApplication) extends ActorDeployer {
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
addressConfig.getSection("cluster") match {
|
addressConfig.getSection("cluster") match {
|
||||||
case None ⇒
|
case None ⇒
|
||||||
Some(Deploy(address, recipe, router, nrOfInstances, RemoveConnectionOnFirstFailureLocalFailureDetector, LocalScope)) // deploy locally
|
Some(Deploy(address, recipe, router, nrOfInstances, NoOpFailureDetector, LocalScope)) // deploy locally
|
||||||
|
|
||||||
case Some(clusterConfig) ⇒
|
case Some(clusterConfig) ⇒
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@
|
||||||
|
|
||||||
package akka.actor
|
package akka.actor
|
||||||
|
|
||||||
import akka.routing.{ RouterType, FailureDetectorType }
|
|
||||||
import akka.AkkaApplication
|
import akka.AkkaApplication
|
||||||
|
import akka.util.Duration
|
||||||
|
import akka.routing.{ RouterType, FailureDetectorType }
|
||||||
|
import akka.routing.FailureDetectorType._
|
||||||
|
|
||||||
object DeploymentConfig {
|
object DeploymentConfig {
|
||||||
|
|
||||||
|
|
@ -17,7 +19,7 @@ object DeploymentConfig {
|
||||||
recipe: Option[ActorRecipe],
|
recipe: Option[ActorRecipe],
|
||||||
routing: Routing = Direct,
|
routing: Routing = Direct,
|
||||||
nrOfInstances: NrOfInstances = ZeroNrOfInstances,
|
nrOfInstances: NrOfInstances = ZeroNrOfInstances,
|
||||||
failureDetector: FailureDetector = RemoveConnectionOnFirstFailureLocalFailureDetector,
|
failureDetector: FailureDetector = NoOpFailureDetector,
|
||||||
scope: Scope = LocalScope) {
|
scope: Scope = LocalScope) {
|
||||||
Address.validate(address)
|
Address.validate(address)
|
||||||
}
|
}
|
||||||
|
|
@ -31,12 +33,13 @@ object DeploymentConfig {
|
||||||
// --- Routing
|
// --- Routing
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
sealed trait Routing
|
sealed trait Routing
|
||||||
case class CustomRouter(router: AnyRef) extends Routing
|
case class CustomRouter(routerClassName: String) extends Routing
|
||||||
|
|
||||||
// For Java API
|
// For Java API
|
||||||
case class Direct() extends Routing
|
case class Direct() extends Routing
|
||||||
case class RoundRobin() extends Routing
|
case class RoundRobin() extends Routing
|
||||||
case class Random() extends Routing
|
case class Random() extends Routing
|
||||||
|
case class ScatterGather() extends Routing
|
||||||
case class LeastCPU() extends Routing
|
case class LeastCPU() extends Routing
|
||||||
case class LeastRAM() extends Routing
|
case class LeastRAM() extends Routing
|
||||||
case class LeastMessages() extends Routing
|
case class LeastMessages() extends Routing
|
||||||
|
|
@ -45,6 +48,7 @@ object DeploymentConfig {
|
||||||
case object Direct extends Routing
|
case object Direct extends Routing
|
||||||
case object RoundRobin extends Routing
|
case object RoundRobin extends Routing
|
||||||
case object Random extends Routing
|
case object Random extends Routing
|
||||||
|
case object ScatterGather extends Routing
|
||||||
case object LeastCPU extends Routing
|
case object LeastCPU extends Routing
|
||||||
case object LeastRAM extends Routing
|
case object LeastRAM extends Routing
|
||||||
case object LeastMessages extends Routing
|
case object LeastMessages extends Routing
|
||||||
|
|
@ -53,15 +57,15 @@ object DeploymentConfig {
|
||||||
// --- FailureDetector
|
// --- FailureDetector
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
sealed trait FailureDetector
|
sealed trait FailureDetector
|
||||||
case class BannagePeriodFailureDetector(timeToBan: Long) extends FailureDetector
|
case class BannagePeriodFailureDetector(timeToBan: Duration) extends FailureDetector
|
||||||
case class CustomFailureDetector(className: String) extends FailureDetector
|
case class CustomFailureDetector(className: String) extends FailureDetector
|
||||||
|
|
||||||
// For Java API
|
// For Java API
|
||||||
case class RemoveConnectionOnFirstFailureLocalFailureDetector() extends FailureDetector
|
case class NoOpFailureDetector() extends FailureDetector
|
||||||
case class RemoveConnectionOnFirstFailureFailureDetector() extends FailureDetector
|
case class RemoveConnectionOnFirstFailureFailureDetector() extends FailureDetector
|
||||||
|
|
||||||
// For Scala API
|
// For Scala API
|
||||||
case object RemoveConnectionOnFirstFailureLocalFailureDetector extends FailureDetector
|
case object NoOpFailureDetector extends FailureDetector
|
||||||
case object RemoveConnectionOnFirstFailureFailureDetector extends FailureDetector
|
case object RemoveConnectionOnFirstFailureFailureDetector extends FailureDetector
|
||||||
|
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
@ -166,29 +170,31 @@ object DeploymentConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
def failureDetectorTypeFor(failureDetector: FailureDetector): FailureDetectorType = failureDetector match {
|
def failureDetectorTypeFor(failureDetector: FailureDetector): FailureDetectorType = failureDetector match {
|
||||||
case BannagePeriodFailureDetector(timeToBan) ⇒ FailureDetectorType.BannagePeriodFailureDetector(timeToBan)
|
case NoOpFailureDetector ⇒ FailureDetectorType.NoOp
|
||||||
case RemoveConnectionOnFirstFailureLocalFailureDetector ⇒ FailureDetectorType.RemoveConnectionOnFirstFailureLocalFailureDetector
|
case NoOpFailureDetector() ⇒ FailureDetectorType.NoOp
|
||||||
case RemoveConnectionOnFirstFailureLocalFailureDetector() ⇒ FailureDetectorType.RemoveConnectionOnFirstFailureLocalFailureDetector
|
case BannagePeriodFailureDetector(timeToBan) ⇒ FailureDetectorType.BannagePeriod(timeToBan)
|
||||||
case RemoveConnectionOnFirstFailureFailureDetector ⇒ FailureDetectorType.RemoveConnectionOnFirstFailureFailureDetector
|
case RemoveConnectionOnFirstFailureFailureDetector ⇒ FailureDetectorType.RemoveConnectionOnFirstFailure
|
||||||
case RemoveConnectionOnFirstFailureFailureDetector() ⇒ FailureDetectorType.RemoveConnectionOnFirstFailureFailureDetector
|
case RemoveConnectionOnFirstFailureFailureDetector() ⇒ FailureDetectorType.RemoveConnectionOnFirstFailure
|
||||||
case CustomFailureDetector(implClass) ⇒ FailureDetectorType.CustomFailureDetector(implClass)
|
case CustomFailureDetector(implClass) ⇒ FailureDetectorType.Custom(implClass)
|
||||||
case unknown ⇒ throw new UnsupportedOperationException("Unknown FailureDetector [" + unknown + "]")
|
case unknown ⇒ throw new UnsupportedOperationException("Unknown FailureDetector [" + unknown + "]")
|
||||||
}
|
}
|
||||||
|
|
||||||
def routerTypeFor(routing: Routing): RouterType = routing match {
|
def routerTypeFor(routing: Routing): RouterType = routing match {
|
||||||
case Direct ⇒ RouterType.Direct
|
case Direct ⇒ RouterType.Direct
|
||||||
case Direct() ⇒ RouterType.Direct
|
case Direct() ⇒ RouterType.Direct
|
||||||
case RoundRobin ⇒ RouterType.RoundRobin
|
case RoundRobin ⇒ RouterType.RoundRobin
|
||||||
case RoundRobin() ⇒ RouterType.RoundRobin
|
case RoundRobin() ⇒ RouterType.RoundRobin
|
||||||
case Random ⇒ RouterType.Random
|
case Random ⇒ RouterType.Random
|
||||||
case Random() ⇒ RouterType.Random
|
case Random() ⇒ RouterType.Random
|
||||||
case LeastCPU ⇒ RouterType.LeastCPU
|
case ScatterGather ⇒ RouterType.ScatterGather
|
||||||
case LeastCPU() ⇒ RouterType.LeastCPU
|
case ScatterGather() ⇒ RouterType.ScatterGather
|
||||||
case LeastRAM ⇒ RouterType.LeastRAM
|
case LeastCPU ⇒ RouterType.LeastCPU
|
||||||
case LeastRAM() ⇒ RouterType.LeastRAM
|
case LeastCPU() ⇒ RouterType.LeastCPU
|
||||||
case LeastMessages ⇒ RouterType.LeastMessages
|
case LeastRAM ⇒ RouterType.LeastRAM
|
||||||
case LeastMessages() ⇒ RouterType.LeastMessages
|
case LeastRAM() ⇒ RouterType.LeastRAM
|
||||||
case c: CustomRouter ⇒ throw new UnsupportedOperationException("Unknown Router [" + c + "]")
|
case LeastMessages ⇒ RouterType.LeastMessages
|
||||||
|
case LeastMessages() ⇒ RouterType.LeastMessages
|
||||||
|
case CustomRouter(implClass) ⇒ RouterType.Custom(implClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
def isReplicated(replicationScheme: ReplicationScheme): Boolean =
|
def isReplicated(replicationScheme: ReplicationScheme): Boolean =
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,7 @@ object Scheduler {
|
||||||
private[akka] val service = Executors.newSingleThreadScheduledExecutor(SchedulerThreadFactory)
|
private[akka] val service = Executors.newSingleThreadScheduledExecutor(SchedulerThreadFactory)
|
||||||
|
|
||||||
private def createSendRunnable(receiver: ActorRef, message: Any, throwWhenReceiverExpired: Boolean): Runnable = {
|
private def createSendRunnable(receiver: ActorRef, message: Any, throwWhenReceiverExpired: Boolean): Runnable = {
|
||||||
new Runnable {
|
new Runnable { def run = receiver ! message }
|
||||||
def run =
|
|
||||||
if (receiver.isShutdown && throwWhenReceiverExpired)
|
|
||||||
throw new RuntimeException("Receiver not found, unregistered")
|
|
||||||
else
|
|
||||||
receiver ! message
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -265,8 +265,9 @@ class TypedActor(val application: AkkaApplication) {
|
||||||
|
|
||||||
private[akka] class TypedActor[R <: AnyRef, T <: R](val proxyVar: AtomVar[R], createInstance: ⇒ T) extends Actor {
|
private[akka] class TypedActor[R <: AnyRef, T <: R](val proxyVar: AtomVar[R], createInstance: ⇒ T) extends Actor {
|
||||||
|
|
||||||
override def preStart = application.registry.registerTypedActor(self, proxyVar.get) //Make sure actor registry knows about this actor
|
// FIXME TypedActor register/unregister on postStop/preStart
|
||||||
override def postStop = application.registry.unregisterTypedActor(self, proxyVar.get)
|
// override def preStart = application.registry.registerTypedActor(self, proxyVar.get) //Make sure actor registry knows about this actor
|
||||||
|
// override def postStop = application.registry.unregisterTypedActor(self, proxyVar.get)
|
||||||
|
|
||||||
val me = createInstance
|
val me = createInstance
|
||||||
def receive = {
|
def receive = {
|
||||||
|
|
|
||||||
|
|
@ -805,14 +805,27 @@ trait Promise[T] extends Future[T] {
|
||||||
|
|
||||||
//Companion object to FState, just to provide a cheap, immutable default entry
|
//Companion object to FState, just to provide a cheap, immutable default entry
|
||||||
private[akka] object FState {
|
private[akka] object FState {
|
||||||
val empty = new FState[Nothing]()
|
def apply[T](): FState[T] = EmptyPending.asInstanceOf[FState[T]]
|
||||||
def apply[T](): FState[T] = empty.asInstanceOf[FState[T]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the internal state of the DefaultCompletableFuture
|
* Represents the internal state of the DefaultCompletableFuture
|
||||||
*/
|
*/
|
||||||
private[akka] case class FState[T](value: Option[Either[Throwable, T]] = None, listeners: List[Future[T] ⇒ Unit] = Nil)
|
|
||||||
|
sealed trait FState[+T] { def value: Option[Either[Throwable, T]] }
|
||||||
|
case class Pending[T](listeners: List[Future[T] ⇒ Unit] = Nil) extends FState[T] {
|
||||||
|
def value: Option[Either[Throwable, T]] = None
|
||||||
|
}
|
||||||
|
case class Success[T](value: Option[Either[Throwable, T]] = None) extends FState[T] {
|
||||||
|
def result: T = value.get.right.get
|
||||||
|
}
|
||||||
|
case class Failure[T](value: Option[Either[Throwable, T]] = None) extends FState[T] {
|
||||||
|
def exception: Throwable = value.get.left.get
|
||||||
|
}
|
||||||
|
case object Expired extends FState[Nothing] {
|
||||||
|
def value: Option[Either[Throwable, Nothing]] = None
|
||||||
|
}
|
||||||
|
val EmptyPending = Pending[Nothing](Nil)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default concrete Future implementation.
|
* The default concrete Future implementation.
|
||||||
|
|
@ -820,6 +833,8 @@ private[akka] case class FState[T](value: Option[Either[Throwable, T]] = None, l
|
||||||
class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDispatcher) extends AbstractPromise with Promise[T] {
|
class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDispatcher) extends AbstractPromise with Promise[T] {
|
||||||
self ⇒
|
self ⇒
|
||||||
|
|
||||||
|
import FState.{ FState, Success, Failure, Pending, Expired }
|
||||||
|
|
||||||
def this()(implicit dispatcher: MessageDispatcher, timeout: Timeout) = this(timeout)
|
def this()(implicit dispatcher: MessageDispatcher, timeout: Timeout) = this(timeout)
|
||||||
|
|
||||||
def this(timeout: Long)(implicit dispatcher: MessageDispatcher) = this(Timeout(timeout))
|
def this(timeout: Long)(implicit dispatcher: MessageDispatcher) = this(Timeout(timeout))
|
||||||
|
|
@ -867,7 +882,18 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi
|
||||||
AbstractPromise.updater.asInstanceOf[AtomicReferenceFieldUpdater[AbstractPromise, FState[T]]].compareAndSet(this, oldState, newState)
|
AbstractPromise.updater.asInstanceOf[AtomicReferenceFieldUpdater[AbstractPromise, FState[T]]].compareAndSet(this, oldState, newState)
|
||||||
|
|
||||||
@inline
|
@inline
|
||||||
protected final def getState: FState[T] = AbstractPromise.updater.asInstanceOf[AtomicReferenceFieldUpdater[AbstractPromise, FState[T]]].get(this)
|
protected final def getState: FState[T] = {
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
def read(): FState[T] = {
|
||||||
|
val cur = AbstractPromise.updater.asInstanceOf[AtomicReferenceFieldUpdater[AbstractPromise, FState[T]]].get(this)
|
||||||
|
if (cur.isInstanceOf[Pending[_]] && isExpired) {
|
||||||
|
if (updateState(cur, Expired)) Expired else read()
|
||||||
|
} else cur
|
||||||
|
}
|
||||||
|
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
|
||||||
def complete(value: Either[Throwable, T]): this.type = {
|
def complete(value: Either[Throwable, T]): this.type = {
|
||||||
val callbacks = {
|
val callbacks = {
|
||||||
|
|
@ -875,15 +901,12 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi
|
||||||
@tailrec
|
@tailrec
|
||||||
def tryComplete: List[Future[T] ⇒ Unit] = {
|
def tryComplete: List[Future[T] ⇒ Unit] = {
|
||||||
val cur = getState
|
val cur = getState
|
||||||
if (cur.value.isDefined) Nil
|
|
||||||
else if ( /*cur.value.isEmpty && */ isExpired) {
|
cur match {
|
||||||
//Empty and expired, so remove listeners
|
case Pending(listeners) ⇒
|
||||||
//TODO Perhaps cancel existing onTimeout listeners in the future here?
|
if (updateState(cur, if (value.isLeft) Failure(Some(value)) else Success(Some(value)))) listeners
|
||||||
updateState(cur, FState()) //Try to reset the state to the default, doesn't matter if it fails
|
else tryComplete
|
||||||
Nil
|
case _ ⇒ Nil
|
||||||
} else {
|
|
||||||
if (updateState(cur, FState(Option(value), Nil))) cur.listeners
|
|
||||||
else tryComplete
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tryComplete
|
tryComplete
|
||||||
|
|
@ -901,10 +924,14 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi
|
||||||
@tailrec //Returns whether the future has already been completed or not
|
@tailrec //Returns whether the future has already been completed or not
|
||||||
def tryAddCallback(): Boolean = {
|
def tryAddCallback(): Boolean = {
|
||||||
val cur = getState
|
val cur = getState
|
||||||
if (cur.value.isDefined) true
|
cur match {
|
||||||
else if (isExpired) false
|
case _: Success[_] | _: Failure[_] ⇒ true
|
||||||
else if (updateState(cur, cur.copy(listeners = func :: cur.listeners))) false
|
case Expired ⇒ false
|
||||||
else tryAddCallback()
|
case p: Pending[_] ⇒
|
||||||
|
val pt = p.asInstanceOf[Pending[T]]
|
||||||
|
if (updateState(pt, pt.copy(listeners = func :: pt.listeners))) false
|
||||||
|
else tryAddCallback()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tryAddCallback()) Future.dispatchTask(() ⇒ notifyCompleted(func))
|
if (tryAddCallback()) Future.dispatchTask(() ⇒ notifyCompleted(func))
|
||||||
|
|
@ -937,10 +964,10 @@ class DefaultPromise[T](val timeout: Timeout)(implicit val dispatcher: MessageDi
|
||||||
|
|
||||||
final def orElse[A >: T](fallback: ⇒ A): Future[A] =
|
final def orElse[A >: T](fallback: ⇒ A): Future[A] =
|
||||||
if (timeout.duration.isFinite) {
|
if (timeout.duration.isFinite) {
|
||||||
value match {
|
getState match {
|
||||||
case Some(_) ⇒ this
|
case _: Success[_] | _: Failure[_] ⇒ this
|
||||||
case _ if isExpired ⇒ Future[A](fallback, timeout)
|
case Expired ⇒ Future[A](fallback, timeout)
|
||||||
case _ ⇒
|
case _: Pending[_] ⇒
|
||||||
val promise = new DefaultPromise[A](Timeout.never) //TODO FIXME We can't have infinite timeout here, doesn't make sense.
|
val promise = new DefaultPromise[A](Timeout.never) //TODO FIXME We can't have infinite timeout here, doesn't make sense.
|
||||||
promise completeWith this
|
promise completeWith this
|
||||||
val runnable = new Runnable {
|
val runnable = new Runnable {
|
||||||
|
|
|
||||||
|
|
@ -51,14 +51,23 @@ object ThreadPoolConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function0 without the fun stuff (mostly for the sake of the Java API side of things)
|
||||||
|
*/
|
||||||
trait ExecutorServiceFactory {
|
trait ExecutorServiceFactory {
|
||||||
def createExecutorService: ExecutorService
|
def createExecutorService: ExecutorService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic way to specify an ExecutorService to a Dispatcher, create it with the given name if desired
|
||||||
|
*/
|
||||||
trait ExecutorServiceFactoryProvider {
|
trait ExecutorServiceFactoryProvider {
|
||||||
def createExecutorServiceFactory(name: String): ExecutorServiceFactory
|
def createExecutorServiceFactory(name: String): ExecutorServiceFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A small configuration DSL to create ThreadPoolExecutors that can be provided as an ExecutorServiceFactoryProvider to Dispatcher
|
||||||
|
*/
|
||||||
case class ThreadPoolConfig(app: AkkaApplication,
|
case class ThreadPoolConfig(app: AkkaApplication,
|
||||||
allowCorePoolTimeout: Boolean = ThreadPoolConfig.defaultAllowCoreThreadTimeout,
|
allowCorePoolTimeout: Boolean = ThreadPoolConfig.defaultAllowCoreThreadTimeout,
|
||||||
corePoolSize: Int = ThreadPoolConfig.defaultCorePoolSize,
|
corePoolSize: Int = ThreadPoolConfig.defaultCorePoolSize,
|
||||||
|
|
@ -90,6 +99,9 @@ object ThreadPoolConfigDispatcherBuilder {
|
||||||
def conf_?[T](opt: Option[T])(fun: (T) ⇒ ThreadPoolConfigDispatcherBuilder ⇒ ThreadPoolConfigDispatcherBuilder): Option[(ThreadPoolConfigDispatcherBuilder) ⇒ ThreadPoolConfigDispatcherBuilder] = opt map fun
|
def conf_?[T](opt: Option[T])(fun: (T) ⇒ ThreadPoolConfigDispatcherBuilder ⇒ ThreadPoolConfigDispatcherBuilder): Option[(ThreadPoolConfigDispatcherBuilder) ⇒ ThreadPoolConfigDispatcherBuilder] = opt map fun
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DSL to configure and create a MessageDispatcher with a ThreadPoolExecutor
|
||||||
|
*/
|
||||||
case class ThreadPoolConfigDispatcherBuilder(dispatcherFactory: (ThreadPoolConfig) ⇒ MessageDispatcher, config: ThreadPoolConfig) extends DispatcherBuilder {
|
case class ThreadPoolConfigDispatcherBuilder(dispatcherFactory: (ThreadPoolConfig) ⇒ MessageDispatcher, config: ThreadPoolConfig) extends DispatcherBuilder {
|
||||||
import ThreadPoolConfig._
|
import ThreadPoolConfig._
|
||||||
def build = dispatcherFactory(config)
|
def build = dispatcherFactory(config)
|
||||||
|
|
@ -224,6 +236,9 @@ class BoundedExecutorDecorator(val app: AkkaApplication, val executor: ExecutorS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As the name says
|
||||||
|
*/
|
||||||
trait ExecutorServiceDelegate extends ExecutorService {
|
trait ExecutorServiceDelegate extends ExecutorService {
|
||||||
|
|
||||||
def executor: ExecutorService
|
def executor: ExecutorService
|
||||||
|
|
@ -255,6 +270,9 @@ trait ExecutorServiceDelegate extends ExecutorService {
|
||||||
def invokeAny[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = executor.invokeAny(callables, l, timeUnit)
|
def invokeAny[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = executor.invokeAny(callables, l, timeUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ExecutorService that only creates the underlying Executor if any of the methods of the ExecutorService are called
|
||||||
|
*/
|
||||||
trait LazyExecutorService extends ExecutorServiceDelegate {
|
trait LazyExecutorService extends ExecutorServiceDelegate {
|
||||||
|
|
||||||
def createExecutor: ExecutorService
|
def createExecutor: ExecutorService
|
||||||
|
|
@ -264,6 +282,9 @@ trait LazyExecutorService extends ExecutorServiceDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A concrete implementation of LazyExecutorService (Scala API)
|
||||||
|
*/
|
||||||
class LazyExecutorServiceWrapper(executorFactory: ⇒ ExecutorService) extends LazyExecutorService {
|
class LazyExecutorServiceWrapper(executorFactory: ⇒ ExecutorService) extends LazyExecutorService {
|
||||||
def createExecutor = executorFactory
|
def createExecutor = executorFactory
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,116 +6,31 @@ package akka.event
|
||||||
|
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
|
|
||||||
trait DeathWatch {
|
/**
|
||||||
def signal(terminated: Terminated): Unit
|
* The contract of DeathWatch is not properly expressed using the type system
|
||||||
|
* Whenever there is a publish, all listeners to the Terminated Actor should be atomically removed
|
||||||
|
* A failed subscribe should also only mean that the Classifier (ActorRef) that is listened to is already shut down
|
||||||
|
* See InVMMonitoring for semantics
|
||||||
|
*/
|
||||||
|
trait DeathWatch extends ActorEventBus with ActorClassifier {
|
||||||
|
type Event = Terminated
|
||||||
|
|
||||||
|
protected final def classify(event: Event): Classifier = event.actor
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Monitoring {
|
object InVMMonitoring extends DeathWatch with ActorClassification {
|
||||||
|
|
||||||
def link(monitor: ActorRef, monitored: ActorRef): Unit
|
def mapSize = 1024
|
||||||
|
|
||||||
def unlink(monitor: ActorRef, monitored: ActorRef): Unit
|
override def publish(event: Event): Unit = {
|
||||||
}
|
val monitors = dissociate(classify(event))
|
||||||
|
if (monitors.nonEmpty) monitors.foreach(_ ! event)
|
||||||
object InVMMonitoring extends DeathWatch with Monitoring {
|
|
||||||
|
|
||||||
class MonitoringBook(mapSize: Int = 1024) {
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
|
|
||||||
val mappings = new ConcurrentHashMap[ActorRef, Vector[ActorRef]](mapSize)
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def associate(monitored: ActorRef, monitor: ActorRef): Boolean = {
|
|
||||||
val current = mappings get monitored
|
|
||||||
current match {
|
|
||||||
case null ⇒
|
|
||||||
if (monitored.isShutdown) false
|
|
||||||
else {
|
|
||||||
if (mappings.putIfAbsent(monitored, Vector(monitor)) ne null) associate(monitored, monitor)
|
|
||||||
else {
|
|
||||||
if (monitored.isShutdown) !dissociate(monitored, monitor)
|
|
||||||
else true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case raw: Vector[_] ⇒
|
|
||||||
val v = raw.asInstanceOf[Vector[ActorRef]]
|
|
||||||
if (monitored.isShutdown) false
|
|
||||||
if (v.contains(monitor)) true
|
|
||||||
else {
|
|
||||||
val added = v :+ monitor
|
|
||||||
if (!mappings.replace(monitored, v, added)) associate(monitored, monitor)
|
|
||||||
else {
|
|
||||||
if (monitored.isShutdown) !dissociate(monitored, monitor)
|
|
||||||
else true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final def dissociate(monitored: ActorRef): Iterable[ActorRef] = {
|
|
||||||
@tailrec
|
|
||||||
def dissociateAsMonitored(monitored: ActorRef): Iterable[ActorRef] = {
|
|
||||||
val current = mappings get monitored
|
|
||||||
current match {
|
|
||||||
case null ⇒ Vector.empty[ActorRef]
|
|
||||||
case raw: Vector[_] ⇒
|
|
||||||
val v = raw.asInstanceOf[Vector[ActorRef]]
|
|
||||||
if (!mappings.remove(monitored, v)) dissociateAsMonitored(monitored)
|
|
||||||
else v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def dissociateAsMonitor(monitor: ActorRef): Unit = {
|
|
||||||
val i = mappings.entrySet.iterator
|
|
||||||
while (i.hasNext()) {
|
|
||||||
val entry = i.next()
|
|
||||||
val v = entry.getValue
|
|
||||||
v match {
|
|
||||||
case raw: Vector[_] ⇒
|
|
||||||
val monitors = raw.asInstanceOf[Vector[ActorRef]]
|
|
||||||
if (monitors.contains(monitor))
|
|
||||||
dissociate(entry.getKey, monitor)
|
|
||||||
case _ ⇒ //Dun care
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try { dissociateAsMonitored(monitored) } finally { dissociateAsMonitor(monitored) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def dissociate(monitored: ActorRef, monitor: ActorRef): Boolean = {
|
|
||||||
val current = mappings get monitored
|
|
||||||
current match {
|
|
||||||
case null ⇒ false
|
|
||||||
case raw: Vector[_] ⇒
|
|
||||||
val v = raw.asInstanceOf[Vector[ActorRef]]
|
|
||||||
val removed = v.filterNot(monitor ==)
|
|
||||||
if (removed eq v) false
|
|
||||||
else if (removed.isEmpty) {
|
|
||||||
if (!mappings.remove(monitored, v)) dissociate(monitored, monitor)
|
|
||||||
else true
|
|
||||||
} else {
|
|
||||||
if (!mappings.replace(monitored, v, removed)) dissociate(monitored, monitor)
|
|
||||||
else true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val monitoring = new MonitoringBook(1024) //Key == monitored, Values == monitors
|
override def subscribe(subscriber: Subscriber, to: Classifier): Boolean = {
|
||||||
|
if (!super.subscribe(subscriber, to)) {
|
||||||
def signal(terminated: Terminated): Unit = {
|
subscriber ! Terminated(subscriber, new ActorKilledException("Already terminated when linking"))
|
||||||
val monitors = monitoring.dissociate(terminated.actor)
|
false
|
||||||
if (monitors.nonEmpty) monitors.foreach(_ ! terminated)
|
} else true
|
||||||
}
|
}
|
||||||
|
|
||||||
def link(monitor: ActorRef, monitored: ActorRef): Unit = {
|
|
||||||
if (!monitoring.associate(monitored, monitor))
|
|
||||||
monitor ! Terminated(monitored, new ActorKilledException("Already terminated when linking"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def unlink(monitor: ActorRef, monitored: ActorRef): Unit =
|
|
||||||
monitoring.dissociate(monitored, monitor)
|
|
||||||
}
|
}
|
||||||
275
akka-actor/src/main/scala/akka/event/EventBus.scala
Normal file
275
akka-actor/src/main/scala/akka/event/EventBus.scala
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.event
|
||||||
|
|
||||||
|
import akka.actor.ActorRef
|
||||||
|
import akka.util.Index
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet
|
||||||
|
import java.util.Comparator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the base type for EventBuses
|
||||||
|
* Internally has an Event type, a Classifier type and a Subscriber type
|
||||||
|
*
|
||||||
|
* For the Java API, @see akka.event.japi.*
|
||||||
|
*/
|
||||||
|
trait EventBus {
|
||||||
|
type Event
|
||||||
|
type Classifier
|
||||||
|
type Subscriber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to register the subscriber to the specified Classifier
|
||||||
|
* @returns true if successful and false if not (because it was already subscribed to that Classifier, or otherwise)
|
||||||
|
*/
|
||||||
|
def subscribe(subscriber: Subscriber, to: Classifier): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to deregister the subscriber from the specified Classifier
|
||||||
|
* @returns true if successful and false if not (because it wasn't subscribed to that Classifier, or otherwise)
|
||||||
|
*/
|
||||||
|
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to deregister the subscriber from all Classifiers it may be subscribed to
|
||||||
|
*/
|
||||||
|
def unsubscribe(subscriber: Subscriber): Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes the specified Event to this bus
|
||||||
|
*/
|
||||||
|
def publish(event: Event): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an EventBus where the Subscriber type is ActorRef
|
||||||
|
*/
|
||||||
|
trait ActorEventBus extends EventBus {
|
||||||
|
type Subscriber = ActorRef
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be mixed into an EventBus to specify that the Classifier type is ActorRef
|
||||||
|
*/
|
||||||
|
trait ActorClassifier { self: EventBus ⇒
|
||||||
|
type Classifier = ActorRef
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be mixed into an EventBus to specify that the Classifier type is a Function from Event to Boolean (predicate)
|
||||||
|
*/
|
||||||
|
trait PredicateClassifier { self: EventBus ⇒
|
||||||
|
type Classifier = Event ⇒ Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps Subscribers to Classifiers using equality on Classifier to store a Set of Subscribers (hence the need for compareSubscribers)
|
||||||
|
* Maps Events to Classifiers through the classify-method (so it knows who to publish to)
|
||||||
|
*
|
||||||
|
* The compareSubscribers need to provide a total ordering of the Subscribers
|
||||||
|
*/
|
||||||
|
trait LookupClassification { self: EventBus ⇒
|
||||||
|
|
||||||
|
protected final val subscribers = new Index[Classifier, Subscriber](mapSize(), new Comparator[Subscriber] {
|
||||||
|
def compare(a: Subscriber, b: Subscriber): Int = compareSubscribers(a, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a size hint for the number of Classifiers you expect to have (use powers of 2)
|
||||||
|
*/
|
||||||
|
protected def mapSize(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a total ordering of Subscribers (think java.util.Comparator.compare)
|
||||||
|
*/
|
||||||
|
protected def compareSubscribers(a: Subscriber, b: Subscriber): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Classifier associated with the given Event
|
||||||
|
*/
|
||||||
|
protected def classify(event: Event): Classifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes the given Event to the given Subscriber
|
||||||
|
*/
|
||||||
|
protected def publish(event: Event, subscriber: Subscriber): Unit
|
||||||
|
|
||||||
|
def subscribe(subscriber: Subscriber, to: Classifier): Boolean = subscribers.put(to, subscriber)
|
||||||
|
|
||||||
|
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean = subscribers.remove(from, subscriber)
|
||||||
|
|
||||||
|
def unsubscribe(subscriber: Subscriber): Unit = subscribers.removeValue(subscriber)
|
||||||
|
|
||||||
|
def publish(event: Event): Unit = {
|
||||||
|
val i = subscribers.valueIterator(classify(event))
|
||||||
|
while (i.hasNext) publish(event, i.next())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps Classifiers to Subscribers and selects which Subscriber should receive which publication through scanning through all Subscribers
|
||||||
|
* through the matches(classifier, event) method
|
||||||
|
*
|
||||||
|
* Note: the compareClassifiers and compareSubscribers must together form an absolute ordering (think java.util.Comparator.compare)
|
||||||
|
*/
|
||||||
|
trait ScanningClassification { self: EventBus ⇒
|
||||||
|
protected final val subscribers = new ConcurrentSkipListSet[(Classifier, Subscriber)](new Comparator[(Classifier, Subscriber)] {
|
||||||
|
def compare(a: (Classifier, Subscriber), b: (Classifier, Subscriber)): Int = {
|
||||||
|
val cM = compareClassifiers(a._1, b._1)
|
||||||
|
if (cM != 0) cM
|
||||||
|
else compareSubscribers(a._2, b._2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a total ordering of Classifiers (think java.util.Comparator.compare)
|
||||||
|
*/
|
||||||
|
protected def compareClassifiers(a: Classifier, b: Classifier): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a total ordering of Subscribers (think java.util.Comparator.compare)
|
||||||
|
*/
|
||||||
|
protected def compareSubscribers(a: Subscriber, b: Subscriber): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the specified Classifier matches the specified Event
|
||||||
|
*/
|
||||||
|
protected def matches(classifier: Classifier, event: Event): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes the specified Event to the specified Subscriber
|
||||||
|
*/
|
||||||
|
protected def publish(event: Event, subscriber: Subscriber): Unit
|
||||||
|
|
||||||
|
def subscribe(subscriber: Subscriber, to: Classifier): Boolean = subscribers.add((to, subscriber))
|
||||||
|
|
||||||
|
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean = subscribers.remove((from, subscriber))
|
||||||
|
|
||||||
|
def unsubscribe(subscriber: Subscriber): Unit = {
|
||||||
|
val i = subscribers.iterator()
|
||||||
|
while (i.hasNext) {
|
||||||
|
val e = i.next()
|
||||||
|
if (compareSubscribers(subscriber, e._2) == 0) i.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def publish(event: Event): Unit = {
|
||||||
|
val currentSubscribers = subscribers.iterator()
|
||||||
|
while (currentSubscribers.hasNext) {
|
||||||
|
val (classifier, subscriber) = currentSubscribers.next()
|
||||||
|
if (matches(classifier, event))
|
||||||
|
publish(event, subscriber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps ActorRefs to ActorRefs to form an EventBus where ActorRefs can listen to other ActorRefs
|
||||||
|
*/
|
||||||
|
trait ActorClassification { self: ActorEventBus with ActorClassifier ⇒
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
protected val mappings = new ConcurrentHashMap[ActorRef, Vector[ActorRef]](mapSize)
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
protected final def associate(monitored: ActorRef, monitor: ActorRef): Boolean = {
|
||||||
|
val current = mappings get monitored
|
||||||
|
current match {
|
||||||
|
case null ⇒
|
||||||
|
if (monitored.isShutdown) false
|
||||||
|
else {
|
||||||
|
if (mappings.putIfAbsent(monitored, Vector(monitor)) ne null) associate(monitored, monitor)
|
||||||
|
else {
|
||||||
|
if (monitored.isShutdown) !dissociate(monitored, monitor)
|
||||||
|
else true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case raw: Vector[_] ⇒
|
||||||
|
val v = raw.asInstanceOf[Vector[ActorRef]]
|
||||||
|
if (monitored.isShutdown) false
|
||||||
|
if (v.contains(monitor)) true
|
||||||
|
else {
|
||||||
|
val added = v :+ monitor
|
||||||
|
if (!mappings.replace(monitored, v, added)) associate(monitored, monitor)
|
||||||
|
else {
|
||||||
|
if (monitored.isShutdown) !dissociate(monitored, monitor)
|
||||||
|
else true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final def dissociate(monitored: ActorRef): Iterable[ActorRef] = {
|
||||||
|
@tailrec
|
||||||
|
def dissociateAsMonitored(monitored: ActorRef): Iterable[ActorRef] = {
|
||||||
|
val current = mappings get monitored
|
||||||
|
current match {
|
||||||
|
case null ⇒ Vector.empty[ActorRef]
|
||||||
|
case raw: Vector[_] ⇒
|
||||||
|
val v = raw.asInstanceOf[Vector[ActorRef]]
|
||||||
|
if (!mappings.remove(monitored, v)) dissociateAsMonitored(monitored)
|
||||||
|
else v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def dissociateAsMonitor(monitor: ActorRef): Unit = {
|
||||||
|
val i = mappings.entrySet.iterator
|
||||||
|
while (i.hasNext()) {
|
||||||
|
val entry = i.next()
|
||||||
|
val v = entry.getValue
|
||||||
|
v match {
|
||||||
|
case raw: Vector[_] ⇒
|
||||||
|
val monitors = raw.asInstanceOf[Vector[ActorRef]]
|
||||||
|
if (monitors.contains(monitor))
|
||||||
|
dissociate(entry.getKey, monitor)
|
||||||
|
case _ ⇒ //Dun care
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try { dissociateAsMonitored(monitored) } finally { dissociateAsMonitor(monitored) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
protected final def dissociate(monitored: ActorRef, monitor: ActorRef): Boolean = {
|
||||||
|
val current = mappings get monitored
|
||||||
|
current match {
|
||||||
|
case null ⇒ false
|
||||||
|
case raw: Vector[_] ⇒
|
||||||
|
val v = raw.asInstanceOf[Vector[ActorRef]]
|
||||||
|
val removed = v.filterNot(monitor ==)
|
||||||
|
if (removed eq v) false
|
||||||
|
else if (removed.isEmpty) {
|
||||||
|
if (!mappings.remove(monitored, v)) dissociate(monitored, monitor)
|
||||||
|
else true
|
||||||
|
} else {
|
||||||
|
if (!mappings.replace(monitored, v, removed)) dissociate(monitored, monitor)
|
||||||
|
else true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Classifier associated with the specified Event
|
||||||
|
*/
|
||||||
|
protected def classify(event: Event): Classifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a size hint for the number of Classifiers you expect to have (use powers of 2)
|
||||||
|
*/
|
||||||
|
protected def mapSize: Int
|
||||||
|
|
||||||
|
def publish(event: Event): Unit = mappings.get(classify(event)) match {
|
||||||
|
case null ⇒
|
||||||
|
case raw: Vector[_] ⇒
|
||||||
|
val v = raw.asInstanceOf[Vector[ActorRef]]
|
||||||
|
v foreach { _ ! event }
|
||||||
|
}
|
||||||
|
|
||||||
|
def subscribe(subscriber: Subscriber, to: Classifier): Boolean = associate(to, subscriber)
|
||||||
|
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean = dissociate(from, subscriber)
|
||||||
|
def unsubscribe(subscriber: Subscriber): Unit = dissociate(subscriber)
|
||||||
|
}
|
||||||
38
akka-actor/src/main/scala/akka/event/EventBusJavaAPI.scala
Normal file
38
akka-actor/src/main/scala/akka/event/EventBusJavaAPI.scala
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package akka.event.japi
|
||||||
|
|
||||||
|
import akka.event._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See documentation for akka.event.LookupClassification
|
||||||
|
* E is the Event type
|
||||||
|
* S is the Subscriber type
|
||||||
|
* C is the Classifier type
|
||||||
|
*/
|
||||||
|
abstract class LookupEventBus[E, S, C] extends EventBus with LookupClassification {
|
||||||
|
type Event = E
|
||||||
|
type Subscriber = S
|
||||||
|
type Classifier = C
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See documentation for akka.event.ScanningClassification
|
||||||
|
* E is the Event type
|
||||||
|
* S is the Subscriber type
|
||||||
|
* C is the Classifier type
|
||||||
|
*/
|
||||||
|
abstract class ScanningEventBus[E, S, C] extends EventBus with ScanningClassification {
|
||||||
|
type Event = E
|
||||||
|
type Subscriber = S
|
||||||
|
type Classifier = C
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See documentation for akka.event.ActorClassification
|
||||||
|
* An EventBus where the Subscribers are ActorRefs and the Classifier is ActorRef
|
||||||
|
* Means that ActorRefs "listen" to other ActorRefs
|
||||||
|
* E is the Event type
|
||||||
|
*/
|
||||||
|
|
||||||
|
abstract class ActorEventBus[E] extends akka.event.ActorEventBus with ActorClassification with ActorClassifier {
|
||||||
|
|
||||||
|
}
|
||||||
120
akka-actor/src/main/scala/akka/routing/ConnectionManager.scala
Normal file
120
akka-actor/src/main/scala/akka/routing/ConnectionManager.scala
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.routing
|
||||||
|
|
||||||
|
import akka.actor._
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Iterable that also contains a version.
|
||||||
|
*/
|
||||||
|
trait VersionedIterable[A] {
|
||||||
|
val version: Long
|
||||||
|
|
||||||
|
def iterable: Iterable[A]
|
||||||
|
|
||||||
|
def apply(): Iterable[A] = iterable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages connections (ActorRefs) for a router.
|
||||||
|
*
|
||||||
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
|
*/
|
||||||
|
trait ConnectionManager {
|
||||||
|
/**
|
||||||
|
* A version that is useful to see if there is any change in the connections. If there is a change, a router is
|
||||||
|
* able to update its internal datastructures.
|
||||||
|
*/
|
||||||
|
def version: Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of 'available' connections. Value could be stale as soon as received, and this method can't be combined (easily)
|
||||||
|
* with an atomic read of and size and version.
|
||||||
|
*/
|
||||||
|
def size: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts the connection manager down, which stops all managed actors
|
||||||
|
*/
|
||||||
|
def shutdown()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a VersionedIterator containing all connectected ActorRefs at some moment in time. Since there is
|
||||||
|
* the time element, also the version is included to be able to read the data (the connections) and the version
|
||||||
|
* in an atomic manner.
|
||||||
|
*
|
||||||
|
* This Iterable is 'persistent'. So it can be handed out to different threads and they see a stable (immutable)
|
||||||
|
* view of some set of connections.
|
||||||
|
*/
|
||||||
|
def connections: VersionedIterable[ActorRef]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a connection from the connection manager.
|
||||||
|
*
|
||||||
|
* @param ref the dead
|
||||||
|
*/
|
||||||
|
def remove(deadRef: ActorRef)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new connection (ActorRef) if it didn't exist. Atomically.
|
||||||
|
*/
|
||||||
|
def putIfAbsent(address: InetSocketAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails over connections from one address to another.
|
||||||
|
*/
|
||||||
|
def failOver(from: InetSocketAddress, to: InetSocketAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages local connections for a router, e.g. local actors.
|
||||||
|
*/
|
||||||
|
class LocalConnectionManager(initialConnections: Iterable[ActorRef]) extends ConnectionManager {
|
||||||
|
|
||||||
|
case class State(version: Long, connections: Iterable[ActorRef]) extends VersionedIterable[ActorRef] {
|
||||||
|
def iterable = connections
|
||||||
|
}
|
||||||
|
|
||||||
|
private val state: AtomicReference[State] = new AtomicReference[State](newState())
|
||||||
|
|
||||||
|
private def newState() = State(Long.MinValue, initialConnections)
|
||||||
|
|
||||||
|
def version: Long = state.get.version
|
||||||
|
|
||||||
|
def size: Int = state.get.connections.size
|
||||||
|
|
||||||
|
def connections = state.get
|
||||||
|
|
||||||
|
def shutdown() {
|
||||||
|
state.get.connections foreach (_.stop())
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
final def remove(ref: ActorRef) = {
|
||||||
|
val oldState = state.get
|
||||||
|
|
||||||
|
//remote the ref from the connections.
|
||||||
|
var newList = oldState.connections.filter(currentActorRef ⇒ currentActorRef ne ref)
|
||||||
|
|
||||||
|
if (newList.size != oldState.connections.size) {
|
||||||
|
//one or more occurrences of the actorRef were removed, so we need to update the state.
|
||||||
|
|
||||||
|
val newState = State(oldState.version + 1, newList)
|
||||||
|
//if we are not able to update the state, we just try again.
|
||||||
|
if (!state.compareAndSet(oldState, newState)) remove(ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def failOver(from: InetSocketAddress, to: InetSocketAddress) {} // do nothing here
|
||||||
|
|
||||||
|
def putIfAbsent(address: InetSocketAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef = {
|
||||||
|
throw new UnsupportedOperationException("Not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.routing
|
|
||||||
|
|
||||||
import akka.AkkaException
|
|
||||||
import akka.actor._
|
|
||||||
import akka.event.EventHandler
|
|
||||||
import akka.config.ConfigurationException
|
|
||||||
import akka.actor.UntypedChannel._
|
|
||||||
import akka.dispatch.Future
|
|
||||||
import akka.util.ReflectiveAccess
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.lang.reflect.InvocationTargetException
|
|
||||||
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
|
|
||||||
sealed trait FailureDetectorType
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for declarative configuration of failure detection.
|
|
||||||
*
|
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
||||||
*/
|
|
||||||
object FailureDetectorType {
|
|
||||||
case object RemoveConnectionOnFirstFailureLocalFailureDetector extends FailureDetectorType
|
|
||||||
case object RemoveConnectionOnFirstFailureFailureDetector extends FailureDetectorType
|
|
||||||
case class BannagePeriodFailureDetector(timeToBan: Long) extends FailureDetectorType
|
|
||||||
case class CustomFailureDetector(className: String) extends FailureDetectorType
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Misc helper and factory methods for failure detection.
|
|
||||||
*/
|
|
||||||
object FailureDetector {
|
|
||||||
|
|
||||||
def createCustomFailureDetector(
|
|
||||||
implClass: String,
|
|
||||||
connections: Map[InetSocketAddress, ActorRef]): FailureDetector = {
|
|
||||||
|
|
||||||
ReflectiveAccess.createInstance(
|
|
||||||
implClass,
|
|
||||||
Array[Class[_]](classOf[Map[InetSocketAddress, ActorRef]]),
|
|
||||||
Array[AnyRef](connections)) match {
|
|
||||||
case Right(actor) ⇒ actor
|
|
||||||
case Left(exception) ⇒
|
|
||||||
val cause = exception match {
|
|
||||||
case i: InvocationTargetException ⇒ i.getTargetException
|
|
||||||
case _ ⇒ exception
|
|
||||||
}
|
|
||||||
throw new ConfigurationException(
|
|
||||||
"Could not instantiate custom FailureDetector of [" +
|
|
||||||
implClass + "] due to: " +
|
|
||||||
cause, cause)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The FailureDetector acts like a middleman between the Router and
|
|
||||||
* the actor reference that does the routing and can dectect and act upon failure.
|
|
||||||
*
|
|
||||||
* Through the FailureDetector:
|
|
||||||
* <ol>
|
|
||||||
* <li>
|
|
||||||
* the actor ref can signal that something has changed in the known set of connections. The Router can see
|
|
||||||
* when a changed happened (by checking the version) and update its internal datastructures.
|
|
||||||
* </li>
|
|
||||||
* <li>
|
|
||||||
* the Router can indicate that some happened happened with a actor ref, e.g. the actor ref dying.
|
|
||||||
* </li>
|
|
||||||
* </ol>
|
|
||||||
*
|
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
||||||
*/
|
|
||||||
trait FailureDetector {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the 'connection' is considered available.
|
|
||||||
*/
|
|
||||||
def isAvailable(connection: InetSocketAddress): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records a successful connection.
|
|
||||||
*/
|
|
||||||
def recordSuccess(connection: InetSocketAddress, timestamp: Long)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records a failed connection.
|
|
||||||
*/
|
|
||||||
def recordFailure(connection: InetSocketAddress, timestamp: Long)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version that is useful to see if there is any change in the connections. If there is a change, a router is
|
|
||||||
* able to update its internal datastructures.
|
|
||||||
*/
|
|
||||||
def version: Long
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of connections. Value could be stale as soon as received, and this method can't be combined (easily)
|
|
||||||
* with an atomic read of and size and version.
|
|
||||||
*/
|
|
||||||
def size: Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops all managed actors
|
|
||||||
*/
|
|
||||||
def stopAll()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a VersionedIterator containing all connectected ActorRefs at some moment in time. Since there is
|
|
||||||
* the time element, also the version is included to be able to read the data (the connections) and the version
|
|
||||||
* in an atomic manner.
|
|
||||||
*
|
|
||||||
* This Iterable is 'persistent'. So it can be handed out to different threads and they see a stable (immutable)
|
|
||||||
* view of some set of connections.
|
|
||||||
*/
|
|
||||||
def versionedIterable: VersionedIterable[ActorRef]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback that can be used to indicate that a connected actorRef was dead.
|
|
||||||
* <p/>
|
|
||||||
* Implementations should make sure that this method can be called without the actorRef being part of the
|
|
||||||
* current set of connections. The most logical way to deal with this situation, is just to ignore it. One of the
|
|
||||||
* reasons this can happen is that multiple thread could at the 'same' moment discover for the same ActorRef that
|
|
||||||
* not working.
|
|
||||||
*
|
|
||||||
* It could be that even after a remove has been called for a specific ActorRef, that the ActorRef
|
|
||||||
* is still being used. A good behaving Router will eventually discard this reference, but no guarantees are
|
|
||||||
* made how long this takes.
|
|
||||||
*
|
|
||||||
* @param ref the dead
|
|
||||||
*/
|
|
||||||
def remove(deadRef: ActorRef)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: document
|
|
||||||
*/
|
|
||||||
def putIfAbsent(address: InetSocketAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fails over connections from one address to another.
|
|
||||||
*/
|
|
||||||
def failOver(from: InetSocketAddress, to: InetSocketAddress)
|
|
||||||
}
|
|
||||||
|
|
@ -6,11 +6,25 @@ package akka.routing
|
||||||
|
|
||||||
import akka.util.Duration
|
import akka.util.Duration
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import akka.util.ReflectiveAccess
|
import akka.util.{ ReflectiveAccess, Duration }
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
import scala.collection.JavaConversions.iterableAsScalaIterable
|
import scala.collection.JavaConversions.{ iterableAsScalaIterable, mapAsScalaMap }
|
||||||
|
|
||||||
|
sealed trait FailureDetectorType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for declarative configuration of failure detection.
|
||||||
|
*
|
||||||
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
|
*/
|
||||||
|
object FailureDetectorType {
|
||||||
|
case object NoOp extends FailureDetectorType
|
||||||
|
case object RemoveConnectionOnFirstFailure extends FailureDetectorType
|
||||||
|
case class BannagePeriod(timeToBan: Duration) extends FailureDetectorType
|
||||||
|
case class Custom(className: String) extends FailureDetectorType
|
||||||
|
}
|
||||||
|
|
||||||
sealed trait RouterType
|
sealed trait RouterType
|
||||||
|
|
||||||
|
|
@ -33,6 +47,11 @@ object RouterType {
|
||||||
*/
|
*/
|
||||||
object RoundRobin extends RouterType
|
object RoundRobin extends RouterType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A RouterType that selects the connection by using scatter gather.
|
||||||
|
*/
|
||||||
|
object ScatterGather extends RouterType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A RouterType that selects the connection based on the least amount of cpu usage
|
* A RouterType that selects the connection based on the least amount of cpu usage
|
||||||
*/
|
*/
|
||||||
|
|
@ -53,25 +72,10 @@ object RouterType {
|
||||||
/**
|
/**
|
||||||
* A user-defined custom RouterType.
|
* A user-defined custom RouterType.
|
||||||
*/
|
*/
|
||||||
object Custom extends RouterType
|
case class Custom(implClass: String) extends RouterType
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object RoutedProps {
|
|
||||||
|
|
||||||
final val defaultTimeout = Timeout(Duration.MinusInf)
|
|
||||||
final val defaultRouterFactory = () ⇒ new RoundRobinRouter
|
|
||||||
final val defaultLocalOnly = false
|
|
||||||
final val defaultFailureDetectorFactory = (connections: Map[InetSocketAddress, ActorRef]) ⇒ new RemoveConnectionOnFirstFailureLocalFailureDetector(connections.values)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default RoutedProps instance, uses the settings from the RoutedProps object starting with default*
|
|
||||||
*/
|
|
||||||
final val default = new RoutedProps
|
|
||||||
|
|
||||||
def apply(): RoutedProps = default
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the configuration to create local and clustered routed actor references.
|
* Contains the configuration to create local and clustered routed actor references.
|
||||||
*
|
*
|
||||||
|
|
@ -86,12 +90,11 @@ object RoutedProps {
|
||||||
*/
|
*/
|
||||||
case class RoutedProps(
|
case class RoutedProps(
|
||||||
routerFactory: () ⇒ Router,
|
routerFactory: () ⇒ Router,
|
||||||
connections: Iterable[ActorRef],
|
connectionManager: ConnectionManager,
|
||||||
failureDetectorFactory: (Map[InetSocketAddress, ActorRef]) ⇒ FailureDetector = RoutedProps.defaultFailureDetectorFactory,
|
|
||||||
timeout: Timeout = RoutedProps.defaultTimeout,
|
timeout: Timeout = RoutedProps.defaultTimeout,
|
||||||
localOnly: Boolean = RoutedProps.defaultLocalOnly) {
|
localOnly: Boolean = RoutedProps.defaultLocalOnly) {
|
||||||
|
|
||||||
def this() = this(RoutedProps.defaultRouterFactory, List())
|
def this() = this(RoutedProps.defaultRouterFactory, new LocalConnectionManager(List()))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new RoutedProps configured with a random router.
|
* Returns a new RoutedProps configured with a random router.
|
||||||
|
|
@ -150,28 +153,35 @@ case class RoutedProps(
|
||||||
*
|
*
|
||||||
* Scala API.
|
* Scala API.
|
||||||
*/
|
*/
|
||||||
def withConnections(c: Iterable[ActorRef]): RoutedProps = copy(connections = c)
|
def withLocalConnections(c: Iterable[ActorRef]): RoutedProps = copy(connectionManager = new LocalConnectionManager(c))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the connections to use.
|
* Sets the connections to use.
|
||||||
*
|
*
|
||||||
* Java API.
|
* Java API.
|
||||||
*/
|
*/
|
||||||
def withConnections(c: java.lang.Iterable[ActorRef]): RoutedProps = copy(connections = iterableAsScalaIterable(c))
|
def withLocalConnections(c: java.lang.Iterable[ActorRef]): RoutedProps = copy(connectionManager = new LocalConnectionManager(iterableAsScalaIterable(c)))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new RoutedProps configured with a FailureDetector factory.
|
* Sets the connections to use.
|
||||||
*
|
*
|
||||||
* Scala API.
|
* Scala API.
|
||||||
*/
|
*/
|
||||||
def withFailureDetector(failureDetectorFactory: (Map[InetSocketAddress, ActorRef]) ⇒ FailureDetector): RoutedProps =
|
// def withRemoteConnections(c: Map[InetSocketAddress, ActorRef]): RoutedProps = copy(connectionManager = new RemoteConnectionManager(c))
|
||||||
copy(failureDetectorFactory = failureDetectorFactory)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new RoutedProps configured with a FailureDetector factory.
|
* Sets the connections to use.
|
||||||
*
|
*
|
||||||
* Java API.
|
* Java API.
|
||||||
*/
|
*/
|
||||||
def withFailureDetector(failureDetectorFactory: akka.japi.Function[Map[InetSocketAddress, ActorRef], FailureDetector]): RoutedProps =
|
// def withRemoteConnections(c: java.util.collection.Map[InetSocketAddress, ActorRef]): RoutedProps = copy(connectionManager = new RemoteConnectionManager(mapAsScalaMap(c)))
|
||||||
copy(failureDetectorFactory = (connections: Map[InetSocketAddress, ActorRef]) ⇒ failureDetectorFactory.apply(connections))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object RoutedProps {
|
||||||
|
final val defaultTimeout = Timeout(Duration.MinusInf)
|
||||||
|
final val defaultRouterFactory = () ⇒ new RoundRobinRouter
|
||||||
|
final val defaultLocalOnly = false
|
||||||
|
|
||||||
|
def apply() = new RoutedProps()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import akka.event.EventHandler
|
||||||
import akka.config.ConfigurationException
|
import akka.config.ConfigurationException
|
||||||
import akka.dispatch.{ Future, MessageDispatcher }
|
import akka.dispatch.{ Future, MessageDispatcher }
|
||||||
import akka.AkkaApplication
|
import akka.AkkaApplication
|
||||||
|
import akka.util.ReflectiveAccess
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
|
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
|
||||||
|
|
@ -35,7 +35,7 @@ trait Router {
|
||||||
* JMM Guarantees:
|
* JMM Guarantees:
|
||||||
* This method guarantees that all changes made in this method, are visible before one of the routing methods is called.
|
* This method guarantees that all changes made in this method, are visible before one of the routing methods is called.
|
||||||
*/
|
*/
|
||||||
def init(connections: FailureDetector)
|
def init(connectionManager: ConnectionManager)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routes the message to one of the connections.
|
* Routes the message to one of the connections.
|
||||||
|
|
@ -53,150 +53,40 @@ trait Router {
|
||||||
def route[T](message: Any, timeout: Timeout)(implicit sender: Option[ActorRef]): Future[T]
|
def route[T](message: Any, timeout: Timeout)(implicit sender: Option[ActorRef]): Future[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An Iterable that also contains a version.
|
|
||||||
*/
|
|
||||||
trait VersionedIterable[A] {
|
|
||||||
val version: Long
|
|
||||||
|
|
||||||
def iterable: Iterable[A]
|
|
||||||
|
|
||||||
def apply(): Iterable[A] = iterable
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AkkaException} thrown when something goes wrong while routing a message
|
* An {@link AkkaException} thrown when something goes wrong while routing a message
|
||||||
*/
|
*/
|
||||||
class RoutingException(message: String) extends AkkaException(message)
|
class RoutingException(message: String) extends AkkaException(message)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default "local" failure detector. This failure detector removes an actor from the
|
* A Helper class to create actor references that use routing.
|
||||||
* router if an exception occured in the router's thread (e.g. when trying to add
|
|
||||||
* the message to the receiver's mailbox).
|
|
||||||
*/
|
*/
|
||||||
class RemoveConnectionOnFirstFailureLocalFailureDetector extends FailureDetector {
|
|
||||||
|
|
||||||
case class State(version: Long, iterable: Iterable[ActorRef]) extends VersionedIterable[ActorRef]
|
|
||||||
|
|
||||||
private val state = new AtomicReference[State]
|
|
||||||
|
|
||||||
def this(connectionIterable: Iterable[ActorRef]) = {
|
|
||||||
this()
|
|
||||||
state.set(State(Long.MinValue, connectionIterable))
|
|
||||||
}
|
|
||||||
|
|
||||||
def isAvailable(connection: InetSocketAddress): Boolean =
|
|
||||||
state.get.iterable.find(c ⇒ connection == c).isDefined
|
|
||||||
|
|
||||||
def recordSuccess(connection: InetSocketAddress, timestamp: Long) {}
|
|
||||||
|
|
||||||
def recordFailure(connection: InetSocketAddress, timestamp: Long) {}
|
|
||||||
|
|
||||||
def version: Long = state.get.version
|
|
||||||
|
|
||||||
def size: Int = state.get.iterable.size
|
|
||||||
|
|
||||||
def versionedIterable = state.get
|
|
||||||
|
|
||||||
def stopAll() {
|
|
||||||
state.get.iterable foreach (_.stop())
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def remove(ref: ActorRef) = {
|
|
||||||
val oldState = state.get
|
|
||||||
|
|
||||||
//remote the ref from the connections.
|
|
||||||
var newList = oldState.iterable.filter(currentActorRef ⇒ currentActorRef ne ref)
|
|
||||||
|
|
||||||
if (newList.size != oldState.iterable.size) {
|
|
||||||
//one or more occurrences of the actorRef were removed, so we need to update the state.
|
|
||||||
|
|
||||||
val newState = State(oldState.version + 1, newList)
|
|
||||||
//if we are not able to update the state, we just try again.
|
|
||||||
if (!state.compareAndSet(oldState, newState)) remove(ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def failOver(from: InetSocketAddress, to: InetSocketAddress) {} // do nothing here
|
|
||||||
|
|
||||||
def putIfAbsent(address: InetSocketAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef = {
|
|
||||||
throw new UnsupportedOperationException("Not supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Routing {
|
object Routing {
|
||||||
|
|
||||||
sealed trait RoutingMessage
|
sealed trait RoutingMessage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to broadcast a message to all connections in a router. E.g. every connection gets the message
|
||||||
|
* regardless of their routing algorithm.
|
||||||
|
*/
|
||||||
case class Broadcast(message: Any) extends RoutingMessage
|
case class Broadcast(message: Any) extends RoutingMessage
|
||||||
|
|
||||||
}
|
def createCustomRouter(implClass: String): Router = {
|
||||||
|
ReflectiveAccess.createInstance(
|
||||||
/**
|
implClass,
|
||||||
* A Helper class to create actor references that use routing.
|
Array[Class[_]](),
|
||||||
*/
|
Array[AnyRef]()) match {
|
||||||
class Routing(val application: AkkaApplication) {
|
case Right(router) ⇒ router.asInstanceOf[Router]
|
||||||
|
case Left(exception) ⇒
|
||||||
/**
|
val cause = exception match {
|
||||||
* FIXME: will very likely be moved to the ActorRef.
|
case i: InvocationTargetException ⇒ i.getTargetException
|
||||||
*/
|
case _ ⇒ exception
|
||||||
def actorOf(props: RoutedProps, address: String = newUuid().toString): ActorRef = {
|
}
|
||||||
//TODO Implement support for configuring by deployment ID etc
|
throw new ConfigurationException(
|
||||||
//TODO If address matches an already created actor (Ahead-of-time deployed) return that actor
|
"Could not instantiate custom Router of [" +
|
||||||
//TODO If address exists in config, it will override the specified Props (should we attempt to merge?)
|
implClass + "] due to: " +
|
||||||
//TODO If the actor deployed uses a different config, then ignore or throw exception?
|
cause, cause)
|
||||||
|
}
|
||||||
val clusteringEnabled = application.reflective.ClusterModule.isEnabled
|
|
||||||
|
|
||||||
if (clusteringEnabled && !props.localOnly)
|
|
||||||
application.reflective.ClusterModule.newClusteredActorRef(props)
|
|
||||||
else {
|
|
||||||
if (props.connections.isEmpty) //FIXME Shouldn't this be checked when instance is created so that it works with linking instead of barfing?
|
|
||||||
throw new IllegalArgumentException("A routed actorRef can't have an empty connection set")
|
|
||||||
|
|
||||||
new RoutedActorRef(props, address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new started RoutedActorRef that uses routing to deliver a message to one of its connected actors.
|
|
||||||
*
|
|
||||||
* @param actorAddress the address of the ActorRef.
|
|
||||||
* @param connections an Iterable pointing to all connected actor references.
|
|
||||||
* @param routerType the type of routing that should be used.
|
|
||||||
* @throws IllegalArgumentException if the number of connections is zero, or if it depends on the actual router implementation
|
|
||||||
* how many connections it can handle.
|
|
||||||
*/
|
|
||||||
@deprecated("Use 'Routing.actorOf(props: RoutedProps)' instead.", "2.0")
|
|
||||||
def actorOf(actorAddress: String, connections: Iterable[ActorRef], routerType: RouterType): ActorRef = {
|
|
||||||
val router = routerType match {
|
|
||||||
case RouterType.Direct if connections.size > 1 ⇒
|
|
||||||
throw new IllegalArgumentException("A direct router can't have more than 1 connection")
|
|
||||||
|
|
||||||
case RouterType.Direct ⇒
|
|
||||||
new DirectRouter
|
|
||||||
|
|
||||||
case RouterType.Random ⇒
|
|
||||||
new RandomRouter
|
|
||||||
|
|
||||||
case RouterType.RoundRobin ⇒
|
|
||||||
new RoundRobinRouter
|
|
||||||
|
|
||||||
case r ⇒
|
|
||||||
throw new IllegalArgumentException("Unsupported routerType " + r)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connections.size == 0)
|
|
||||||
throw new IllegalArgumentException("To create a routed actor ref, at least one connection is required")
|
|
||||||
|
|
||||||
new RoutedActorRef(
|
|
||||||
new RoutedProps(
|
|
||||||
() ⇒ router,
|
|
||||||
connections,
|
|
||||||
RoutedProps.defaultFailureDetectorFactory,
|
|
||||||
RoutedProps.defaultTimeout, true),
|
|
||||||
actorAddress)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,7 +135,7 @@ private[akka] class RoutedActorRef(val routedProps: RoutedProps, val address: St
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.init(new RemoveConnectionOnFirstFailureLocalFailureDetector(routedProps.connections))
|
router.init(routedProps.connectionManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -257,21 +147,21 @@ private[akka] class RoutedActorRef(val routedProps: RoutedProps, val address: St
|
||||||
trait BasicRouter extends Router {
|
trait BasicRouter extends Router {
|
||||||
|
|
||||||
@volatile
|
@volatile
|
||||||
protected var connections: FailureDetector = _
|
protected var connectionManager: ConnectionManager = _
|
||||||
|
|
||||||
def init(connections: FailureDetector) = {
|
def init(connectionManager: ConnectionManager) = {
|
||||||
this.connections = connections
|
this.connectionManager = connectionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
def route(message: Any)(implicit sender: Option[ActorRef]) = message match {
|
def route(message: Any)(implicit sender: Option[ActorRef]) = message match {
|
||||||
case Routing.Broadcast(message) ⇒
|
case Routing.Broadcast(message) ⇒
|
||||||
//it is a broadcast message, we are going to send to message to all connections.
|
//it is a broadcast message, we are going to send to message to all connections.
|
||||||
connections.versionedIterable.iterable foreach { connection ⇒
|
connectionManager.connections.iterable foreach { connection ⇒
|
||||||
try {
|
try {
|
||||||
connection.!(message)(sender) // we use original sender, so this is essentially a 'forward'
|
connection.!(message)(sender) // we use original sender, so this is essentially a 'forward'
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception ⇒
|
case e: Exception ⇒
|
||||||
connections.remove(connection)
|
connectionManager.remove(connection)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -283,7 +173,7 @@ trait BasicRouter extends Router {
|
||||||
connection.!(message)(sender) // we use original sender, so this is essentially a 'forward'
|
connection.!(message)(sender) // we use original sender, so this is essentially a 'forward'
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception ⇒
|
case e: Exception ⇒
|
||||||
connections.remove(connection)
|
connectionManager.remove(connection)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
case None ⇒
|
case None ⇒
|
||||||
|
|
@ -303,7 +193,7 @@ trait BasicRouter extends Router {
|
||||||
connection.?(message, timeout)(sender).asInstanceOf[Future[T]]
|
connection.?(message, timeout)(sender).asInstanceOf[Future[T]]
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception ⇒
|
case e: Exception ⇒
|
||||||
connections.remove(connection)
|
connectionManager.remove(connection)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
case None ⇒
|
case None ⇒
|
||||||
|
|
@ -329,33 +219,32 @@ class DirectRouter extends BasicRouter {
|
||||||
private val state = new AtomicReference[DirectRouterState]
|
private val state = new AtomicReference[DirectRouterState]
|
||||||
|
|
||||||
lazy val next: Option[ActorRef] = {
|
lazy val next: Option[ActorRef] = {
|
||||||
val currentState = getState
|
val current = currentState
|
||||||
if (currentState.ref == null) None else Some(currentState.ref)
|
if (current.ref == null) None else Some(current.ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME rename all 'getState' methods to 'currentState', non-scala
|
|
||||||
@tailrec
|
@tailrec
|
||||||
private def getState: DirectRouterState = {
|
private def currentState: DirectRouterState = {
|
||||||
val currentState = state.get
|
val current = state.get
|
||||||
|
|
||||||
if (currentState != null && connections.version == currentState.version) {
|
if (current != null && connectionManager.version == current.version) {
|
||||||
//we are lucky since nothing has changed in the connections.
|
//we are lucky since nothing has changed in the connections.
|
||||||
currentState
|
current
|
||||||
} else {
|
} else {
|
||||||
//there has been a change in the connections, or this is the first time this method is called. So we are going to do some updating.
|
//there has been a change in the connections, or this is the first time this method is called. So we are going to do some updating.
|
||||||
|
|
||||||
val versionedIterable = connections.versionedIterable
|
val connections = connectionManager.connections
|
||||||
|
|
||||||
val connectionCount = versionedIterable.iterable.size
|
val connectionCount = connections.iterable.size
|
||||||
if (connectionCount > 1)
|
if (connectionCount > 1)
|
||||||
throw new RoutingException("A DirectRouter can't have more than 1 connected Actor, but found [%s]".format(connectionCount))
|
throw new RoutingException("A DirectRouter can't have more than 1 connected Actor, but found [%s]".format(connectionCount))
|
||||||
|
|
||||||
val newState = new DirectRouterState(versionedIterable.iterable.head, versionedIterable.version)
|
val newState = new DirectRouterState(connections.iterable.head, connections.version)
|
||||||
if (state.compareAndSet(currentState, newState))
|
if (state.compareAndSet(current, newState))
|
||||||
//we are lucky since we just updated the state, so we can send it back as the state to use
|
//we are lucky since we just updated the state, so we can send it back as the state to use
|
||||||
newState
|
newState
|
||||||
else //we failed to update the state, lets try again... better luck next time.
|
else //we failed to update the state, lets try again... better luck next time.
|
||||||
getState
|
currentState // recur
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,28 +263,28 @@ class RandomRouter extends BasicRouter {
|
||||||
//FIXME: threadlocal random?
|
//FIXME: threadlocal random?
|
||||||
private val random = new java.util.Random(System.nanoTime)
|
private val random = new java.util.Random(System.nanoTime)
|
||||||
|
|
||||||
def next: Option[ActorRef] = getState.array match {
|
def next: Option[ActorRef] = currentState.array match {
|
||||||
case a if a.isEmpty ⇒ None
|
case a if a.isEmpty ⇒ None
|
||||||
case a ⇒ Some(a(random.nextInt(a.length)))
|
case a ⇒ Some(a(random.nextInt(a.length)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailrec
|
@tailrec
|
||||||
private def getState: RandomRouterState = {
|
private def currentState: RandomRouterState = {
|
||||||
val currentState = state.get
|
val current = state.get
|
||||||
|
|
||||||
if (currentState != null && currentState.version == connections.version) {
|
if (current != null && current.version == connectionManager.version) {
|
||||||
//we are lucky, since there has not been any change in the connections. So therefor we can use the existing state.
|
//we are lucky, since there has not been any change in the connections. So therefor we can use the existing state.
|
||||||
currentState
|
current
|
||||||
} else {
|
} else {
|
||||||
//there has been a change in connections, or it was the first try, so we need to update the internal state
|
//there has been a change in connections, or it was the first try, so we need to update the internal state
|
||||||
|
|
||||||
val versionedIterable = connections.versionedIterable
|
val connections = connectionManager.connections
|
||||||
val newState = new RandomRouterState(versionedIterable.iterable.toIndexedSeq, versionedIterable.version)
|
val newState = new RandomRouterState(connections.iterable.toIndexedSeq, connections.version)
|
||||||
if (state.compareAndSet(currentState, newState))
|
if (state.compareAndSet(current, newState))
|
||||||
//we are lucky since we just updated the state, so we can send it back as the state to use
|
//we are lucky since we just updated the state, so we can send it back as the state to use
|
||||||
newState
|
newState
|
||||||
else //we failed to update the state, lets try again... better luck next time.
|
else //we failed to update the state, lets try again... better luck next time.
|
||||||
getState
|
currentState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -411,25 +300,25 @@ class RoundRobinRouter extends BasicRouter {
|
||||||
|
|
||||||
private val state = new AtomicReference[RoundRobinState]
|
private val state = new AtomicReference[RoundRobinState]
|
||||||
|
|
||||||
def next: Option[ActorRef] = getState.next
|
def next: Option[ActorRef] = currentState.next
|
||||||
|
|
||||||
@tailrec
|
@tailrec
|
||||||
private def getState: RoundRobinState = {
|
private def currentState: RoundRobinState = {
|
||||||
val currentState = state.get
|
val current = state.get
|
||||||
|
|
||||||
if (currentState != null && currentState.version == connections.version) {
|
if (current != null && current.version == connectionManager.version) {
|
||||||
//we are lucky, since there has not been any change in the connections. So therefor we can use the existing state.
|
//we are lucky, since there has not been any change in the connections. So therefor we can use the existing state.
|
||||||
currentState
|
current
|
||||||
} else {
|
} else {
|
||||||
//there has been a change in connections, or it was the first try, so we need to update the internal state
|
//there has been a change in connections, or it was the first try, so we need to update the internal state
|
||||||
|
|
||||||
val versionedIterable = connections.versionedIterable
|
val connections = connectionManager.connections
|
||||||
val newState = new RoundRobinState(versionedIterable.iterable.toIndexedSeq[ActorRef], versionedIterable.version)
|
val newState = new RoundRobinState(connections.iterable.toIndexedSeq[ActorRef], connections.version)
|
||||||
if (state.compareAndSet(currentState, newState))
|
if (state.compareAndSet(current, newState))
|
||||||
//we are lucky since we just updated the state, so we can send it back as the state to use
|
//we are lucky since we just updated the state, so we can send it back as the state to use
|
||||||
newState
|
newState
|
||||||
else //we failed to update the state, lets try again... better luck next time.
|
else //we failed to update the state, lets try again... better luck next time.
|
||||||
getState
|
currentState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -463,19 +352,20 @@ class RoundRobinRouter extends BasicRouter {
|
||||||
trait ScatterGatherRouter extends BasicRouter with Serializable {
|
trait ScatterGatherRouter extends BasicRouter with Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aggregates the responses into a single Future
|
* Aggregates the responses into a single Future.
|
||||||
|
*
|
||||||
* @param results Futures of the responses from connections
|
* @param results Futures of the responses from connections
|
||||||
*/
|
*/
|
||||||
protected def gather[S, G >: S](results: Iterable[Future[S]]): Future[G]
|
protected def gather[S, G >: S](results: Iterable[Future[S]]): Future[G]
|
||||||
|
|
||||||
private def scatterGather[S, G >: S](message: Any, timeout: Timeout)(implicit sender: Option[ActorRef]): Future[G] = {
|
private def scatterGather[S, G >: S](message: Any, timeout: Timeout)(implicit sender: Option[ActorRef]): Future[G] = {
|
||||||
val responses = connections.versionedIterable.iterable.flatMap { actor ⇒
|
val responses = connectionManager.connections.iterable.flatMap { actor ⇒
|
||||||
try {
|
try {
|
||||||
if (actor.isShutdown) throw new ActorInitializationException("For compatability - check death first")
|
if (actor.isShutdown) throw new ActorInitializationException("For compatability - check death first")
|
||||||
Some(actor.?(message, timeout)(sender).asInstanceOf[Future[S]])
|
Some(actor.?(message, timeout)(sender).asInstanceOf[Future[S]])
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception ⇒
|
case e: Exception ⇒
|
||||||
connections.remove(actor)
|
connectionManager.remove(actor)
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ object Crypt {
|
||||||
|
|
||||||
def hexify(bytes: Array[Byte]): String = {
|
def hexify(bytes: Array[Byte]): String = {
|
||||||
val builder = new StringBuilder
|
val builder = new StringBuilder
|
||||||
bytes.foreach { byte ⇒ builder.append(hex.charAt((byte & 0xF) >> 4)).append(hex.charAt(byte & 0xF)) }
|
bytes.foreach { byte ⇒ builder.append(hex.charAt((byte & 0xF0) >> 4)).append(hex.charAt(byte & 0xF)) }
|
||||||
builder.toString
|
builder.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ package akka.util
|
||||||
|
|
||||||
import annotation.tailrec
|
import annotation.tailrec
|
||||||
|
|
||||||
import java.util.{ Set ⇒ JSet }
|
|
||||||
import java.util.concurrent.{ ConcurrentSkipListSet, ConcurrentHashMap }
|
import java.util.concurrent.{ ConcurrentSkipListSet, ConcurrentHashMap }
|
||||||
|
import java.util.{ Comparator, Set ⇒ JSet }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of a ConcurrentMultiMap
|
* An implementation of a ConcurrentMultiMap
|
||||||
|
|
@ -16,9 +16,13 @@ import java.util.concurrent.{ ConcurrentSkipListSet, ConcurrentHashMap }
|
||||||
*
|
*
|
||||||
* @author Viktor Klang
|
* @author Viktor Klang
|
||||||
*/
|
*/
|
||||||
class Index[K <: AnyRef, V <: AnyRef: Manifest] {
|
class Index[K, V](val mapSize: Int, val valueComparator: Comparator[V]) {
|
||||||
private val Naught = Array[V]() //Nil for Arrays
|
|
||||||
private val container = new ConcurrentHashMap[K, JSet[V]]
|
def this(mapSize: Int, cmp: (V, V) ⇒ Int) = this(mapSize, new Comparator[V] {
|
||||||
|
def compare(a: V, b: V): Int = cmp(a, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
private val container = new ConcurrentHashMap[K, JSet[V]](mapSize)
|
||||||
private val emptySet = new ConcurrentSkipListSet[V]
|
private val emptySet = new ConcurrentSkipListSet[V]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -42,7 +46,7 @@ class Index[K <: AnyRef, V <: AnyRef: Manifest] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val newSet = new ConcurrentSkipListSet[V]
|
val newSet = new ConcurrentSkipListSet[V](valueComparator)
|
||||||
newSet add v
|
newSet add v
|
||||||
|
|
||||||
// Parry for two simultaneous putIfAbsent(id,newSet)
|
// Parry for two simultaneous putIfAbsent(id,newSet)
|
||||||
|
|
@ -65,15 +69,6 @@ class Index[K <: AnyRef, V <: AnyRef: Manifest] {
|
||||||
spinPut(key, value)
|
spinPut(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a _new_ array of all existing values for the given key at the time of the call
|
|
||||||
*/
|
|
||||||
def values(key: K): Array[V] = {
|
|
||||||
val set: JSet[V] = container get key
|
|
||||||
val result = if (set ne null) set toArray Naught else Naught
|
|
||||||
result.asInstanceOf[Array[V]]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Some(value) for the first matching value where the supplied function returns true for the given key,
|
* @return Some(value) for the first matching value where the supplied function returns true for the given key,
|
||||||
* if no matches it returns None
|
* if no matches it returns None
|
||||||
|
|
@ -85,6 +80,16 @@ class Index[K <: AnyRef, V <: AnyRef: Manifest] {
|
||||||
else None
|
else None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an Iterator of V containing the values for the supplied key, or an empty iterator if the key doesn't exist
|
||||||
|
*/
|
||||||
|
def valueIterator(key: K): scala.Iterator[V] = {
|
||||||
|
container.get(key) match {
|
||||||
|
case null ⇒ Iterator.empty
|
||||||
|
case some ⇒ scala.collection.JavaConversions.asScalaIterator(some.iterator())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the supplied function to all keys and their values
|
* Applies the supplied function to all keys and their values
|
||||||
*/
|
*/
|
||||||
|
|
@ -112,6 +117,10 @@ class Index[K <: AnyRef, V <: AnyRef: Manifest] {
|
||||||
} else false //Remove failed
|
} else false //Remove failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disassociates all the values for the specified key
|
||||||
|
* @returns None if the key wasn't associated at all, or Some(scala.Iterable[V]) if it was associated
|
||||||
|
*/
|
||||||
def remove(key: K): Option[Iterable[V]] = {
|
def remove(key: K): Option[Iterable[V]] = {
|
||||||
val set = container get key
|
val set = container get key
|
||||||
|
|
||||||
|
|
@ -123,6 +132,26 @@ class Index[K <: AnyRef, V <: AnyRef: Manifest] {
|
||||||
} else None //Remove failed
|
} else None //Remove failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified value from all keys
|
||||||
|
*/
|
||||||
|
def removeValue(value: V): Unit = {
|
||||||
|
val i = container.entrySet().iterator()
|
||||||
|
while (i.hasNext) {
|
||||||
|
val e = i.next()
|
||||||
|
val set = e.getValue()
|
||||||
|
|
||||||
|
if (set ne null) {
|
||||||
|
set.synchronized {
|
||||||
|
if (set.remove(value)) { //If we can remove the value
|
||||||
|
if (set.isEmpty) //and the set becomes empty
|
||||||
|
container.remove(e.getKey, emptySet) //We try to remove the key if it's mapped to an empty set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the underlying containers is empty, may report false negatives when the last remove is underway
|
* @return true if the underlying containers is empty, may report false negatives when the last remove is underway
|
||||||
*/
|
*/
|
||||||
|
|
@ -131,5 +160,21 @@ class Index[K <: AnyRef, V <: AnyRef: Manifest] {
|
||||||
/**
|
/**
|
||||||
* Removes all keys and all values
|
* Removes all keys and all values
|
||||||
*/
|
*/
|
||||||
def clear = foreach { case (k, v) ⇒ remove(k, v) }
|
def clear(): Unit = {
|
||||||
|
val i = container.entrySet().iterator()
|
||||||
|
while (i.hasNext) {
|
||||||
|
val e = i.next()
|
||||||
|
val set = e.getValue()
|
||||||
|
if (set ne null) { set.synchronized { set.clear(); container.remove(e.getKey, emptySet) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of a ConcurrentMultiMap
|
||||||
|
* Adds/remove is serialized over the specified key
|
||||||
|
* Reads are fully concurrent <-- el-cheapo
|
||||||
|
*
|
||||||
|
* @author Viktor Klang
|
||||||
|
*/
|
||||||
|
class ConcurrentMultiMap[K, V](mapSize: Int, valueComparator: Comparator[V]) extends Index[K, V](mapSize, valueComparator)
|
||||||
|
|
|
||||||
|
|
@ -1370,7 +1370,7 @@ class DefaultClusterNode private[akka] (
|
||||||
|
|
||||||
private[cluster] def failOverClusterActorRefConnections(from: InetSocketAddress, to: InetSocketAddress) {
|
private[cluster] def failOverClusterActorRefConnections(from: InetSocketAddress, to: InetSocketAddress) {
|
||||||
EventHandler.info(this, "Failing over ClusterActorRef from %s to %s".format(from, to))
|
EventHandler.info(this, "Failing over ClusterActorRef from %s to %s".format(from, to))
|
||||||
clusterActorRefs.values(from) foreach (_.failOver(from, to))
|
clusterActorRefs.valueIterator(from) foreach (_.failOver(from, to))
|
||||||
}
|
}
|
||||||
|
|
||||||
private[cluster] def migrateActorsOnFailedNodes(
|
private[cluster] def migrateActorsOnFailedNodes(
|
||||||
|
|
|
||||||
|
|
@ -48,17 +48,22 @@ htmlhelp_basename = 'Akkadoc'
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
from sphinx.util.texescape import tex_replacements
|
||||||
|
tex_replacements.append((u'⇒', ur'\(\Rightarrow\)'))
|
||||||
|
|
||||||
latex_paper_size = 'a4'
|
latex_paper_size = 'a4'
|
||||||
latex_font_size = '10pt'
|
latex_font_size = '10pt'
|
||||||
|
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'Akka.tex', u' Akka Documentation',
|
('index', 'Akka.tex', u' Akka Documentation',
|
||||||
u'Scalable Solutions AB', 'manual'),
|
u'Typesafe Inc', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
'classoptions': ',oneside,openany',
|
'classoptions': ',oneside,openany',
|
||||||
'babel': '\\usepackage[english]{babel}',
|
'babel': '\\usepackage[english]{babel}',
|
||||||
|
'fontpkg': '\\PassOptionsToPackage{warn}{textcomp} \\usepackage{times}',
|
||||||
'preamble': '\\definecolor{VerbatimColor}{rgb}{0.935,0.935,0.935}'
|
'preamble': '\\definecolor{VerbatimColor}{rgb}{0.935,0.935,0.935}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ object Pi extends App {
|
||||||
val workers = Vector.fill(nrOfWorkers)(actorOf[Worker])
|
val workers = Vector.fill(nrOfWorkers)(actorOf[Worker])
|
||||||
|
|
||||||
// wrap them with a load-balancing router
|
// wrap them with a load-balancing router
|
||||||
val router = Routing.actorOf(RoutedProps().withRoundRobinRouter.withConnections(workers), "pi")
|
val router = Routing.actorOf(RoutedProps().withRoundRobinRouter.withLocalConnections(workers), "pi")
|
||||||
|
|
||||||
loadBalancerActor(CyclicIterator(workers))
|
loadBalancerActor(CyclicIterator(workers))
|
||||||
//#create-workers
|
//#create-workers
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ object Pi extends App {
|
||||||
val workers = Vector.fill(nrOfWorkers)(app.createActor[Worker])
|
val workers = Vector.fill(nrOfWorkers)(app.createActor[Worker])
|
||||||
|
|
||||||
// wrap them with a load-balancing router
|
// wrap them with a load-balancing router
|
||||||
val router = app.routing.actorOf(RoutedProps().withRoundRobinRouter.withConnections(workers), "pi")
|
val router = app.createActor(RoutedProps().withRoundRobinRouter.withLocalConnections(workers), "pi")
|
||||||
//#create-workers
|
//#create-workers
|
||||||
|
|
||||||
//#master-receive
|
//#master-receive
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ trait RootEndpointLocator {
|
||||||
|
|
||||||
def configureRoot(address: String) {
|
def configureRoot(address: String) {
|
||||||
def findRoot(address: String): ActorRef =
|
def findRoot(address: String): ActorRef =
|
||||||
app.registry.actorFor(address).getOrElse(
|
app.provider.actorFor(address).getOrElse(
|
||||||
throw new ConfigurationException("akka.http.root-actor-id configuration option does not have a valid actor address [" + address + "]"))
|
throw new ConfigurationException("akka.http.root-actor-id configuration option does not have a valid actor address [" + address + "]"))
|
||||||
|
|
||||||
root = if ((address eq null) || address == "") findRoot(app.MistSettings.RootActorID) else findRoot(address)
|
root = if ((address eq null) || address == "") findRoot(app.MistSettings.RootActorID) else findRoot(address)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.remote
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
import scala.collection.immutable.Map
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of 'The Phi Accrual Failure Detector' by Hayashibara et al. as defined in their paper:
|
||||||
|
* [http://ddg.jaist.ac.jp/pub/HDY+04.pdf]
|
||||||
|
* <p/>
|
||||||
|
* A low threshold is prone to generate many wrong suspicions but ensures a quick detection in the event
|
||||||
|
* of a real crash. Conversely, a high threshold generates fewer mistakes but needs more time to detect
|
||||||
|
* actual crashes
|
||||||
|
* <p/>
|
||||||
|
* For example a threshold of:
|
||||||
|
* - 1 => 10% error rate
|
||||||
|
* - 2 => 1% error rate
|
||||||
|
* - 3 => 0.1% error rate -
|
||||||
|
* <p/>
|
||||||
|
* This means that for example a threshold of 3 => no heartbeat for > 6 seconds => node marked as dead/not available.
|
||||||
|
* <p/>
|
||||||
|
* Default threshold is 8 (taken from Cassandra defaults), but can be configured in the Akka config.
|
||||||
|
*/
|
||||||
|
class AccrualFailureDetector(
|
||||||
|
val threshold: Int = 8, // FIXME make these configurable
|
||||||
|
val maxSampleSize: Int = 1000) extends FailureDetector {
|
||||||
|
|
||||||
|
private case class FailureStats(mean: Double = 0.0D, variance: Double = 0.0D, deviation: Double = 0.0D)
|
||||||
|
|
||||||
|
// Implement using optimistic lockless concurrency, all state is represented
|
||||||
|
// by this immutable case class and managed by an AtomicReference
|
||||||
|
private case class State(
|
||||||
|
version: Long = 0L,
|
||||||
|
failureStats: Map[InetSocketAddress, FailureStats] = Map.empty[InetSocketAddress, FailureStats],
|
||||||
|
intervalHistory: Map[InetSocketAddress, Vector[Long]] = Map.empty[InetSocketAddress, Vector[Long]],
|
||||||
|
timestamps: Map[InetSocketAddress, Long] = Map.empty[InetSocketAddress, Long])
|
||||||
|
|
||||||
|
private val state = new AtomicReference[State](State())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the connection is considered to be up and healthy
|
||||||
|
* and returns false otherwise.
|
||||||
|
*/
|
||||||
|
def isAvailable(connection: InetSocketAddress): Boolean = phi(connection) < threshold
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a heartbeat for a connection.
|
||||||
|
*/
|
||||||
|
@tailrec
|
||||||
|
final def heartbeat(connection: InetSocketAddress) {
|
||||||
|
val oldState = state.get
|
||||||
|
|
||||||
|
val latestTimestamp = oldState.timestamps.get(connection)
|
||||||
|
if (latestTimestamp.isEmpty) {
|
||||||
|
|
||||||
|
// this is heartbeat from a new connection
|
||||||
|
// add starter records for this new connection
|
||||||
|
val failureStats = oldState.failureStats + (connection -> FailureStats())
|
||||||
|
val intervalHistory = oldState.intervalHistory + (connection -> Vector.empty[Long])
|
||||||
|
val timestamps = oldState.timestamps + (connection -> newTimestamp)
|
||||||
|
|
||||||
|
val newState = oldState copy (version = oldState.version + 1,
|
||||||
|
failureStats = failureStats,
|
||||||
|
intervalHistory = intervalHistory,
|
||||||
|
timestamps = timestamps)
|
||||||
|
|
||||||
|
// if we won the race then update else try again
|
||||||
|
if (!state.compareAndSet(oldState, newState)) heartbeat(connection) // recur
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// this is a known connection
|
||||||
|
val timestamp = newTimestamp
|
||||||
|
val interval = timestamp - latestTimestamp.get
|
||||||
|
|
||||||
|
val timestamps = oldState.timestamps + (connection -> timestamp) // record new timestamp
|
||||||
|
|
||||||
|
var newIntervalsForConnection =
|
||||||
|
oldState.intervalHistory.get(connection).getOrElse(Vector.empty[Long]) :+ interval // append the new interval to history
|
||||||
|
|
||||||
|
if (newIntervalsForConnection.size > maxSampleSize) {
|
||||||
|
// reached max history, drop first interval
|
||||||
|
newIntervalsForConnection = newIntervalsForConnection drop 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val failureStats =
|
||||||
|
if (newIntervalsForConnection.size > 1) {
|
||||||
|
|
||||||
|
val mean: Double = newIntervalsForConnection.sum / newIntervalsForConnection.size.toDouble
|
||||||
|
|
||||||
|
val oldFailureStats = oldState.failureStats.get(connection).getOrElse(FailureStats())
|
||||||
|
|
||||||
|
val deviationSum =
|
||||||
|
newIntervalsForConnection
|
||||||
|
.map(_.toDouble)
|
||||||
|
.foldLeft(0.0D)((x, y) ⇒ x + (y - mean))
|
||||||
|
|
||||||
|
val variance: Double = deviationSum / newIntervalsForConnection.size.toDouble
|
||||||
|
val deviation: Double = math.sqrt(variance)
|
||||||
|
|
||||||
|
val newFailureStats = oldFailureStats copy (mean = mean,
|
||||||
|
deviation = deviation,
|
||||||
|
variance = variance)
|
||||||
|
|
||||||
|
oldState.failureStats + (connection -> newFailureStats)
|
||||||
|
} else {
|
||||||
|
oldState.failureStats
|
||||||
|
}
|
||||||
|
|
||||||
|
val intervalHistory = oldState.intervalHistory + (connection -> newIntervalsForConnection)
|
||||||
|
|
||||||
|
val newState = oldState copy (version = oldState.version + 1,
|
||||||
|
failureStats = failureStats,
|
||||||
|
intervalHistory = intervalHistory,
|
||||||
|
timestamps = timestamps)
|
||||||
|
|
||||||
|
// if we won the race then update else try again
|
||||||
|
if (!state.compareAndSet(oldState, newState)) heartbeat(connection) // recur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates how likely it is that the connection has failed.
|
||||||
|
* <p/>
|
||||||
|
* If a connection does not have any records in failure detector then it is
|
||||||
|
* considered dead. This is true either if the heartbeat have not started
|
||||||
|
* yet or the connection have been explicitly removed.
|
||||||
|
*/
|
||||||
|
def phi(connection: InetSocketAddress): Double = {
|
||||||
|
val oldState = state.get
|
||||||
|
val oldTimestamp = oldState.timestamps.get(connection)
|
||||||
|
|
||||||
|
if (oldTimestamp.isEmpty) Double.MaxValue
|
||||||
|
else {
|
||||||
|
-1 * math.log10(
|
||||||
|
probability(
|
||||||
|
connection,
|
||||||
|
newTimestamp - oldTimestamp.get,
|
||||||
|
oldState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the heartbeat management for a connection.
|
||||||
|
*/
|
||||||
|
@tailrec
|
||||||
|
final def remove(connection: InetSocketAddress) {
|
||||||
|
val oldState = state.get
|
||||||
|
|
||||||
|
if (oldState.failureStats.contains(connection)) {
|
||||||
|
val failureStats = oldState.failureStats - connection
|
||||||
|
val intervalHistory = oldState.intervalHistory - connection
|
||||||
|
val timestamps = oldState.timestamps - connection
|
||||||
|
|
||||||
|
val newState = oldState copy (version = oldState.version + 1,
|
||||||
|
failureStats = failureStats,
|
||||||
|
intervalHistory = intervalHistory,
|
||||||
|
timestamps = timestamps)
|
||||||
|
|
||||||
|
// if we won the race then update else try again
|
||||||
|
if (!state.compareAndSet(oldState, newState)) remove(connection) // recur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def probability(connection: InetSocketAddress, timestamp: Long, oldState: State): Double = {
|
||||||
|
val statsForConnection = oldState.failureStats.get(connection).getOrElse(FailureStats())
|
||||||
|
val exponent = -1.0 * timestamp / statsForConnection.mean
|
||||||
|
1 - (1.0 - math.pow(math.E, exponent))
|
||||||
|
}
|
||||||
|
|
||||||
|
def recordSuccess(connection: InetSocketAddress, timestamp: Long) {}
|
||||||
|
def recordFailure(connection: InetSocketAddress, timestamp: Long) {}
|
||||||
|
def notify(event: RemoteLifeCycleEvent) {}
|
||||||
|
}
|
||||||
|
|
||||||
230
akka-remote/src/main/scala/akka/remote/FailureDetector.scala
Normal file
230
akka-remote/src/main/scala/akka/remote/FailureDetector.scala
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.remote
|
||||||
|
|
||||||
|
import akka.AkkaException
|
||||||
|
import akka.actor._
|
||||||
|
import akka.event.EventHandler
|
||||||
|
import akka.config.ConfigurationException
|
||||||
|
import akka.actor.UntypedChannel._
|
||||||
|
import akka.dispatch.Future
|
||||||
|
import akka.util.ReflectiveAccess
|
||||||
|
import akka.util.Duration
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.util.concurrent.atomic.{ AtomicReference, AtomicInteger }
|
||||||
|
|
||||||
|
import scala.collection.immutable.Map
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The failure detector uses different heuristics (depending on implementation) to try to detect and manage
|
||||||
|
* failed connections.
|
||||||
|
*
|
||||||
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
|
*/
|
||||||
|
trait FailureDetector extends NetworkEventStream.Listener {
|
||||||
|
|
||||||
|
def newTimestamp: Long = System.currentTimeMillis
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the 'connection' is considered available.
|
||||||
|
*/
|
||||||
|
def isAvailable(connection: InetSocketAddress): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a successful connection.
|
||||||
|
*/
|
||||||
|
def recordSuccess(connection: InetSocketAddress, timestamp: Long)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a failed connection.
|
||||||
|
*/
|
||||||
|
def recordFailure(connection: InetSocketAddress, timestamp: Long)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Misc helper and factory methods for failure detection.
|
||||||
|
*/
|
||||||
|
object FailureDetector {
|
||||||
|
|
||||||
|
def createCustomFailureDetector(implClass: String): FailureDetector = {
|
||||||
|
|
||||||
|
ReflectiveAccess.createInstance(
|
||||||
|
implClass,
|
||||||
|
Array[Class[_]](),
|
||||||
|
Array[AnyRef]()) match {
|
||||||
|
case Right(failureDetector) ⇒ failureDetector
|
||||||
|
case Left(exception) ⇒
|
||||||
|
val cause = exception match {
|
||||||
|
case i: InvocationTargetException ⇒ i.getTargetException
|
||||||
|
case _ ⇒ exception
|
||||||
|
}
|
||||||
|
throw new ConfigurationException(
|
||||||
|
"Could not instantiate custom FailureDetector of [" +
|
||||||
|
implClass + "] due to: " +
|
||||||
|
cause, cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-op failure detector. Does not do anything.
|
||||||
|
*/
|
||||||
|
class NoOpFailureDetector extends FailureDetector {
|
||||||
|
|
||||||
|
def isAvailable(connection: InetSocketAddress): Boolean = true
|
||||||
|
|
||||||
|
def recordSuccess(connection: InetSocketAddress, timestamp: Long) {}
|
||||||
|
|
||||||
|
def recordFailure(connection: InetSocketAddress, timestamp: Long) {}
|
||||||
|
|
||||||
|
def notify(event: RemoteLifeCycleEvent) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple failure detector that removes the failing connection permanently on first error.
|
||||||
|
*/
|
||||||
|
class RemoveConnectionOnFirstFailureFailureDetector extends FailureDetector {
|
||||||
|
|
||||||
|
protected case class State(version: Long, banned: Set[InetSocketAddress])
|
||||||
|
|
||||||
|
protected val state: AtomicReference[State] = new AtomicReference[State](newState())
|
||||||
|
|
||||||
|
protected def newState() = State(Long.MinValue, Set.empty[InetSocketAddress])
|
||||||
|
|
||||||
|
def isAvailable(connectionAddress: InetSocketAddress): Boolean = state.get.banned.contains(connectionAddress)
|
||||||
|
|
||||||
|
final def recordSuccess(connectionAddress: InetSocketAddress, timestamp: Long) {}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
final def recordFailure(connectionAddress: InetSocketAddress, timestamp: Long) {
|
||||||
|
val oldState = state.get
|
||||||
|
if (!oldState.banned.contains(connectionAddress)) {
|
||||||
|
val newBannedConnections = oldState.banned + connectionAddress
|
||||||
|
val newState = oldState copy (version = oldState.version + 1, banned = newBannedConnections)
|
||||||
|
if (!state.compareAndSet(oldState, newState)) recordFailure(connectionAddress, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkEventStream.Listener callback
|
||||||
|
def notify(event: RemoteLifeCycleEvent) = event match {
|
||||||
|
case RemoteClientWriteFailed(request, cause, client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientError(cause, client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientDisconnected(client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientShutdown(client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case _ ⇒ {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Failure detector that bans the failing connection for 'timeToBan: Duration' and will try to use the connection
|
||||||
|
* again after the ban period have expired.
|
||||||
|
*
|
||||||
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
|
*/
|
||||||
|
class BannagePeriodFailureDetector(timeToBan: Duration) extends FailureDetector with NetworkEventStream.Listener {
|
||||||
|
|
||||||
|
// FIXME considering adding a Scheduler event to notify the BannagePeriodFailureDetector unban the banned connection after the timeToBan have exprired
|
||||||
|
|
||||||
|
protected case class State(version: Long, banned: Map[InetSocketAddress, BannedConnection])
|
||||||
|
|
||||||
|
protected val state: AtomicReference[State] = new AtomicReference[State](newState())
|
||||||
|
|
||||||
|
case class BannedConnection(bannedSince: Long, address: InetSocketAddress)
|
||||||
|
|
||||||
|
val timeToBanInMillis = timeToBan.toMillis
|
||||||
|
|
||||||
|
protected def newState() = State(Long.MinValue, Map.empty[InetSocketAddress, BannedConnection])
|
||||||
|
|
||||||
|
private def bannedConnections = state.get.banned
|
||||||
|
|
||||||
|
def isAvailable(connectionAddress: InetSocketAddress): Boolean = bannedConnections.get(connectionAddress).isEmpty
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
final def recordSuccess(connectionAddress: InetSocketAddress, timestamp: Long) {
|
||||||
|
val oldState = state.get
|
||||||
|
val bannedConnection = oldState.banned.get(connectionAddress)
|
||||||
|
|
||||||
|
if (bannedConnection.isDefined) { // is it banned or not?
|
||||||
|
val BannedConnection(bannedSince, banned) = bannedConnection.get
|
||||||
|
val currentlyBannedFor = newTimestamp - bannedSince
|
||||||
|
|
||||||
|
if (currentlyBannedFor > timeToBanInMillis) {
|
||||||
|
val newBannedConnections = oldState.banned - connectionAddress
|
||||||
|
|
||||||
|
val newState = oldState copy (version = oldState.version + 1, banned = newBannedConnections)
|
||||||
|
|
||||||
|
if (!state.compareAndSet(oldState, newState)) recordSuccess(connectionAddress, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
final def recordFailure(connectionAddress: InetSocketAddress, timestamp: Long) {
|
||||||
|
val oldState = state.get
|
||||||
|
val connection = oldState.banned.get(connectionAddress)
|
||||||
|
|
||||||
|
if (connection.isEmpty) { // is it already banned or not?
|
||||||
|
val bannedConnection = BannedConnection(timestamp, connectionAddress)
|
||||||
|
val newBannedConnections = oldState.banned + (connectionAddress -> bannedConnection)
|
||||||
|
|
||||||
|
val newState = oldState copy (version = oldState.version + 1, banned = newBannedConnections)
|
||||||
|
|
||||||
|
if (!state.compareAndSet(oldState, newState)) recordFailure(connectionAddress, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkEventStream.Listener callback
|
||||||
|
def notify(event: RemoteLifeCycleEvent) = event match {
|
||||||
|
case RemoteClientStarted(client, connectionAddress) ⇒
|
||||||
|
recordSuccess(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientConnected(client, connectionAddress) ⇒
|
||||||
|
recordSuccess(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientWriteFailed(request, cause, client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientError(cause, client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientDisconnected(client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case RemoteClientShutdown(client, connectionAddress) ⇒
|
||||||
|
recordFailure(connectionAddress, newTimestamp)
|
||||||
|
|
||||||
|
case _ ⇒ {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Failure detector that uses the Circuit Breaker pattern to detect and recover from failing connections.
|
||||||
|
*
|
||||||
|
* class CircuitBreakerNetworkEventStream.Listener(initialConnections: Map[InetSocketAddress, ActorRef])
|
||||||
|
* extends RemoteConnectionManager(initialConnections) {
|
||||||
|
*
|
||||||
|
* def newState() = State(Long.MinValue, initialConnections, None)
|
||||||
|
*
|
||||||
|
* def isAvailable(connectionAddress: InetSocketAddress): Boolean = connections.get(connectionAddress).isDefined
|
||||||
|
*
|
||||||
|
* def recordSuccess(connectionAddress: InetSocketAddress, timestamp: Long) {}
|
||||||
|
*
|
||||||
|
* def recordFailure(connectionAddress: InetSocketAddress, timestamp: Long) {}
|
||||||
|
*
|
||||||
|
* // FIXME implement CircuitBreakerNetworkEventStream.Listener
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
@ -6,9 +6,8 @@ package akka.remote
|
||||||
|
|
||||||
import akka.actor._
|
import akka.actor._
|
||||||
import akka.routing._
|
import akka.routing._
|
||||||
import DeploymentConfig._
|
import akka.actor.Actor._
|
||||||
import Actor._
|
import akka.actor.Status._
|
||||||
import Status._
|
|
||||||
import akka.event.EventHandler
|
import akka.event.EventHandler
|
||||||
import akka.util.duration._
|
import akka.util.duration._
|
||||||
import akka.config.ConfigurationException
|
import akka.config.ConfigurationException
|
||||||
|
|
@ -35,7 +34,10 @@ class RemoteActorRefProvider(val app: AkkaApplication, val remote: Remote) exten
|
||||||
|
|
||||||
private val actors = new ConcurrentHashMap[String, Promise[Option[ActorRef]]]
|
private val actors = new ConcurrentHashMap[String, Promise[Option[ActorRef]]]
|
||||||
|
|
||||||
private val failureDetector = new BannagePeriodFailureDetector(app, remote, timeToBan = 60 seconds) // FIXME make timeToBan configurable
|
private val remoteDaemonConnectionManager = new RemoteConnectionManager(
|
||||||
|
app,
|
||||||
|
remote = remote,
|
||||||
|
failureDetector = new BannagePeriodFailureDetector(60 seconds)) // FIXME make timeout configurable
|
||||||
|
|
||||||
def actorOf(props: Props, address: String): Option[ActorRef] = {
|
def actorOf(props: Props, address: String): Option[ActorRef] = {
|
||||||
Address.validate(address)
|
Address.validate(address)
|
||||||
|
|
@ -44,9 +46,17 @@ class RemoteActorRefProvider(val app: AkkaApplication, val remote: Remote) exten
|
||||||
val oldFuture = actors.putIfAbsent(address, newFuture)
|
val oldFuture = actors.putIfAbsent(address, newFuture)
|
||||||
|
|
||||||
if (oldFuture eq null) { // we won the race -- create the actor and resolve the future
|
if (oldFuture eq null) { // we won the race -- create the actor and resolve the future
|
||||||
|
val deploymentConfig = app.deployer.deploymentConfig
|
||||||
val actor = try {
|
val actor = try {
|
||||||
app.deployer.lookupDeploymentFor(address) match {
|
app.deployer.lookupDeploymentFor(address) match {
|
||||||
case Some(Deploy(_, _, router, nrOfInstances, _, app.deployer.deploymentConfig.RemoteScope(remoteAddresses))) ⇒
|
case Some(DeploymentConfig.Deploy(_, _, routerType, nrOfInstances, failureDetectorType, deploymentConfig.RemoteScope(remoteAddresses))) ⇒
|
||||||
|
|
||||||
|
val failureDetector = DeploymentConfig.failureDetectorTypeFor(failureDetectorType) match {
|
||||||
|
case FailureDetectorType.NoOp ⇒ new NoOpFailureDetector
|
||||||
|
case FailureDetectorType.RemoveConnectionOnFirstFailure ⇒ new RemoveConnectionOnFirstFailureFailureDetector
|
||||||
|
case FailureDetectorType.BannagePeriod(timeToBan) ⇒ new BannagePeriodFailureDetector(timeToBan)
|
||||||
|
case FailureDetectorType.Custom(implClass) ⇒ FailureDetector.createCustomFailureDetector(implClass)
|
||||||
|
}
|
||||||
|
|
||||||
val thisHostname = remote.address.getHostName
|
val thisHostname = remote.address.getHostName
|
||||||
val thisPort = remote.address.getPort
|
val thisPort = remote.address.getPort
|
||||||
|
|
@ -61,8 +71,7 @@ class RemoteActorRefProvider(val app: AkkaApplication, val remote: Remote) exten
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// we are on the single "reference" node uses the remote actors on the replica nodes
|
// we are on the single "reference" node uses the remote actors on the replica nodes
|
||||||
val routerType = DeploymentConfig.routerTypeFor(router)
|
val routerFactory: () ⇒ Router = DeploymentConfig.routerTypeFor(routerType) match {
|
||||||
val routerFactory: () ⇒ Router = routerType match {
|
|
||||||
case RouterType.Direct ⇒
|
case RouterType.Direct ⇒
|
||||||
if (remoteAddresses.size != 1) throw new ConfigurationException(
|
if (remoteAddresses.size != 1) throw new ConfigurationException(
|
||||||
"Actor [%s] configured with Direct router must have exactly 1 remote node configured. Found [%s]"
|
"Actor [%s] configured with Direct router must have exactly 1 remote node configured. Found [%s]"
|
||||||
|
|
@ -81,23 +90,31 @@ class RemoteActorRefProvider(val app: AkkaApplication, val remote: Remote) exten
|
||||||
.format(address, remoteAddresses.mkString(", ")))
|
.format(address, remoteAddresses.mkString(", ")))
|
||||||
() ⇒ new RoundRobinRouter
|
() ⇒ new RoundRobinRouter
|
||||||
|
|
||||||
case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet")
|
case RouterType.ScatterGather ⇒
|
||||||
case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet")
|
if (remoteAddresses.size < 1) throw new ConfigurationException(
|
||||||
case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet")
|
"Actor [%s] configured with ScatterGather router must have at least 1 remote node configured. Found [%s]"
|
||||||
case RouterType.Custom ⇒ sys.error("Router Custom not supported yet")
|
.format(address, remoteAddresses.mkString(", ")))
|
||||||
|
() ⇒ new ScatterGatherFirstCompletedRouter
|
||||||
|
|
||||||
|
case RouterType.LeastCPU ⇒ sys.error("Router LeastCPU not supported yet")
|
||||||
|
case RouterType.LeastRAM ⇒ sys.error("Router LeastRAM not supported yet")
|
||||||
|
case RouterType.LeastMessages ⇒ sys.error("Router LeastMessages not supported yet")
|
||||||
|
case RouterType.Custom(implClass) ⇒ () ⇒ Routing.createCustomRouter(implClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
def provisionActorToNode(remoteAddress: RemoteAddress): RemoteActorRef = {
|
var connections = Map.empty[InetSocketAddress, ActorRef]
|
||||||
|
remoteAddresses foreach { remoteAddress: DeploymentConfig.RemoteAddress ⇒
|
||||||
val inetSocketAddress = new InetSocketAddress(remoteAddress.hostname, remoteAddress.port)
|
val inetSocketAddress = new InetSocketAddress(remoteAddress.hostname, remoteAddress.port)
|
||||||
useActorOnNode(inetSocketAddress, address, props.creator)
|
connections += (inetSocketAddress -> RemoteActorRef(app.remote, inetSocketAddress, address, None))
|
||||||
RemoteActorRef(app, app.remote, inetSocketAddress, address, None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val connections: Iterable[ActorRef] = remoteAddresses map { provisionActorToNode(_) }
|
val connectionManager = new RemoteConnectionManager(app, remote, connections, failureDetector)
|
||||||
|
|
||||||
Some(app.routing.actorOf(RoutedProps(
|
connections.keys foreach { useActorOnNode(_, address, props.creator) }
|
||||||
|
|
||||||
|
Some(app.createActor(RoutedProps(
|
||||||
routerFactory = routerFactory,
|
routerFactory = routerFactory,
|
||||||
connections = connections)))
|
connectionManager = connectionManager)))
|
||||||
}
|
}
|
||||||
|
|
||||||
case deploy ⇒ None // non-remote actor
|
case deploy ⇒ None // non-remote actor
|
||||||
|
|
@ -108,7 +125,7 @@ class RemoteActorRefProvider(val app: AkkaApplication, val remote: Remote) exten
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
actor foreach app.registry.register // only for ActorRegistry backward compat, will be removed later
|
// actor foreach app.registry.register // only for ActorRegistry backward compat, will be removed later
|
||||||
|
|
||||||
newFuture completeWithResult actor
|
newFuture completeWithResult actor
|
||||||
actor
|
actor
|
||||||
|
|
@ -118,7 +135,18 @@ class RemoteActorRefProvider(val app: AkkaApplication, val remote: Remote) exten
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def findActorRef(address: String): Option[ActorRef] = throw new UnsupportedOperationException
|
/**
|
||||||
|
* Copied from LocalActorRefProvider...
|
||||||
|
*/
|
||||||
|
def actorOf(props: RoutedProps, address: String): Option[ActorRef] = {
|
||||||
|
if (props.connectionManager.size == 0) throw new ConfigurationException("RoutedProps used for creating actor [" + address + "] has zero connections configured; can't create a router")
|
||||||
|
Some(new RoutedActorRef(props, address))
|
||||||
|
}
|
||||||
|
|
||||||
|
def actorFor(address: String): Option[ActorRef] = actors.get(address) match {
|
||||||
|
case null ⇒ None
|
||||||
|
case future ⇒ future.await.resultOrException.getOrElse(None)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the actor was in the provider's cache and evicted successfully, else false.
|
* Returns true if the actor was in the provider's cache and evicted successfully, else false.
|
||||||
|
|
@ -150,7 +178,7 @@ class RemoteActorRefProvider(val app: AkkaApplication, val remote: Remote) exten
|
||||||
remote.remoteDaemonServiceName, remoteAddress.getHostName, remoteAddress.getPort)
|
remote.remoteDaemonServiceName, remoteAddress.getHostName, remoteAddress.getPort)
|
||||||
|
|
||||||
// try to get the connection for the remote address, if not already there then create it
|
// try to get the connection for the remote address, if not already there then create it
|
||||||
val connection = failureDetector.putIfAbsent(remoteAddress, connectionFactory)
|
val connection = remoteDaemonConnectionManager.putIfAbsent(remoteAddress, connectionFactory)
|
||||||
|
|
||||||
sendCommandToRemoteNode(connection, command, withACK = true) // ensure we get an ACK on the USE command
|
sendCommandToRemoteNode(connection, command, withACK = true) // ensure we get an ACK on the USE command
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.remote
|
||||||
|
|
||||||
|
import akka.actor._
|
||||||
|
import akka.routing._
|
||||||
|
import akka.AkkaApplication
|
||||||
|
|
||||||
|
import scala.collection.immutable.Map
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote connection manager, manages remote connections, e.g. RemoteActorRef's.
|
||||||
|
*
|
||||||
|
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
||||||
|
*/
|
||||||
|
class RemoteConnectionManager(
|
||||||
|
app: AkkaApplication,
|
||||||
|
remote: Remote,
|
||||||
|
initialConnections: Map[InetSocketAddress, ActorRef] = Map.empty[InetSocketAddress, ActorRef],
|
||||||
|
failureDetector: FailureDetector = new NoOpFailureDetector)
|
||||||
|
extends ConnectionManager {
|
||||||
|
|
||||||
|
case class State(version: Long, connections: Map[InetSocketAddress, ActorRef])
|
||||||
|
extends VersionedIterable[ActorRef] {
|
||||||
|
def iterable: Iterable[ActorRef] = connections.values
|
||||||
|
}
|
||||||
|
|
||||||
|
private val state: AtomicReference[State] = new AtomicReference[State](newState())
|
||||||
|
|
||||||
|
// register all initial connections - e.g listen to events from them
|
||||||
|
initialConnections.keys foreach (remote.eventStream.register(failureDetector, _))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is using the FailureDetector to filter out connections that are considered not available.
|
||||||
|
*/
|
||||||
|
private def filterAvailableConnections(current: State): State = {
|
||||||
|
val availableConnections = current.connections filter { entry ⇒ failureDetector.isAvailable(entry._1) }
|
||||||
|
current copy (version = current.version, connections = availableConnections)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def newState() = State(Long.MinValue, initialConnections)
|
||||||
|
|
||||||
|
def version: Long = state.get.version
|
||||||
|
|
||||||
|
def connections = filterAvailableConnections(state.get)
|
||||||
|
|
||||||
|
def size: Int = connections.connections.size
|
||||||
|
|
||||||
|
def shutdown() {
|
||||||
|
state.get.iterable foreach (_.stop()) // shut down all remote connections
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
final def failOver(from: InetSocketAddress, to: InetSocketAddress) {
|
||||||
|
app.eventHandler.debug(this, "Failing over connection from [%s] to [%s]".format(from, to))
|
||||||
|
|
||||||
|
val oldState = state.get
|
||||||
|
var changed = false
|
||||||
|
|
||||||
|
val newMap = oldState.connections map {
|
||||||
|
case (`from`, actorRef) ⇒
|
||||||
|
changed = true
|
||||||
|
//actorRef.stop()
|
||||||
|
(to, newConnection(actorRef.address, to))
|
||||||
|
case other ⇒ other
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
//there was a state change, so we are now going to update the state.
|
||||||
|
val newState = oldState copy (version = oldState.version + 1, connections = newMap)
|
||||||
|
|
||||||
|
//if we are not able to update, the state, we are going to try again.
|
||||||
|
if (!state.compareAndSet(oldState, newState)) {
|
||||||
|
failOver(from, to) // recur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
final def remove(faultyConnection: ActorRef) {
|
||||||
|
|
||||||
|
val oldState = state.get()
|
||||||
|
var changed = false
|
||||||
|
|
||||||
|
var faultyAddress: InetSocketAddress = null
|
||||||
|
var newConnections = Map.empty[InetSocketAddress, ActorRef]
|
||||||
|
|
||||||
|
oldState.connections.keys foreach { address ⇒
|
||||||
|
val actorRef: ActorRef = oldState.connections.get(address).get
|
||||||
|
if (actorRef ne faultyConnection) {
|
||||||
|
newConnections = newConnections + ((address, actorRef))
|
||||||
|
} else {
|
||||||
|
faultyAddress = address
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
//one or more occurrances of the actorRef were removed, so we need to update the state.
|
||||||
|
val newState = oldState copy (version = oldState.version + 1, connections = newConnections)
|
||||||
|
|
||||||
|
//if we are not able to update the state, we just try again.
|
||||||
|
if (!state.compareAndSet(oldState, newState)) {
|
||||||
|
remove(faultyConnection) // recur
|
||||||
|
} else {
|
||||||
|
app.eventHandler.debug(this, "Removing connection [%s]".format(faultyAddress))
|
||||||
|
remote.eventStream.unregister(failureDetector, faultyAddress) // unregister the connections - e.g stop listen to events from it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
final def putIfAbsent(address: InetSocketAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef = {
|
||||||
|
|
||||||
|
val oldState = state.get()
|
||||||
|
val oldConnections = oldState.connections
|
||||||
|
|
||||||
|
oldConnections.get(address) match {
|
||||||
|
case Some(connection) ⇒ connection // we already had the connection, return it
|
||||||
|
case None ⇒ // we need to create it
|
||||||
|
val newConnection = newConnectionFactory()
|
||||||
|
val newConnections = oldConnections + (address -> newConnection)
|
||||||
|
|
||||||
|
//one or more occurrances of the actorRef were removed, so we need to update the state.
|
||||||
|
val newState = oldState copy (version = oldState.version + 1, connections = newConnections)
|
||||||
|
|
||||||
|
//if we are not able to update the state, we just try again.
|
||||||
|
if (!state.compareAndSet(oldState, newState)) {
|
||||||
|
// we failed, need compensating action
|
||||||
|
newConnection.stop() // stop the new connection actor and try again
|
||||||
|
putIfAbsent(address, newConnectionFactory) // recur
|
||||||
|
} else {
|
||||||
|
// we succeeded
|
||||||
|
app.eventHandler.debug(this, "Adding connection [%s]".format(address))
|
||||||
|
remote.eventStream.register(failureDetector, address) // register the connection - e.g listen to events from it
|
||||||
|
newConnection // return new connection actor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private[remote] def newConnection(actorAddress: String, inetSocketAddress: InetSocketAddress) = {
|
||||||
|
RemoteActorRef(remote.server, inetSocketAddress, actorAddress, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,381 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.remote
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import Actor._
|
|
||||||
import akka.routing._
|
|
||||||
import akka.dispatch.PinnedDispatcher
|
|
||||||
import akka.event.EventHandler
|
|
||||||
import akka.util.{ ListenerManagement, Duration }
|
|
||||||
import scala.collection.immutable.Map
|
|
||||||
import scala.collection.mutable
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
import System.{ currentTimeMillis ⇒ newTimestamp }
|
|
||||||
import akka.AkkaApplication
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for remote failure detection management.
|
|
||||||
*
|
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
||||||
*/
|
|
||||||
abstract class RemoteFailureDetectorBase(app: AkkaApplication, remote: Remote, initialConnections: Map[InetSocketAddress, ActorRef])
|
|
||||||
extends FailureDetector
|
|
||||||
with NetworkEventStream.Listener {
|
|
||||||
|
|
||||||
type T <: AnyRef
|
|
||||||
|
|
||||||
protected case class State(
|
|
||||||
version: Long,
|
|
||||||
connections: Map[InetSocketAddress, ActorRef],
|
|
||||||
meta: T = null.asInstanceOf[T])
|
|
||||||
extends VersionedIterable[ActorRef] {
|
|
||||||
def iterable: Iterable[ActorRef] = connections.values
|
|
||||||
}
|
|
||||||
|
|
||||||
protected val state: AtomicReference[State] = new AtomicReference[State](newState())
|
|
||||||
|
|
||||||
// register all initial connections - e.g listen to events from them
|
|
||||||
initialConnections.keys foreach (remote.eventStream.register(this, _))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State factory. To be defined by subclass that wants to add extra info in the 'meta: T' field.
|
|
||||||
*/
|
|
||||||
protected def newState(): State
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the 'connection' is considered available.
|
|
||||||
*
|
|
||||||
* To be implemented by subclass.
|
|
||||||
*/
|
|
||||||
def isAvailable(connectionAddress: InetSocketAddress): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records a successful connection.
|
|
||||||
*
|
|
||||||
* To be implemented by subclass.
|
|
||||||
*/
|
|
||||||
def recordSuccess(connectionAddress: InetSocketAddress, timestamp: Long)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records a failed connection.
|
|
||||||
*
|
|
||||||
* To be implemented by subclass.
|
|
||||||
*/
|
|
||||||
def recordFailure(connectionAddress: InetSocketAddress, timestamp: Long)
|
|
||||||
|
|
||||||
def version: Long = state.get.version
|
|
||||||
|
|
||||||
def versionedIterable = state.get
|
|
||||||
|
|
||||||
def size: Int = state.get.connections.size
|
|
||||||
|
|
||||||
def connections: Map[InetSocketAddress, ActorRef] = state.get.connections
|
|
||||||
|
|
||||||
def stopAll() {
|
|
||||||
state.get.iterable foreach (_.stop()) // shut down all remote connections
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def failOver(from: InetSocketAddress, to: InetSocketAddress) {
|
|
||||||
app.eventHandler.debug(this, "RemoteFailureDetector failover from [%s] to [%s]".format(from, to))
|
|
||||||
|
|
||||||
val oldState = state.get
|
|
||||||
var changed = false
|
|
||||||
|
|
||||||
val newMap = oldState.connections map {
|
|
||||||
case (`from`, actorRef) ⇒
|
|
||||||
changed = true
|
|
||||||
//actorRef.stop()
|
|
||||||
(to, newConnection(actorRef.address, to))
|
|
||||||
case other ⇒ other
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
//there was a state change, so we are now going to update the state.
|
|
||||||
val newState = oldState copy (version = oldState.version + 1, connections = newMap)
|
|
||||||
|
|
||||||
//if we are not able to update, the state, we are going to try again.
|
|
||||||
if (!state.compareAndSet(oldState, newState)) {
|
|
||||||
failOver(from, to) // recur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def remove(faultyConnection: ActorRef) {
|
|
||||||
|
|
||||||
val oldState = state.get()
|
|
||||||
var changed = false
|
|
||||||
|
|
||||||
var faultyAddress: InetSocketAddress = null
|
|
||||||
var newConnections = Map.empty[InetSocketAddress, ActorRef]
|
|
||||||
|
|
||||||
oldState.connections.keys foreach { address ⇒
|
|
||||||
val actorRef: ActorRef = oldState.connections.get(address).get
|
|
||||||
if (actorRef ne faultyConnection) {
|
|
||||||
newConnections = newConnections + ((address, actorRef))
|
|
||||||
} else {
|
|
||||||
faultyAddress = address
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
//one or more occurrances of the actorRef were removed, so we need to update the state.
|
|
||||||
val newState = oldState copy (version = oldState.version + 1, connections = newConnections)
|
|
||||||
|
|
||||||
//if we are not able to update the state, we just try again.
|
|
||||||
if (!state.compareAndSet(oldState, newState)) {
|
|
||||||
remove(faultyConnection) // recur
|
|
||||||
} else {
|
|
||||||
app.eventHandler.debug(this, "Removing connection [%s]".format(faultyAddress))
|
|
||||||
remote.eventStream.unregister(this, faultyAddress) // unregister the connections - e.g stop listen to events from it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def putIfAbsent(address: InetSocketAddress, newConnectionFactory: () ⇒ ActorRef): ActorRef = {
|
|
||||||
|
|
||||||
val oldState = state.get()
|
|
||||||
val oldConnections = oldState.connections
|
|
||||||
|
|
||||||
oldConnections.get(address) match {
|
|
||||||
case Some(connection) ⇒ connection // we already had the connection, return it
|
|
||||||
case None ⇒ // we need to create it
|
|
||||||
val newConnection = newConnectionFactory()
|
|
||||||
val newConnections = oldConnections + (address -> newConnection)
|
|
||||||
|
|
||||||
//one or more occurrances of the actorRef were removed, so we need to update the state.
|
|
||||||
val newState = oldState copy (version = oldState.version + 1, connections = newConnections)
|
|
||||||
|
|
||||||
//if we are not able to update the state, we just try again.
|
|
||||||
if (!state.compareAndSet(oldState, newState)) {
|
|
||||||
// we failed, need compensating action
|
|
||||||
newConnection.stop() // stop the new connection actor and try again
|
|
||||||
putIfAbsent(address, newConnectionFactory) // recur
|
|
||||||
} else {
|
|
||||||
// we succeeded
|
|
||||||
app.eventHandler.debug(this, "Adding connection [%s]".format(address))
|
|
||||||
remote.eventStream.register(this, address) // register the connection - e.g listen to events from it
|
|
||||||
newConnection // return new connection actor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private[remote] def newConnection(actorAddress: String, inetSocketAddress: InetSocketAddress) = {
|
|
||||||
RemoteActorRef(remote.app, remote.server, inetSocketAddress, actorAddress, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple failure detector that removes the failing connection permanently on first error.
|
|
||||||
*/
|
|
||||||
class RemoveConnectionOnFirstFailureRemoteFailureDetector(_app: AkkaApplication, _remote: Remote,
|
|
||||||
initialConnections: Map[InetSocketAddress, ActorRef] = Map.empty[InetSocketAddress, ActorRef])
|
|
||||||
extends RemoteFailureDetectorBase(_app, _remote, initialConnections) {
|
|
||||||
|
|
||||||
protected def newState() = State(Long.MinValue, initialConnections)
|
|
||||||
|
|
||||||
def isAvailable(connectionAddress: InetSocketAddress): Boolean = connections.get(connectionAddress).isDefined
|
|
||||||
|
|
||||||
def recordSuccess(connectionAddress: InetSocketAddress, timestamp: Long) {}
|
|
||||||
|
|
||||||
def recordFailure(connectionAddress: InetSocketAddress, timestamp: Long) {}
|
|
||||||
|
|
||||||
def notify(event: RemoteLifeCycleEvent) = event match {
|
|
||||||
case RemoteClientWriteFailed(request, cause, client, connectionAddress) ⇒
|
|
||||||
removeConnection(connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientError(cause, client, connectionAddress) ⇒
|
|
||||||
removeConnection(connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientDisconnected(client, connectionAddress) ⇒
|
|
||||||
removeConnection(connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientShutdown(client, connectionAddress) ⇒
|
|
||||||
removeConnection(connectionAddress)
|
|
||||||
|
|
||||||
case _ ⇒ {}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def removeConnection(connectionAddress: InetSocketAddress) =
|
|
||||||
connections.get(connectionAddress) foreach { conn ⇒ remove(conn) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Failure detector that bans the failing connection for 'timeToBan: Duration' and will try to use the connection
|
|
||||||
* again after the ban period have expired.
|
|
||||||
*
|
|
||||||
* @author <a href="http://jonasboner.com">Jonas Bonér</a>
|
|
||||||
*/
|
|
||||||
class BannagePeriodFailureDetector(_app: AkkaApplication, _remote: Remote,
|
|
||||||
initialConnections: Map[InetSocketAddress, ActorRef] = Map.empty[InetSocketAddress, ActorRef],
|
|
||||||
timeToBan: Duration)
|
|
||||||
extends RemoteFailureDetectorBase(_app, _remote, initialConnections) {
|
|
||||||
|
|
||||||
// FIXME considering adding a Scheduler event to notify the BannagePeriodFailureDetector unban the banned connection after the timeToBan have exprired
|
|
||||||
|
|
||||||
type T = Map[InetSocketAddress, BannedConnection]
|
|
||||||
|
|
||||||
case class BannedConnection(bannedSince: Long, connection: ActorRef)
|
|
||||||
|
|
||||||
val timeToBanInMillis = timeToBan.toMillis
|
|
||||||
|
|
||||||
protected def newState() =
|
|
||||||
State(Long.MinValue, initialConnections, Map.empty[InetSocketAddress, BannedConnection])
|
|
||||||
|
|
||||||
private def removeConnection(connectionAddress: InetSocketAddress) =
|
|
||||||
connections.get(connectionAddress) foreach { conn ⇒ remove(conn) }
|
|
||||||
|
|
||||||
// ===================================================================================
|
|
||||||
// FailureDetector callbacks
|
|
||||||
// ===================================================================================
|
|
||||||
|
|
||||||
def isAvailable(connectionAddress: InetSocketAddress): Boolean = connections.get(connectionAddress).isDefined
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def recordSuccess(connectionAddress: InetSocketAddress, timestamp: Long) {
|
|
||||||
val oldState = state.get
|
|
||||||
val bannedConnection = oldState.meta.get(connectionAddress)
|
|
||||||
|
|
||||||
if (bannedConnection.isDefined) {
|
|
||||||
val BannedConnection(bannedSince, connection) = bannedConnection.get
|
|
||||||
val currentlyBannedFor = newTimestamp - bannedSince
|
|
||||||
|
|
||||||
if (currentlyBannedFor > timeToBanInMillis) {
|
|
||||||
// ban time has expired - add connection to available connections
|
|
||||||
val newConnections = oldState.connections + (connectionAddress -> connection)
|
|
||||||
val newBannedConnections = oldState.meta - connectionAddress
|
|
||||||
|
|
||||||
val newState = oldState copy (version = oldState.version + 1,
|
|
||||||
connections = newConnections,
|
|
||||||
meta = newBannedConnections)
|
|
||||||
|
|
||||||
if (!state.compareAndSet(oldState, newState)) recordSuccess(connectionAddress, timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
final def recordFailure(connectionAddress: InetSocketAddress, timestamp: Long) {
|
|
||||||
val oldState = state.get
|
|
||||||
val connection = oldState.connections.get(connectionAddress)
|
|
||||||
|
|
||||||
if (connection.isDefined) {
|
|
||||||
val newConnections = oldState.connections - connectionAddress
|
|
||||||
val bannedConnection = BannedConnection(timestamp, connection.get)
|
|
||||||
val newBannedConnections = oldState.meta + (connectionAddress -> bannedConnection)
|
|
||||||
|
|
||||||
val newState = oldState copy (version = oldState.version + 1,
|
|
||||||
connections = newConnections,
|
|
||||||
meta = newBannedConnections)
|
|
||||||
|
|
||||||
if (!state.compareAndSet(oldState, newState)) recordFailure(connectionAddress, timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================================================================
|
|
||||||
// NetworkEventStream.Listener callback
|
|
||||||
// ===================================================================================
|
|
||||||
|
|
||||||
def notify(event: RemoteLifeCycleEvent) = event match {
|
|
||||||
case RemoteClientStarted(client, connectionAddress) ⇒
|
|
||||||
recordSuccess(connectionAddress, newTimestamp)
|
|
||||||
|
|
||||||
case RemoteClientConnected(client, connectionAddress) ⇒
|
|
||||||
recordSuccess(connectionAddress, newTimestamp)
|
|
||||||
|
|
||||||
case RemoteClientWriteFailed(request, cause, client, connectionAddress) ⇒
|
|
||||||
recordFailure(connectionAddress, newTimestamp)
|
|
||||||
|
|
||||||
case RemoteClientError(cause, client, connectionAddress) ⇒
|
|
||||||
recordFailure(connectionAddress, newTimestamp)
|
|
||||||
|
|
||||||
case RemoteClientDisconnected(client, connectionAddress) ⇒
|
|
||||||
recordFailure(connectionAddress, newTimestamp)
|
|
||||||
|
|
||||||
case RemoteClientShutdown(client, connectionAddress) ⇒
|
|
||||||
recordFailure(connectionAddress, newTimestamp)
|
|
||||||
|
|
||||||
case _ ⇒ {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Failure detector that uses the Circuit Breaker pattern to detect and recover from failing connections.
|
|
||||||
*
|
|
||||||
* class CircuitBreakerNetworkEventStream.Listener(initialConnections: Map[InetSocketAddress, ActorRef])
|
|
||||||
* extends RemoteFailureDetectorBase(initialConnections) {
|
|
||||||
*
|
|
||||||
* def newState() = State(Long.MinValue, initialConnections, None)
|
|
||||||
*
|
|
||||||
* def isAvailable(connectionAddress: InetSocketAddress): Boolean = connections.get(connectionAddress).isDefined
|
|
||||||
*
|
|
||||||
* def recordSuccess(connectionAddress: InetSocketAddress, timestamp: Long) {}
|
|
||||||
*
|
|
||||||
* def recordFailure(connectionAddress: InetSocketAddress, timestamp: Long) {}
|
|
||||||
*
|
|
||||||
* // FIXME implement CircuitBreakerNetworkEventStream.Listener
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base trait for remote failure event listener.
|
|
||||||
*/
|
|
||||||
trait RemoteFailureListener {
|
|
||||||
|
|
||||||
final private[akka] def notify(event: RemoteLifeCycleEvent) = event match {
|
|
||||||
case RemoteClientStarted(client, connectionAddress) ⇒
|
|
||||||
remoteClientStarted(client, connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientConnected(client, connectionAddress) ⇒
|
|
||||||
remoteClientConnected(client, connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientWriteFailed(request, cause, client, connectionAddress) ⇒
|
|
||||||
remoteClientWriteFailed(request, cause, client, connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientError(cause, client, connectionAddress) ⇒
|
|
||||||
remoteClientError(cause, client, connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientDisconnected(client, connectionAddress) ⇒
|
|
||||||
remoteClientDisconnected(client, connectionAddress)
|
|
||||||
|
|
||||||
case RemoteClientShutdown(client, connectionAddress) ⇒
|
|
||||||
remoteClientShutdown(client, connectionAddress)
|
|
||||||
|
|
||||||
case RemoteServerWriteFailed(request, cause, server, clientAddress) ⇒
|
|
||||||
remoteServerWriteFailed(request, cause, server, clientAddress)
|
|
||||||
|
|
||||||
case RemoteServerError(cause, server) ⇒
|
|
||||||
remoteServerError(cause, server)
|
|
||||||
|
|
||||||
case RemoteServerShutdown(server) ⇒
|
|
||||||
remoteServerShutdown(server)
|
|
||||||
}
|
|
||||||
|
|
||||||
def remoteClientStarted(client: RemoteClientModule, connectionAddress: InetSocketAddress) {}
|
|
||||||
|
|
||||||
def remoteClientConnected(client: RemoteClientModule, connectionAddress: InetSocketAddress) {}
|
|
||||||
|
|
||||||
def remoteClientWriteFailed(
|
|
||||||
request: AnyRef, cause: Throwable, client: RemoteClientModule, connectionAddress: InetSocketAddress) {}
|
|
||||||
|
|
||||||
def remoteClientError(cause: Throwable, client: RemoteClientModule, connectionAddress: InetSocketAddress) {}
|
|
||||||
|
|
||||||
def remoteClientDisconnected(client: RemoteClientModule, connectionAddress: InetSocketAddress) {}
|
|
||||||
|
|
||||||
def remoteClientShutdown(client: RemoteClientModule, connectionAddress: InetSocketAddress) {}
|
|
||||||
|
|
||||||
def remoteServerWriteFailed(
|
|
||||||
request: AnyRef, cause: Throwable, server: RemoteServerModule, clientAddress: Option[InetSocketAddress]) {}
|
|
||||||
|
|
||||||
def remoteServerError(cause: Throwable, server: RemoteServerModule) {}
|
|
||||||
|
|
||||||
def remoteServerShutdown(server: RemoteServerModule) {}
|
|
||||||
}
|
|
||||||
|
|
@ -51,7 +51,6 @@ trait NettyRemoteClientModule extends RemoteClientModule {
|
||||||
self: RemoteSupport ⇒
|
self: RemoteSupport ⇒
|
||||||
|
|
||||||
private val remoteClients = new HashMap[RemoteAddress, RemoteClient]
|
private val remoteClients = new HashMap[RemoteAddress, RemoteClient]
|
||||||
private val remoteActors = new Index[RemoteAddress, Uuid]
|
|
||||||
private val lock = new ReadWriteGuard
|
private val lock = new ReadWriteGuard
|
||||||
|
|
||||||
def app: AkkaApplication
|
def app: AkkaApplication
|
||||||
|
|
@ -143,22 +142,13 @@ abstract class RemoteClient private[akka] (
|
||||||
val module: NettyRemoteClientModule,
|
val module: NettyRemoteClientModule,
|
||||||
val remoteAddress: InetSocketAddress) {
|
val remoteAddress: InetSocketAddress) {
|
||||||
|
|
||||||
import app.config
|
|
||||||
implicit def _app = app
|
|
||||||
val serialization = new RemoteActorSerialization(app)
|
|
||||||
|
|
||||||
val useTransactionLog = config.getBool("akka.remote.client.buffering.retry-message-send-on-failure", false)
|
|
||||||
val transactionLogCapacity = config.getInt("akka.remote.client.buffering.capacity", -1)
|
|
||||||
|
|
||||||
val name = this.getClass.getSimpleName + "@" +
|
val name = this.getClass.getSimpleName + "@" +
|
||||||
remoteAddress.getAddress.getHostAddress + "::" +
|
remoteAddress.getAddress.getHostAddress + "::" +
|
||||||
remoteAddress.getPort
|
remoteAddress.getPort
|
||||||
|
|
||||||
|
val serialization = new RemoteActorSerialization(app)
|
||||||
|
|
||||||
protected val futures = new ConcurrentHashMap[Uuid, Promise[_]]
|
protected val futures = new ConcurrentHashMap[Uuid, Promise[_]]
|
||||||
protected val pendingRequests = {
|
|
||||||
if (transactionLogCapacity < 0) new ConcurrentLinkedQueue[(Boolean, Uuid, RemoteMessageProtocol)]
|
|
||||||
new LinkedBlockingQueue[(Boolean, Uuid, RemoteMessageProtocol)](transactionLogCapacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private[remote] val runSwitch = new Switch()
|
private[remote] val runSwitch = new Switch()
|
||||||
|
|
||||||
|
|
@ -172,19 +162,6 @@ abstract class RemoteClient private[akka] (
|
||||||
|
|
||||||
def shutdown(): Boolean
|
def shutdown(): Boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array with the current pending messages not yet delivered.
|
|
||||||
*/
|
|
||||||
def pendingMessages: Array[Any] = {
|
|
||||||
var messages = Vector[Any]()
|
|
||||||
val iter = pendingRequests.iterator
|
|
||||||
while (iter.hasNext) {
|
|
||||||
val (_, _, message) = iter.next
|
|
||||||
messages = messages :+ MessageSerializer.deserialize(app, message.getMessage)
|
|
||||||
}
|
|
||||||
messages.toArray
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the message to the wireprotocol and sends the message across the wire
|
* Converts the message to the wireprotocol and sends the message across the wire
|
||||||
*/
|
*/
|
||||||
|
|
@ -219,13 +196,7 @@ abstract class RemoteClient private[akka] (
|
||||||
notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress))
|
notifyListeners(RemoteClientWriteFailed(request, future.getCause, module, remoteAddress))
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception ⇒
|
case e: Exception ⇒ notifyListeners(RemoteClientError(e, module, remoteAddress))
|
||||||
notifyListeners(RemoteClientError(e, module, remoteAddress))
|
|
||||||
|
|
||||||
if (useTransactionLog && !pendingRequests.offer((true, null, request))) { // Add the request to the tx log after a failing send
|
|
||||||
pendingRequests.clear()
|
|
||||||
throw new RemoteClientMessageBufferException("Buffer limit [" + transactionLogCapacity + "] reached")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
||||||
|
|
@ -233,20 +204,14 @@ abstract class RemoteClient private[akka] (
|
||||||
} else {
|
} else {
|
||||||
val futureResult =
|
val futureResult =
|
||||||
if (senderFuture.isDefined) senderFuture.get
|
if (senderFuture.isDefined) senderFuture.get
|
||||||
else new DefaultPromise[T](request.getActorInfo.getTimeout)
|
else new DefaultPromise[T](request.getActorInfo.getTimeout)(app.dispatcher)
|
||||||
|
|
||||||
val futureUuid = uuidFrom(request.getUuid.getHigh, request.getUuid.getLow)
|
val futureUuid = uuidFrom(request.getUuid.getHigh, request.getUuid.getLow)
|
||||||
futures.put(futureUuid, futureResult) // Add future prematurely, remove it if write fails
|
futures.put(futureUuid, futureResult) // Add future prematurely, remove it if write fails
|
||||||
|
|
||||||
def handleRequestReplyError(future: ChannelFuture) = {
|
def handleRequestReplyError(future: ChannelFuture) = {
|
||||||
if (useTransactionLog && !pendingRequests.offer((false, futureUuid, request))) { // Add the request to the tx log after a failing send
|
val f = futures.remove(futureUuid) // Clean up future
|
||||||
pendingRequests.clear()
|
if (f ne null) f.completeWithException(future.getCause)
|
||||||
throw new RemoteClientMessageBufferException("Buffer limit [" + transactionLogCapacity + "] reached")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
val f = futures.remove(futureUuid) // Clean up future
|
|
||||||
if (f ne null) f.completeWithException(future.getCause)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var future: ChannelFuture = null
|
var future: ChannelFuture = null
|
||||||
|
|
@ -274,41 +239,6 @@ abstract class RemoteClient private[akka] (
|
||||||
throw exception
|
throw exception
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private[remote] def sendPendingRequests() = pendingRequests synchronized {
|
|
||||||
// ensure only one thread at a time can flush the log
|
|
||||||
val nrOfMessages = pendingRequests.size
|
|
||||||
if (nrOfMessages > 0) app.eventHandler.info(this, "Resending [%s] previously failed messages after remote client reconnect" format nrOfMessages)
|
|
||||||
var pendingRequest = pendingRequests.peek
|
|
||||||
|
|
||||||
while (pendingRequest ne null) {
|
|
||||||
val (isOneWay, futureUuid, message) = pendingRequest
|
|
||||||
|
|
||||||
if (isOneWay) {
|
|
||||||
// tell
|
|
||||||
val future = currentChannel.write(RemoteEncoder.encode(message))
|
|
||||||
future.awaitUninterruptibly()
|
|
||||||
|
|
||||||
if (future.isCancelled && !future.isSuccess) {
|
|
||||||
notifyListeners(RemoteClientWriteFailed(message, future.getCause, module, remoteAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// ask
|
|
||||||
val future = currentChannel.write(RemoteEncoder.encode(message))
|
|
||||||
future.awaitUninterruptibly()
|
|
||||||
|
|
||||||
if (future.isCancelled || !future.isSuccess) {
|
|
||||||
val f = futures.remove(futureUuid) // Clean up future
|
|
||||||
if (f ne null) f.completeWithException(future.getCause)
|
|
||||||
notifyListeners(RemoteClientWriteFailed(message, future.getCause, module, remoteAddress))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingRequests.remove(pendingRequest)
|
|
||||||
pendingRequest = pendingRequests.peek // try to grab next message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -441,7 +371,6 @@ class ActiveRemoteClient private[akka] (
|
||||||
bootstrap.releaseExternalResources()
|
bootstrap.releaseExternalResources()
|
||||||
bootstrap = null
|
bootstrap = null
|
||||||
connection = null
|
connection = null
|
||||||
pendingRequests.clear()
|
|
||||||
|
|
||||||
app.eventHandler.info(this, "[%s] has been shut down".format(name))
|
app.eventHandler.info(this, "[%s] has been shut down".format(name))
|
||||||
}
|
}
|
||||||
|
|
@ -564,7 +493,6 @@ class ActiveRemoteClientHandler(
|
||||||
|
|
||||||
override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = {
|
override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = {
|
||||||
try {
|
try {
|
||||||
if (client.useTransactionLog) client.sendPendingRequests() // try to send pending requests (still there after client/server crash ard reconnect
|
|
||||||
client.notifyListeners(RemoteClientConnected(client.module, client.remoteAddress))
|
client.notifyListeners(RemoteClientConnected(client.module, client.remoteAddress))
|
||||||
app.eventHandler.debug(this, "Remote client connected to [%s]".format(ctx.getChannel.getRemoteAddress))
|
app.eventHandler.debug(this, "Remote client connected to [%s]".format(ctx.getChannel.getRemoteAddress))
|
||||||
client.resetReconnectionTimeWindow
|
client.resetReconnectionTimeWindow
|
||||||
|
|
@ -648,7 +576,7 @@ class NettyRemoteSupport(_app: AkkaApplication) extends RemoteSupport(_app) with
|
||||||
app.eventHandler.debug(this,
|
app.eventHandler.debug(this,
|
||||||
"Creating RemoteActorRef with address [%s] connected to [%s]"
|
"Creating RemoteActorRef with address [%s] connected to [%s]"
|
||||||
.format(actorAddress, remoteInetSocketAddress))
|
.format(actorAddress, remoteInetSocketAddress))
|
||||||
RemoteActorRef(app, app.remote, remoteInetSocketAddress, actorAddress, loader)
|
RemoteActorRef(app.remote, remoteInetSocketAddress, actorAddress, loader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -971,7 +899,7 @@ class RemoteServerHandler(
|
||||||
override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) = {
|
override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) = {
|
||||||
event.getMessage match {
|
event.getMessage match {
|
||||||
case null ⇒
|
case null ⇒
|
||||||
throw new IllegalActorStateException("Message in remote MessageEvent is null: " + event)
|
throw new IllegalActorStateException("Message in remote MessageEvent is null [" + event + "]")
|
||||||
|
|
||||||
case remote: AkkaRemoteProtocol if remote.hasMessage ⇒
|
case remote: AkkaRemoteProtocol if remote.hasMessage ⇒
|
||||||
handleRemoteMessageProtocol(remote.getMessage, event.getChannel)
|
handleRemoteMessageProtocol(remote.getMessage, event.getChannel)
|
||||||
|
|
@ -1070,12 +998,6 @@ class RemoteServerHandler(
|
||||||
private def createActor(actorInfo: ActorInfoProtocol, channel: Channel): ActorRef = {
|
private def createActor(actorInfo: ActorInfoProtocol, channel: Channel): ActorRef = {
|
||||||
val uuid = actorInfo.getUuid
|
val uuid = actorInfo.getUuid
|
||||||
val address = actorInfo.getAddress
|
val address = actorInfo.getAddress
|
||||||
// val address = {
|
|
||||||
// // strip off clusterActorRefPrefix if needed
|
|
||||||
// val addr = actorInfo.getAddress
|
|
||||||
// if (addr.startsWith(Address.clusterActorRefPrefix)) addr.substring(addr.indexOf('.') + 1, addr.length)
|
|
||||||
// else addr
|
|
||||||
// }
|
|
||||||
|
|
||||||
app.eventHandler.debug(this,
|
app.eventHandler.debug(this,
|
||||||
"Looking up a remotely available actor for address [%s] on node [%s]"
|
"Looking up a remotely available actor for address [%s] on node [%s]"
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ class RemoteActorSerialization(val app: AkkaApplication) {
|
||||||
app.eventHandler.debug(this, "Deserializing RemoteActorRefProtocol to RemoteActorRef:\n %s".format(protocol))
|
app.eventHandler.debug(this, "Deserializing RemoteActorRefProtocol to RemoteActorRef:\n %s".format(protocol))
|
||||||
|
|
||||||
val ref = RemoteActorRef(
|
val ref = RemoteActorRef(
|
||||||
app, app.remote,
|
app.remote,
|
||||||
JavaSerializer.fromBinary(protocol.getInetSocketAddress.toByteArray, Some(classOf[InetSocketAddress]), loader).asInstanceOf[InetSocketAddress],
|
JavaSerializer.fromBinary(protocol.getInetSocketAddress.toByteArray, Some(classOf[InetSocketAddress]), loader).asInstanceOf[InetSocketAddress],
|
||||||
protocol.getAddress,
|
protocol.getAddress,
|
||||||
loader)
|
loader)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package akka.remote
|
||||||
|
|
||||||
|
import org.scalatest.WordSpec
|
||||||
|
import org.scalatest.matchers.MustMatchers
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
|
class AccrualFailureDetectorSpec extends WordSpec with MustMatchers {
|
||||||
|
|
||||||
|
"An AccrualFailureDetector" should {
|
||||||
|
|
||||||
|
"mark node as available after a series of successful heartbeats" in {
|
||||||
|
val fd = new AccrualFailureDetector
|
||||||
|
val conn = new InetSocketAddress("localhost", 2552)
|
||||||
|
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(100)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
fd.isAvailable(conn) must be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"mark node as dead after explicit removal of connection" in {
|
||||||
|
val fd = new AccrualFailureDetector
|
||||||
|
val conn = new InetSocketAddress("localhost", 2552)
|
||||||
|
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(100)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
fd.isAvailable(conn) must be(true)
|
||||||
|
|
||||||
|
fd.remove(conn)
|
||||||
|
|
||||||
|
fd.isAvailable(conn) must be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
"mark node as dead if heartbeat are missed" in {
|
||||||
|
val fd = new AccrualFailureDetector(threshold = 3)
|
||||||
|
val conn = new InetSocketAddress("localhost", 2552)
|
||||||
|
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(100)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
fd.isAvailable(conn) must be(true)
|
||||||
|
|
||||||
|
Thread.sleep(5000)
|
||||||
|
fd.isAvailable(conn) must be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
"mark node as available if it starts heartbeat again after being marked dead due to detection of failure" in {
|
||||||
|
val fd = new AccrualFailureDetector(threshold = 3)
|
||||||
|
val conn = new InetSocketAddress("localhost", 2552)
|
||||||
|
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(100)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
fd.isAvailable(conn) must be(true)
|
||||||
|
|
||||||
|
Thread.sleep(5000)
|
||||||
|
fd.isAvailable(conn) must be(false)
|
||||||
|
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
Thread.sleep(100)
|
||||||
|
fd.heartbeat(conn)
|
||||||
|
|
||||||
|
fd.isAvailable(conn) must be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ package akka.remote
|
||||||
import org.scalatest.{ BeforeAndAfterAll, BeforeAndAfterEach }
|
import org.scalatest.{ BeforeAndAfterAll, BeforeAndAfterEach }
|
||||||
|
|
||||||
import akka.remote.netty.NettyRemoteSupport
|
import akka.remote.netty.NettyRemoteSupport
|
||||||
import akka.actor.{ Actor, ActorRegistry }
|
import akka.actor.Actor
|
||||||
import akka.testkit.AkkaSpec
|
import akka.testkit.AkkaSpec
|
||||||
import akka.dispatch.Future
|
import akka.dispatch.Future
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,5 +85,4 @@ object TestFSMRef {
|
||||||
|
|
||||||
def apply[S, D, T <: Actor](factory: ⇒ T, address: String)(implicit ev: T <:< FSM[S, D], application: AkkaApplication): TestFSMRef[S, D, T] =
|
def apply[S, D, T <: Actor](factory: ⇒ T, address: String)(implicit ev: T <:< FSM[S, D], application: AkkaApplication): TestFSMRef[S, D, T] =
|
||||||
new TestFSMRef(application, Props(creator = () ⇒ factory), address)
|
new TestFSMRef(application, Props(creator = () ⇒ factory), address)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class TestFSMRefSpec extends AkkaSpec {
|
||||||
when(2) {
|
when(2) {
|
||||||
case Ev("back") ⇒ goto(1) using "back"
|
case Ev("back") ⇒ goto(1) using "back"
|
||||||
}
|
}
|
||||||
})
|
}, "test-fsm-ref-1")
|
||||||
fsm.stateName must be(1)
|
fsm.stateName must be(1)
|
||||||
fsm.stateData must be("")
|
fsm.stateData must be("")
|
||||||
fsm ! "go"
|
fsm ! "go"
|
||||||
|
|
@ -49,14 +49,12 @@ class TestFSMRefSpec extends AkkaSpec {
|
||||||
when(1) {
|
when(1) {
|
||||||
case x ⇒ stay
|
case x ⇒ stay
|
||||||
}
|
}
|
||||||
})
|
}, "test-fsm-ref-2")
|
||||||
fsm.timerActive_?("test") must be(false)
|
fsm.timerActive_?("test") must be(false)
|
||||||
fsm.setTimer("test", 12, 10 millis, true)
|
fsm.setTimer("test", 12, 10 millis, true)
|
||||||
fsm.timerActive_?("test") must be(true)
|
fsm.timerActive_?("test") must be(true)
|
||||||
fsm.cancelTimer("test")
|
fsm.cancelTimer("test")
|
||||||
fsm.timerActive_?("test") must be(false)
|
fsm.timerActive_?("test") must be(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,12 @@ import static akka.actor.Actors.poisonPill;
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
import akka.actor.ActorRef;
|
import akka.actor.ActorRef;
|
||||||
|
import akka.actor.Actors;
|
||||||
import akka.actor.UntypedActor;
|
import akka.actor.UntypedActor;
|
||||||
import akka.actor.UntypedActorFactory;
|
import akka.actor.UntypedActorFactory;
|
||||||
import akka.routing.RoutedProps;
|
import akka.routing.RoutedProps;
|
||||||
import akka.routing.RouterType;
|
import akka.routing.RouterType;
|
||||||
|
import akka.routing.LocalConnectionManager;
|
||||||
import akka.routing.Routing;
|
import akka.routing.Routing;
|
||||||
import akka.routing.Routing.Broadcast;
|
import akka.routing.Routing.Broadcast;
|
||||||
import scala.collection.JavaConversions;
|
import scala.collection.JavaConversions;
|
||||||
|
|
@ -22,7 +24,7 @@ import java.util.concurrent.CountDownLatch;
|
||||||
import akka.AkkaApplication;
|
import akka.AkkaApplication;
|
||||||
|
|
||||||
public class Pi {
|
public class Pi {
|
||||||
|
|
||||||
private static final AkkaApplication app = new AkkaApplication();
|
private static final AkkaApplication app = new AkkaApplication();
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
@ -112,7 +114,7 @@ public class Pi {
|
||||||
workers.add(worker);
|
workers.add(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
router = app.routing().actorOf(RoutedProps.apply().withRoundRobinRouter().withConnections(workers), "pi");
|
router = app.createActor(new RoutedProps().withRoundRobinRouter().withLocalConnections(workers), "pi");
|
||||||
}
|
}
|
||||||
|
|
||||||
// message handler
|
// message handler
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ object Pi extends App {
|
||||||
val workers = Vector.fill(nrOfWorkers)(app.createActor[Worker])
|
val workers = Vector.fill(nrOfWorkers)(app.createActor[Worker])
|
||||||
|
|
||||||
// wrap them with a load-balancing router
|
// wrap them with a load-balancing router
|
||||||
val router = app.routing.actorOf(RoutedProps().withRoundRobinRouter.withConnections(workers), "pi")
|
val router = app.createActor(RoutedProps().withRoundRobinRouter.withLocalConnections(workers), "pi")
|
||||||
|
|
||||||
// message handler
|
// message handler
|
||||||
def receive = {
|
def receive = {
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,10 @@ import static java.util.Arrays.asList;
|
||||||
import akka.AkkaApplication;
|
import akka.AkkaApplication;
|
||||||
import akka.routing.RoutedProps;
|
import akka.routing.RoutedProps;
|
||||||
import akka.routing.Routing;
|
import akka.routing.Routing;
|
||||||
|
import akka.routing.LocalConnectionManager;
|
||||||
import scala.Option;
|
import scala.Option;
|
||||||
import akka.actor.ActorRef;
|
import akka.actor.ActorRef;
|
||||||
|
import akka.actor.Actors;
|
||||||
import akka.actor.Channel;
|
import akka.actor.Channel;
|
||||||
import akka.actor.UntypedActor;
|
import akka.actor.UntypedActor;
|
||||||
import akka.actor.UntypedActorFactory;
|
import akka.actor.UntypedActorFactory;
|
||||||
|
|
@ -24,7 +26,7 @@ import scala.collection.JavaConversions;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
public class Pi {
|
public class Pi {
|
||||||
|
|
||||||
private static final AkkaApplication app = new AkkaApplication();
|
private static final AkkaApplication app = new AkkaApplication();
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
@ -105,7 +107,7 @@ public class Pi {
|
||||||
workers.add(worker);
|
workers.add(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
router = app.routing().actorOf(RoutedProps.apply().withConnections(workers).withRoundRobinRouter(), "pi");
|
router = app.createActor(new RoutedProps().withRoundRobinRouter().withLocalConnections(workers), "pi");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import akka.event.EventHandler
|
||||||
import System.{ currentTimeMillis ⇒ now }
|
import System.{ currentTimeMillis ⇒ now }
|
||||||
import akka.routing.Routing.Broadcast
|
import akka.routing.Routing.Broadcast
|
||||||
import akka.actor.{ Timeout, Channel, Actor, PoisonPill }
|
import akka.actor.{ Timeout, Channel, Actor, PoisonPill }
|
||||||
import akka.routing.{ RoutedProps, Routing }
|
import akka.routing._
|
||||||
import akka.AkkaApplication
|
import akka.AkkaApplication
|
||||||
|
|
||||||
object Pi extends App {
|
object Pi extends App {
|
||||||
|
|
@ -56,7 +56,9 @@ object Pi extends App {
|
||||||
val workers = Vector.fill(nrOfWorkers)(app.createActor[Worker])
|
val workers = Vector.fill(nrOfWorkers)(app.createActor[Worker])
|
||||||
|
|
||||||
// wrap them with a load-balancing router
|
// wrap them with a load-balancing router
|
||||||
val router = app.routing.actorOf(RoutedProps().withConnections(workers).withRoundRobinRouter, "pi")
|
val router = app.createActor(RoutedProps(
|
||||||
|
routerFactory = () ⇒ new RoundRobinRouter,
|
||||||
|
connectionManager = new LocalConnectionManager(workers)), "pi")
|
||||||
|
|
||||||
// phase 1, can accept a Calculate message
|
// phase 1, can accept a Calculate message
|
||||||
def scatter: Receive = {
|
def scatter: Receive = {
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ akka {
|
||||||
|
|
||||||
service-ping { # deployment id pattern
|
service-ping { # deployment id pattern
|
||||||
|
|
||||||
router = "least-cpu" # routing (load-balance) scheme to use
|
router = "round-robin" # routing (load-balance) scheme to use
|
||||||
# available: "direct", "round-robin", "random",
|
# available: "direct", "round-robin", "random", "scatter-gather"
|
||||||
# "least-cpu", "least-ram", "least-messages"
|
# "least-cpu", "least-ram", "least-messages"
|
||||||
# or: fully qualified class name of the router class
|
# or: fully qualified class name of the router class
|
||||||
# default is "direct";
|
# default is "direct";
|
||||||
|
|
@ -76,7 +76,7 @@ akka {
|
||||||
# if the "direct" router is used then this element is ignored (always '1')
|
# if the "direct" router is used then this element is ignored (always '1')
|
||||||
|
|
||||||
failure-detector { # failure detection scheme to use
|
failure-detector { # failure detection scheme to use
|
||||||
bannage-period { # available: remove-connection-on-first-local-failure {}
|
bannage-period { # available: no-op {}
|
||||||
time-to-ban = 10 # remove-connection-on-first-failure {}
|
time-to-ban = 10 # remove-connection-on-first-failure {}
|
||||||
} # bannage-period { ... }
|
} # bannage-period { ... }
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue