Update osgi branch with latest changes from 'akka/master'
Conflicts: project/AkkaBuild.scala
This commit is contained in:
commit
a7293ca7f3
123 changed files with 3618 additions and 1845 deletions
|
|
@ -227,7 +227,7 @@ class ActorRefSpec extends AkkaSpec with DefaultTimeout {
|
|||
contextStackMustBeEmpty
|
||||
}
|
||||
|
||||
filterException[java.lang.IllegalStateException] {
|
||||
EventFilter[ActorInitializationException](occurrences = 1) intercept {
|
||||
(intercept[java.lang.IllegalStateException] {
|
||||
wrap(result ⇒
|
||||
actorOf(Props(new OuterActor(actorOf(Props(promiseIntercept({ throw new IllegalStateException("Ur state be b0rked"); new InnerActor })(result)))))))
|
||||
|
|
@ -257,14 +257,14 @@ class ActorRefSpec extends AkkaSpec with DefaultTimeout {
|
|||
val in = new ObjectInputStream(new ByteArrayInputStream(bytes))
|
||||
val readA = in.readObject
|
||||
|
||||
a.isInstanceOf[LocalActorRef] must be === true
|
||||
readA.isInstanceOf[LocalActorRef] must be === true
|
||||
a.isInstanceOf[ActorRefWithCell] must be === true
|
||||
readA.isInstanceOf[ActorRefWithCell] must be === true
|
||||
(readA eq a) must be === true
|
||||
}
|
||||
|
||||
val ser = new JavaSerializer(esys)
|
||||
val readA = ser.fromBinary(bytes, None)
|
||||
readA.isInstanceOf[LocalActorRef] must be === true
|
||||
readA.isInstanceOf[ActorRefWithCell] must be === true
|
||||
(readA eq a) must be === true
|
||||
}
|
||||
|
||||
|
|
@ -369,13 +369,13 @@ class ActorRefSpec extends AkkaSpec with DefaultTimeout {
|
|||
val timeout = Timeout(20000)
|
||||
val ref = system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case 5 ⇒ sender.tell("five")
|
||||
case null ⇒ sender.tell("null")
|
||||
case 5 ⇒ sender.tell("five")
|
||||
case 0 ⇒ sender.tell("null")
|
||||
}
|
||||
}))
|
||||
|
||||
val ffive = (ref.ask(5)(timeout)).mapTo[String]
|
||||
val fnull = (ref.ask(null)(timeout)).mapTo[String]
|
||||
val fnull = (ref.ask(0)(timeout)).mapTo[String]
|
||||
ref ! PoisonPill
|
||||
|
||||
Await.result(ffive, timeout.duration) must be("five")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import akka.dispatch.Await
|
|||
import akka.util.duration._
|
||||
import scala.collection.JavaConverters
|
||||
import java.util.concurrent.{ TimeUnit, RejectedExecutionException, CountDownLatch, ConcurrentLinkedQueue }
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import akka.dispatch.Future
|
||||
|
||||
class JavaExtensionSpec extends JavaExtension with JUnitSuite
|
||||
|
||||
|
|
@ -21,8 +24,46 @@ object TestExtension extends ExtensionId[TestExtension] with ExtensionIdProvider
|
|||
// Dont't place inside ActorSystemSpec object, since it will not be garbage collected and reference to system remains
|
||||
class TestExtension(val system: ExtendedActorSystem) extends Extension
|
||||
|
||||
object ActorSystemSpec {
|
||||
|
||||
class Waves extends Actor {
|
||||
var master: ActorRef = _
|
||||
var terminaters = Set[ActorRef]()
|
||||
|
||||
def receive = {
|
||||
case n: Int ⇒
|
||||
master = sender
|
||||
terminaters = Set() ++ (for (i ← 1 to n) yield {
|
||||
val man = context.watch(context.system.actorOf(Props[Terminater]))
|
||||
man ! "run"
|
||||
man
|
||||
})
|
||||
case Terminated(child) if terminaters contains child ⇒
|
||||
terminaters -= child
|
||||
if (terminaters.isEmpty) {
|
||||
master ! "done"
|
||||
context stop self
|
||||
}
|
||||
}
|
||||
|
||||
override def preRestart(cause: Throwable, msg: Option[Any]) {
|
||||
if (master ne null) {
|
||||
master ! "failed with " + cause + " while processing " + msg
|
||||
}
|
||||
context stop self
|
||||
}
|
||||
}
|
||||
|
||||
class Terminater extends Actor {
|
||||
def receive = {
|
||||
case "run" ⇒ context.stop(self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class ActorSystemSpec extends AkkaSpec("""akka.extensions = ["akka.actor.TestExtension$"]""") {
|
||||
class ActorSystemSpec extends AkkaSpec("""akka.extensions = ["akka.actor.TestExtension$"]""") with ImplicitSender {
|
||||
|
||||
"An ActorSystem" must {
|
||||
|
||||
|
|
@ -112,6 +153,35 @@ class ActorSystemSpec extends AkkaSpec("""akka.extensions = ["akka.actor.TestExt
|
|||
}.getMessage must be("Must be called prior to system shutdown.")
|
||||
}
|
||||
|
||||
"reliably create waves of actors" in {
|
||||
import system.dispatcher
|
||||
implicit val timeout = Timeout(30 seconds)
|
||||
val waves = for (i ← 1 to 3) yield system.actorOf(Props[ActorSystemSpec.Waves]) ? 50000
|
||||
Await.result(Future.sequence(waves), timeout.duration + 5.seconds) must be === Seq("done", "done", "done")
|
||||
}
|
||||
|
||||
"reliable deny creation of actors while shutting down" in {
|
||||
val system = ActorSystem()
|
||||
system.scheduler.scheduleOnce(200 millis) { system.shutdown() }
|
||||
var failing = false
|
||||
var created = Vector.empty[ActorRef]
|
||||
while (!system.isTerminated && system.uptime < 5) {
|
||||
try {
|
||||
val t = system.actorOf(Props[ActorSystemSpec.Terminater])
|
||||
failing must not be true // because once failing => always failing (it’s due to shutdown)
|
||||
created :+= t
|
||||
} catch {
|
||||
case _: IllegalStateException ⇒ failing = true
|
||||
}
|
||||
}
|
||||
if (system.uptime >= 5) {
|
||||
println(created.last)
|
||||
println(system.asInstanceOf[ExtendedActorSystem].printTree)
|
||||
system.uptime must be < 5L
|
||||
}
|
||||
created filter (ref ⇒ !ref.isTerminated && !ref.asInstanceOf[ActorRefWithCell].underlying.isInstanceOf[UnstartedCell]) must be(Seq())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,26 @@ trait DeathWatchSpec { this: AkkaSpec with ImplicitSender with DefaultTimeout
|
|||
result must be(Seq(1, 2, 3))
|
||||
}
|
||||
}
|
||||
|
||||
"be able to watch a child with the same name after the old died" in {
|
||||
val parent = system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case "NKOTB" ⇒
|
||||
val currentKid = context.watch(context.actorOf(Props(ctx ⇒ { case "NKOTB" ⇒ ctx stop ctx.self }), "kid"))
|
||||
currentKid forward "NKOTB"
|
||||
context become {
|
||||
case Terminated(`currentKid`) ⇒
|
||||
testActor ! "GREEN"
|
||||
context unbecome
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
parent ! "NKOTB"
|
||||
expectMsg("GREEN")
|
||||
parent ! "NKOTB"
|
||||
expectMsg("GREEN")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,13 +140,13 @@ class FSMTimingSpec extends AkkaSpec with ImplicitSender {
|
|||
object FSMTimingSpec {
|
||||
|
||||
def suspend(actorRef: ActorRef): Unit = actorRef match {
|
||||
case l: LocalActorRef ⇒ l.suspend()
|
||||
case _ ⇒
|
||||
case l: ActorRefWithCell ⇒ l.suspend()
|
||||
case _ ⇒
|
||||
}
|
||||
|
||||
def resume(actorRef: ActorRef): Unit = actorRef match {
|
||||
case l: LocalActorRef ⇒ l.resume()
|
||||
case _ ⇒
|
||||
case l: ActorRefWithCell ⇒ l.resume()
|
||||
case _ ⇒
|
||||
}
|
||||
|
||||
trait State
|
||||
|
|
|
|||
|
|
@ -3,24 +3,23 @@
|
|||
*/
|
||||
package akka.actor.dispatch
|
||||
|
||||
import org.scalatest.Assertions._
|
||||
import akka.testkit._
|
||||
import akka.dispatch._
|
||||
import akka.util.Timeout
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.{ ConcurrentHashMap, CountDownLatch, TimeUnit }
|
||||
import akka.util.Switch
|
||||
import java.rmi.RemoteException
|
||||
import org.junit.{ After, Test }
|
||||
import akka.actor._
|
||||
import util.control.NoStackTrace
|
||||
import akka.actor.ActorSystem
|
||||
import akka.util.duration._
|
||||
import akka.event.Logging.Error
|
||||
import java.util.concurrent.{ TimeUnit, CountDownLatch, ConcurrentHashMap }
|
||||
import java.util.concurrent.atomic.{ AtomicLong, AtomicInteger }
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.Assertions.{ fail, assert }
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import akka.util.Duration
|
||||
|
||||
import akka.actor._
|
||||
import akka.dispatch._
|
||||
import akka.event.Logging.Error
|
||||
import akka.pattern.ask
|
||||
import akka.testkit._
|
||||
import akka.util.{ Timeout, Switch, Duration }
|
||||
import akka.util.duration._
|
||||
|
||||
object ActorModelSpec {
|
||||
|
||||
|
|
@ -201,7 +200,7 @@ object ActorModelSpec {
|
|||
msgsReceived: Long = statsFor(actorRef, dispatcher).msgsReceived.get(),
|
||||
msgsProcessed: Long = statsFor(actorRef, dispatcher).msgsProcessed.get(),
|
||||
restarts: Long = statsFor(actorRef, dispatcher).restarts.get())(implicit system: ActorSystem) {
|
||||
val stats = statsFor(actorRef, Option(dispatcher).getOrElse(actorRef.asInstanceOf[LocalActorRef].underlying.dispatcher))
|
||||
val stats = statsFor(actorRef, Option(dispatcher).getOrElse(actorRef.asInstanceOf[ActorRefWithCell].underlying.asInstanceOf[ActorCell].dispatcher))
|
||||
val deadline = System.currentTimeMillis + 1000
|
||||
try {
|
||||
await(deadline)(stats.suspensions.get() == suspensions)
|
||||
|
|
@ -241,6 +240,13 @@ abstract class ActorModelSpec(config: String) extends AkkaSpec(config) with Defa
|
|||
|
||||
def newTestActor(dispatcher: String) = system.actorOf(Props[DispatcherActor].withDispatcher(dispatcher))
|
||||
|
||||
def awaitStarted(ref: ActorRef): Unit = {
|
||||
awaitCond(ref match {
|
||||
case r: RepointableRef ⇒ r.isStarted
|
||||
case _ ⇒ true
|
||||
}, 1 second, 10 millis)
|
||||
}
|
||||
|
||||
protected def interceptedDispatcher(): MessageDispatcherInterceptor
|
||||
protected def dispatcherType: String
|
||||
|
||||
|
|
@ -280,6 +286,7 @@ abstract class ActorModelSpec(config: String) extends AkkaSpec(config) with Defa
|
|||
implicit val dispatcher = interceptedDispatcher()
|
||||
val start, oneAtATime = new CountDownLatch(1)
|
||||
val a = newTestActor(dispatcher.id)
|
||||
awaitStarted(a)
|
||||
|
||||
a ! CountDown(start)
|
||||
assertCountDown(start, 3.seconds.dilated.toMillis, "Should process first message within 3 seconds")
|
||||
|
|
@ -328,7 +335,8 @@ abstract class ActorModelSpec(config: String) extends AkkaSpec(config) with Defa
|
|||
|
||||
"not process messages for a suspended actor" in {
|
||||
implicit val dispatcher = interceptedDispatcher()
|
||||
val a = newTestActor(dispatcher.id).asInstanceOf[LocalActorRef]
|
||||
val a = newTestActor(dispatcher.id).asInstanceOf[InternalActorRef]
|
||||
awaitStarted(a)
|
||||
val done = new CountDownLatch(1)
|
||||
a.suspend
|
||||
a ! CountDown(done)
|
||||
|
|
@ -436,6 +444,7 @@ abstract class ActorModelSpec(config: String) extends AkkaSpec(config) with Defa
|
|||
|
||||
"not double-deregister" in {
|
||||
implicit val dispatcher = interceptedDispatcher()
|
||||
for (i ← 1 to 1000) system.actorOf(Props.empty)
|
||||
val a = newTestActor(dispatcher.id)
|
||||
a ! DoubleStop
|
||||
awaitCond(statsFor(a, dispatcher).registers.get == 1)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
package akka.actor.dispatch
|
||||
|
||||
import java.util.concurrent.{ TimeUnit, CountDownLatch }
|
||||
import akka.dispatch.{ Mailbox, Dispatchers }
|
||||
import akka.actor.{ LocalActorRef, IllegalActorStateException, Actor, Props }
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import akka.actor.{ Props, ActorRefWithCell, ActorCell, Actor }
|
||||
import akka.dispatch.Mailbox
|
||||
import akka.testkit.AkkaSpec
|
||||
|
||||
object BalancingDispatcherSpec {
|
||||
|
|
@ -51,8 +55,8 @@ class BalancingDispatcherSpec extends AkkaSpec(BalancingDispatcherSpec.config) {
|
|||
"have fast actor stealing work from slow actor" in {
|
||||
val finishedCounter = new CountDownLatch(110)
|
||||
|
||||
val slow = system.actorOf(Props(new DelayableActor(50, finishedCounter)).withDispatcher(delayableActorDispatcher)).asInstanceOf[LocalActorRef]
|
||||
val fast = system.actorOf(Props(new DelayableActor(10, finishedCounter)).withDispatcher(delayableActorDispatcher)).asInstanceOf[LocalActorRef]
|
||||
val slow = system.actorOf(Props(new DelayableActor(50, finishedCounter)).withDispatcher(delayableActorDispatcher)).asInstanceOf[ActorRefWithCell]
|
||||
val fast = system.actorOf(Props(new DelayableActor(10, finishedCounter)).withDispatcher(delayableActorDispatcher)).asInstanceOf[ActorRefWithCell]
|
||||
|
||||
var sentToFast = 0
|
||||
|
||||
|
|
@ -76,11 +80,11 @@ class BalancingDispatcherSpec extends AkkaSpec(BalancingDispatcherSpec.config) {
|
|||
}
|
||||
|
||||
finishedCounter.await(5, TimeUnit.SECONDS)
|
||||
fast.underlying.mailbox.asInstanceOf[Mailbox].hasMessages must be(false)
|
||||
slow.underlying.mailbox.asInstanceOf[Mailbox].hasMessages must be(false)
|
||||
fast.underlying.actor.asInstanceOf[DelayableActor].invocationCount must be > sentToFast
|
||||
fast.underlying.actor.asInstanceOf[DelayableActor].invocationCount must be >
|
||||
(slow.underlying.actor.asInstanceOf[DelayableActor].invocationCount)
|
||||
fast.underlying.asInstanceOf[ActorCell].mailbox.asInstanceOf[Mailbox].hasMessages must be(false)
|
||||
slow.underlying.asInstanceOf[ActorCell].mailbox.asInstanceOf[Mailbox].hasMessages must be(false)
|
||||
fast.underlying.asInstanceOf[ActorCell].actor.asInstanceOf[DelayableActor].invocationCount must be > sentToFast
|
||||
fast.underlying.asInstanceOf[ActorCell].actor.asInstanceOf[DelayableActor].invocationCount must be >
|
||||
(slow.underlying.asInstanceOf[ActorCell].actor.asInstanceOf[DelayableActor].invocationCount)
|
||||
system.stop(slow)
|
||||
system.stop(fast)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.dispatch
|
||||
|
||||
import org.scalatest.{ BeforeAndAfterAll, BeforeAndAfterEach }
|
||||
import java.util.concurrent.{ TimeUnit, BlockingQueue }
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import akka.util._
|
||||
import akka.util.duration._
|
||||
import akka.testkit.AkkaSpec
|
||||
import java.util.concurrent.{ ConcurrentLinkedQueue, BlockingQueue }
|
||||
|
||||
import org.scalatest.{ BeforeAndAfterEach, BeforeAndAfterAll }
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import akka.actor._
|
||||
|
||||
import akka.actor.{ RepointableRef, Props, DeadLetter, ActorSystem, ActorRefWithCell, ActorRef, ActorCell }
|
||||
import akka.testkit.AkkaSpec
|
||||
import akka.util.duration.intToDurationInt
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
abstract class MailboxSpec extends AkkaSpec with BeforeAndAfterAll with BeforeAndAfterEach {
|
||||
|
|
@ -75,7 +79,7 @@ abstract class MailboxSpec extends AkkaSpec with BeforeAndAfterAll with BeforeAn
|
|||
result
|
||||
}
|
||||
|
||||
def createMessageInvocation(msg: Any): Envelope = Envelope(msg, system.deadLetters)(system)
|
||||
def createMessageInvocation(msg: Any): Envelope = Envelope(msg, system.deadLetters, system)
|
||||
|
||||
def ensureInitialMailboxState(config: MailboxType, q: MessageQueue) {
|
||||
q must not be null
|
||||
|
|
@ -136,8 +140,8 @@ abstract class MailboxSpec extends AkkaSpec with BeforeAndAfterAll with BeforeAn
|
|||
class DefaultMailboxSpec extends MailboxSpec {
|
||||
lazy val name = "The default mailbox implementation"
|
||||
def factory = {
|
||||
case u: UnboundedMailbox ⇒ u.create(None)
|
||||
case b: BoundedMailbox ⇒ b.create(None)
|
||||
case u: UnboundedMailbox ⇒ u.create(None, None)
|
||||
case b: BoundedMailbox ⇒ b.create(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -145,8 +149,8 @@ class PriorityMailboxSpec extends MailboxSpec {
|
|||
val comparator = PriorityGenerator(_.##)
|
||||
lazy val name = "The priority mailbox implementation"
|
||||
def factory = {
|
||||
case UnboundedMailbox() ⇒ new UnboundedPriorityMailbox(comparator).create(None)
|
||||
case BoundedMailbox(capacity, pushTimeOut) ⇒ new BoundedPriorityMailbox(comparator, capacity, pushTimeOut).create(None)
|
||||
case UnboundedMailbox() ⇒ new UnboundedPriorityMailbox(comparator).create(None, None)
|
||||
case BoundedMailbox(capacity, pushTimeOut) ⇒ new BoundedPriorityMailbox(comparator, capacity, pushTimeOut).create(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,13 +162,13 @@ object CustomMailboxSpec {
|
|||
"""
|
||||
|
||||
class MyMailboxType(settings: ActorSystem.Settings, config: Config) extends MailboxType {
|
||||
override def create(owner: Option[ActorContext]) = owner match {
|
||||
override def create(owner: Option[ActorRef], system: Option[ActorSystem]) = owner match {
|
||||
case Some(o) ⇒ new MyMailbox(o)
|
||||
case None ⇒ throw new Exception("no mailbox owner given")
|
||||
}
|
||||
}
|
||||
|
||||
class MyMailbox(owner: ActorContext) extends QueueBasedMessageQueue with UnboundedMessageQueueSemantics {
|
||||
class MyMailbox(owner: ActorRef) extends QueueBasedMessageQueue with UnboundedMessageQueueSemantics {
|
||||
final val queue = new ConcurrentLinkedQueue[Envelope]()
|
||||
}
|
||||
}
|
||||
|
|
@ -174,7 +178,11 @@ class CustomMailboxSpec extends AkkaSpec(CustomMailboxSpec.config) {
|
|||
"Dispatcher configuration" must {
|
||||
"support custom mailboxType" in {
|
||||
val actor = system.actorOf(Props.empty.withDispatcher("my-dispatcher"))
|
||||
val queue = actor.asInstanceOf[LocalActorRef].underlying.mailbox.messageQueue
|
||||
awaitCond(actor match {
|
||||
case r: RepointableRef ⇒ r.isStarted
|
||||
case _ ⇒ true
|
||||
}, 1 second, 10 millis)
|
||||
val queue = actor.asInstanceOf[ActorRefWithCell].underlying.asInstanceOf[ActorCell].mailbox.messageQueue
|
||||
queue.getClass must be(classOf[CustomMailboxSpec.MyMailbox])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package akka.dispatch
|
||||
|
||||
import akka.actor.{ Props, LocalActorRef, Actor }
|
||||
import akka.testkit.AkkaSpec
|
||||
import akka.pattern.ask
|
||||
import akka.util.duration._
|
||||
import akka.testkit.DefaultTimeout
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import akka.actor.ActorSystem
|
||||
|
||||
import akka.actor.{ Props, InternalActorRef, ActorSystem, Actor }
|
||||
import akka.pattern.ask
|
||||
import akka.testkit.{ DefaultTimeout, AkkaSpec }
|
||||
import akka.util.duration.intToDurationInt
|
||||
|
||||
object PriorityDispatcherSpec {
|
||||
val config = """
|
||||
|
|
@ -54,7 +56,7 @@ class PriorityDispatcherSpec extends AkkaSpec(PriorityDispatcherSpec.config) wit
|
|||
case i: Int ⇒ acc = i :: acc
|
||||
case 'Result ⇒ sender.tell(acc)
|
||||
}
|
||||
}).withDispatcher(dispatcherKey)).asInstanceOf[LocalActorRef]
|
||||
}).withDispatcher(dispatcherKey)).asInstanceOf[InternalActorRef]
|
||||
|
||||
actor.suspend //Make sure the actor isn't treating any messages, let it buffer the incoming messages
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,17 @@ class EventStreamSpec extends AkkaSpec(EventStreamSpec.config) {
|
|||
}
|
||||
}
|
||||
|
||||
"not allow null as subscriber" in {
|
||||
val bus = new EventStream(true)
|
||||
intercept[IllegalArgumentException] { bus.subscribe(null, classOf[M]) }.getMessage must be("subscriber is null")
|
||||
}
|
||||
|
||||
"not allow null as unsubscriber" in {
|
||||
val bus = new EventStream(true)
|
||||
intercept[IllegalArgumentException] { bus.unsubscribe(null, classOf[M]) }.getMessage must be("subscriber is null")
|
||||
intercept[IllegalArgumentException] { bus.unsubscribe(null) }.getMessage must be("subscriber is null")
|
||||
}
|
||||
|
||||
"be able to log unhandled messages" in {
|
||||
val sys = ActorSystem("EventStreamSpecUnhandled", configUnhandled)
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@
|
|||
package akka.routing
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import akka.actor.{ Props, LocalActorRef, Deploy, Actor, ActorRef }
|
||||
import akka.actor.{ Props, Deploy, Actor, ActorRef }
|
||||
import akka.ConfigurationException
|
||||
import akka.dispatch.Await
|
||||
import akka.pattern.{ ask, gracefulStop }
|
||||
import akka.testkit.{ TestLatch, ImplicitSender, DefaultTimeout, AkkaSpec }
|
||||
import akka.util.duration.intToDurationInt
|
||||
import akka.actor.UnstartedCell
|
||||
|
||||
object ConfiguredLocalRoutingSpec {
|
||||
val config = """
|
||||
|
|
@ -47,6 +46,14 @@ object ConfiguredLocalRoutingSpec {
|
|||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class ConfiguredLocalRoutingSpec extends AkkaSpec(ConfiguredLocalRoutingSpec.config) with DefaultTimeout with ImplicitSender {
|
||||
|
||||
def routerConfig(ref: ActorRef): RouterConfig = ref match {
|
||||
case r: RoutedActorRef ⇒
|
||||
r.underlying match {
|
||||
case c: RoutedActorCell ⇒ c.routerConfig
|
||||
case _: UnstartedCell ⇒ awaitCond(r.isStarted, 1 second, 10 millis); routerConfig(ref)
|
||||
}
|
||||
}
|
||||
|
||||
"RouterConfig" must {
|
||||
|
||||
"be picked up from Props" in {
|
||||
|
|
@ -55,7 +62,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec(ConfiguredLocalRoutingSpec.con
|
|||
case "get" ⇒ sender ! context.props
|
||||
}
|
||||
}).withRouter(RoundRobinRouter(12)), "someOther")
|
||||
actor.asInstanceOf[LocalActorRef].underlying.props.routerConfig must be === RoundRobinRouter(12)
|
||||
routerConfig(actor) must be === RoundRobinRouter(12)
|
||||
Await.result(gracefulStop(actor, 3 seconds), 3 seconds)
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +72,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec(ConfiguredLocalRoutingSpec.con
|
|||
case "get" ⇒ sender ! context.props
|
||||
}
|
||||
}).withRouter(RoundRobinRouter(12)), "config")
|
||||
actor.asInstanceOf[LocalActorRef].underlying.props.routerConfig must be === RandomRouter(4)
|
||||
routerConfig(actor) must be === RandomRouter(4)
|
||||
Await.result(gracefulStop(actor, 3 seconds), 3 seconds)
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +82,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec(ConfiguredLocalRoutingSpec.con
|
|||
case "get" ⇒ sender ! context.props
|
||||
}
|
||||
}).withRouter(FromConfig).withDeploy(Deploy(routerConfig = RoundRobinRouter(12))), "someOther")
|
||||
actor.asInstanceOf[LocalActorRef].underlying.props.routerConfig must be === RoundRobinRouter(12)
|
||||
routerConfig(actor) must be === RoundRobinRouter(12)
|
||||
Await.result(gracefulStop(actor, 3 seconds), 3 seconds)
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +92,7 @@ class ConfiguredLocalRoutingSpec extends AkkaSpec(ConfiguredLocalRoutingSpec.con
|
|||
case "get" ⇒ sender ! context.props
|
||||
}
|
||||
}).withRouter(FromConfig).withDeploy(Deploy(routerConfig = RoundRobinRouter(12))), "config")
|
||||
actor.asInstanceOf[LocalActorRef].underlying.props.routerConfig must be === RandomRouter(4)
|
||||
routerConfig(actor) must be === RandomRouter(4)
|
||||
Await.result(gracefulStop(actor, 3 seconds), 3 seconds)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ import akka.dispatch.Await
|
|||
import akka.util.Duration
|
||||
import akka.ConfigurationException
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.pattern.ask
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import com.typesafe.config.Config
|
||||
import akka.dispatch.Dispatchers
|
||||
import akka.util.Timeout
|
||||
|
||||
object RoutingSpec {
|
||||
|
||||
|
|
@ -25,6 +26,10 @@ object RoutingSpec {
|
|||
router = round-robin
|
||||
nr-of-instances = 3
|
||||
}
|
||||
/router2 {
|
||||
router = round-robin
|
||||
nr-of-instances = 3
|
||||
}
|
||||
/myrouter {
|
||||
router = "akka.routing.RoutingSpec$MyRouter"
|
||||
foo = bar
|
||||
|
|
@ -128,7 +133,7 @@ class RoutingSpec extends AkkaSpec(RoutingSpec.config) with DefaultTimeout with
|
|||
}
|
||||
|
||||
"use configured nr-of-instances when router is specified" in {
|
||||
val router = system.actorOf(Props[TestActor].withRouter(RoundRobinRouter(nrOfInstances = 2)), "router1")
|
||||
val router = system.actorOf(Props[TestActor].withRouter(RoundRobinRouter(nrOfInstances = 2)), "router2")
|
||||
Await.result(router ? CurrentRoutees, 5 seconds).asInstanceOf[RouterRoutees].routees.size must be(3)
|
||||
system.stop(router)
|
||||
}
|
||||
|
|
@ -171,6 +176,18 @@ class RoutingSpec extends AkkaSpec(RoutingSpec.config) with DefaultTimeout with
|
|||
expectMsg("restarted")
|
||||
}
|
||||
|
||||
"must start in-line for context.actorOf()" in {
|
||||
system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case "start" ⇒
|
||||
context.actorOf(Props(new Actor {
|
||||
def receive = { case x ⇒ sender ! x }
|
||||
}).withRouter(RoundRobinRouter(2))) ? "hello" pipeTo sender
|
||||
}
|
||||
})) ! "start"
|
||||
expectMsg("hello")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"no router" must {
|
||||
|
|
@ -528,7 +545,7 @@ class RoutingSpec extends AkkaSpec(RoutingSpec.config) with DefaultTimeout with
|
|||
}
|
||||
}
|
||||
"support custom router" in {
|
||||
val myrouter = system.actorOf(Props().withRouter(FromConfig), "myrouter")
|
||||
val myrouter = system.actorOf(Props.empty.withRouter(FromConfig), "myrouter")
|
||||
myrouter.isTerminated must be(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -540,7 +557,7 @@ class RoutingSpec extends AkkaSpec(RoutingSpec.config) with DefaultTimeout with
|
|||
}
|
||||
|
||||
"count votes as intended - not as in Florida" in {
|
||||
val routedActor = system.actorOf(Props().withRouter(VoteCountRouter()))
|
||||
val routedActor = system.actorOf(Props.empty.withRouter(VoteCountRouter()))
|
||||
routedActor ! DemocratVote
|
||||
routedActor ! DemocratVote
|
||||
routedActor ! RepublicanVote
|
||||
|
|
|
|||
|
|
@ -8,10 +8,14 @@ import akka.util.Unsafe;
|
|||
|
||||
final class AbstractActorCell {
|
||||
final static long mailboxOffset;
|
||||
final static long childrenOffset;
|
||||
final static long nextNameOffset;
|
||||
|
||||
static {
|
||||
try {
|
||||
mailboxOffset = Unsafe.instance.objectFieldOffset(ActorCell.class.getDeclaredField("_mailboxDoNotCallMeDirectly"));
|
||||
childrenOffset = Unsafe.instance.objectFieldOffset(ActorCell.class.getDeclaredField("_childrenRefsDoNotCallMeDirectly"));
|
||||
nextNameOffset = Unsafe.instance.objectFieldOffset(ActorCell.class.getDeclaredField("_nextNameDoNotCallMeDirectly"));
|
||||
} catch(Throwable t){
|
||||
throw new ExceptionInInitializerError(t);
|
||||
}
|
||||
|
|
|
|||
19
akka-actor/src/main/java/akka/actor/AbstractActorRef.java
Normal file
19
akka-actor/src/main/java/akka/actor/AbstractActorRef.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.actor;
|
||||
|
||||
import akka.util.Unsafe;
|
||||
|
||||
final class AbstractActorRef {
|
||||
final static long cellOffset;
|
||||
|
||||
static {
|
||||
try {
|
||||
cellOffset = Unsafe.instance.objectFieldOffset(RepointableActorRef.class.getDeclaredField("_cellDoNotCallMeDirectly"));
|
||||
} catch(Throwable t){
|
||||
throw new ExceptionInInitializerError(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ package akka
|
|||
* <ul>
|
||||
* <li>a uuid for tracking purposes</li>
|
||||
* <li>toString that includes exception name, message and uuid</li>
|
||||
* <li>toLongString which also includes the stack trace</li>
|
||||
* </ul>
|
||||
*/
|
||||
//TODO add @SerialVersionUID(1L) when SI-4804 is fixed
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ case object Kill extends Kill {
|
|||
/**
|
||||
* When Death Watch is used, the watcher will receive a Terminated(watched) message when watched is terminated.
|
||||
*/
|
||||
case class Terminated(@BeanProperty actor: ActorRef)(@BeanProperty val existenceConfirmed: Boolean)
|
||||
case class Terminated(@BeanProperty actor: ActorRef)(@BeanProperty val existenceConfirmed: Boolean) extends AutoReceivedMessage
|
||||
|
||||
abstract class ReceiveTimeout extends PossiblyHarmful
|
||||
|
||||
|
|
@ -134,8 +134,7 @@ class ActorInitializationException private[akka] (actor: ActorRef, message: Stri
|
|||
* there might be more of them in the future, or not.
|
||||
*/
|
||||
class InvalidMessageException private[akka] (message: String, cause: Throwable = null)
|
||||
extends AkkaException(message, cause)
|
||||
with NoStackTrace {
|
||||
extends AkkaException(message, cause) {
|
||||
def this(msg: String) = this(msg, null)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import akka.serialization.SerializationExtension
|
|||
import akka.event.Logging.LogEventException
|
||||
import collection.immutable.{ TreeSet, TreeMap }
|
||||
import akka.util.{ Unsafe, Duration, Helpers, NonFatal }
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
//TODO: everything here for current compatibility - could be limited more
|
||||
|
||||
|
|
@ -167,6 +168,78 @@ trait UntypedActorContext extends ActorContext {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] trait Cell {
|
||||
/**
|
||||
* The “self” reference which this Cell is attached to.
|
||||
*/
|
||||
def self: ActorRef
|
||||
/**
|
||||
* The system within which this Cell lives.
|
||||
*/
|
||||
def system: ActorSystem
|
||||
/**
|
||||
* The system internals where this Cell lives.
|
||||
*/
|
||||
def systemImpl: ActorSystemImpl
|
||||
/**
|
||||
* Recursively suspend this actor and all its children.
|
||||
*/
|
||||
def suspend(): Unit
|
||||
/**
|
||||
* Recursively resume this actor and all its children.
|
||||
*/
|
||||
def resume(): Unit
|
||||
/**
|
||||
* Restart this actor (will recursively restart or stop all children).
|
||||
*/
|
||||
def restart(cause: Throwable): Unit
|
||||
/**
|
||||
* Recursively terminate this actor and all its children.
|
||||
*/
|
||||
def stop(): Unit
|
||||
/**
|
||||
* Returns “true” if the actor is locally known to be terminated, “false” if
|
||||
* alive or uncertain.
|
||||
*/
|
||||
def isTerminated: Boolean
|
||||
/**
|
||||
* The supervisor of this actor.
|
||||
*/
|
||||
def parent: InternalActorRef
|
||||
/**
|
||||
* All children of this actor, including only reserved-names.
|
||||
*/
|
||||
def childrenRefs: ActorCell.ChildrenContainer
|
||||
/**
|
||||
* Enqueue a message to be sent to the actor; may or may not actually
|
||||
* schedule the actor to run, depending on which type of cell it is.
|
||||
*/
|
||||
def tell(message: Any, sender: ActorRef): Unit
|
||||
/**
|
||||
* Enqueue a message to be sent to the actor; may or may not actually
|
||||
* schedule the actor to run, depending on which type of cell it is.
|
||||
*/
|
||||
def sendSystemMessage(msg: SystemMessage): Unit
|
||||
/**
|
||||
* Returns true if the actor is local, i.e. if it is actually scheduled
|
||||
* on a Thread in the current JVM when run.
|
||||
*/
|
||||
def isLocal: Boolean
|
||||
/**
|
||||
* If the actor isLocal, returns whether messages are currently queued,
|
||||
* “false” otherwise.
|
||||
*/
|
||||
def hasMessages: Boolean
|
||||
/**
|
||||
* If the actor isLocal, returns the number of messages currently queued,
|
||||
* which may be a costly operation, 0 otherwise.
|
||||
*/
|
||||
def numberOfMessages: Int
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything in here is completely Akka PRIVATE. You will not find any
|
||||
* supported APIs in this place. This is not the API you were looking
|
||||
|
|
@ -201,10 +274,18 @@ private[akka] object ActorCell {
|
|||
def children: Iterable[ActorRef]
|
||||
def stats: Iterable[ChildRestartStats]
|
||||
def shallDie(actor: ActorRef): ChildrenContainer
|
||||
/**
|
||||
* reserve that name or throw an exception
|
||||
*/
|
||||
def reserve(name: String): ChildrenContainer
|
||||
/**
|
||||
* cancel a reservation
|
||||
*/
|
||||
def unreserve(name: String): ChildrenContainer
|
||||
}
|
||||
|
||||
trait EmptyChildrenContainer extends ChildrenContainer {
|
||||
val emptyStats = TreeMap.empty[String, ChildRestartStats]
|
||||
val emptyStats = TreeMap.empty[String, ChildStats]
|
||||
def add(child: ActorRef): ChildrenContainer =
|
||||
new NormalChildrenContainer(emptyStats.updated(child.path.name, ChildRestartStats(child)))
|
||||
def remove(child: ActorRef): ChildrenContainer = this
|
||||
|
|
@ -213,6 +294,8 @@ private[akka] object ActorCell {
|
|||
def children: Iterable[ActorRef] = Nil
|
||||
def stats: Iterable[ChildRestartStats] = Nil
|
||||
def shallDie(actor: ActorRef): ChildrenContainer = this
|
||||
def reserve(name: String): ChildrenContainer = new NormalChildrenContainer(emptyStats.updated(name, ChildNameReserved))
|
||||
def unreserve(name: String): ChildrenContainer = this
|
||||
override def toString = "no children"
|
||||
}
|
||||
|
||||
|
|
@ -228,6 +311,8 @@ private[akka] object ActorCell {
|
|||
*/
|
||||
object TerminatedChildrenContainer extends EmptyChildrenContainer {
|
||||
override def add(child: ActorRef): ChildrenContainer = this
|
||||
override def reserve(name: String): ChildrenContainer =
|
||||
throw new IllegalStateException("cannot reserve actor name '" + name + "': already terminated")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -236,32 +321,46 @@ private[akka] object ActorCell {
|
|||
* calling context.stop(child) and processing the ChildTerminated() system
|
||||
* message).
|
||||
*/
|
||||
class NormalChildrenContainer(c: TreeMap[String, ChildRestartStats]) extends ChildrenContainer {
|
||||
class NormalChildrenContainer(c: TreeMap[String, ChildStats]) extends ChildrenContainer {
|
||||
|
||||
def add(child: ActorRef): ChildrenContainer = new NormalChildrenContainer(c.updated(child.path.name, ChildRestartStats(child)))
|
||||
def add(child: ActorRef): ChildrenContainer =
|
||||
new NormalChildrenContainer(c.updated(child.path.name, ChildRestartStats(child)))
|
||||
|
||||
def remove(child: ActorRef): ChildrenContainer = NormalChildrenContainer(c - child.path.name)
|
||||
|
||||
def getByName(name: String): Option[ChildRestartStats] = c get name
|
||||
|
||||
def getByRef(actor: ActorRef): Option[ChildRestartStats] = c get actor.path.name match {
|
||||
case c @ Some(crs) if (crs.child == actor) ⇒ c
|
||||
case _ ⇒ None
|
||||
def getByName(name: String): Option[ChildRestartStats] = c.get(name) match {
|
||||
case s @ Some(_: ChildRestartStats) ⇒ s.asInstanceOf[Option[ChildRestartStats]]
|
||||
case _ ⇒ None
|
||||
}
|
||||
|
||||
def children: Iterable[ActorRef] = c.values.view.map(_.child)
|
||||
def getByRef(actor: ActorRef): Option[ChildRestartStats] = c.get(actor.path.name) match {
|
||||
case c @ Some(crs: ChildRestartStats) if (crs.child == actor) ⇒ c.asInstanceOf[Option[ChildRestartStats]]
|
||||
case _ ⇒ None
|
||||
}
|
||||
|
||||
def stats: Iterable[ChildRestartStats] = c.values
|
||||
def children: Iterable[ActorRef] = c.values.view.collect { case ChildRestartStats(child, _, _) ⇒ child }
|
||||
|
||||
def stats: Iterable[ChildRestartStats] = c.values.collect { case c: ChildRestartStats ⇒ c }
|
||||
|
||||
def shallDie(actor: ActorRef): ChildrenContainer = TerminatingChildrenContainer(c, Set(actor), UserRequest)
|
||||
|
||||
def reserve(name: String): ChildrenContainer =
|
||||
if (c contains name)
|
||||
throw new InvalidActorNameException("actor name " + name + " is not unique!")
|
||||
else new NormalChildrenContainer(c.updated(name, ChildNameReserved))
|
||||
|
||||
def unreserve(name: String): ChildrenContainer = c.get(name) match {
|
||||
case Some(ChildNameReserved) ⇒ NormalChildrenContainer(c - name)
|
||||
case _ ⇒ this
|
||||
}
|
||||
|
||||
override def toString =
|
||||
if (c.size > 20) c.size + " children"
|
||||
else c.mkString("children:\n ", "\n ", "")
|
||||
}
|
||||
|
||||
object NormalChildrenContainer {
|
||||
def apply(c: TreeMap[String, ChildRestartStats]): ChildrenContainer =
|
||||
def apply(c: TreeMap[String, ChildStats]): ChildrenContainer =
|
||||
if (c.isEmpty) EmptyChildrenContainer
|
||||
else new NormalChildrenContainer(c)
|
||||
}
|
||||
|
|
@ -276,7 +375,7 @@ private[akka] object ActorCell {
|
|||
* type of container, depending on whether or not children are left and whether or not
|
||||
* the reason was “Terminating”.
|
||||
*/
|
||||
case class TerminatingChildrenContainer(c: TreeMap[String, ChildRestartStats], toDie: Set[ActorRef], reason: SuspendReason)
|
||||
case class TerminatingChildrenContainer(c: TreeMap[String, ChildStats], toDie: Set[ActorRef], reason: SuspendReason)
|
||||
extends ChildrenContainer {
|
||||
|
||||
def add(child: ActorRef): ChildrenContainer = copy(c.updated(child.path.name, ChildRestartStats(child)))
|
||||
|
|
@ -290,19 +389,35 @@ private[akka] object ActorCell {
|
|||
else copy(c - child.path.name, t)
|
||||
}
|
||||
|
||||
def getByName(name: String): Option[ChildRestartStats] = c get name
|
||||
|
||||
def getByRef(actor: ActorRef): Option[ChildRestartStats] = c get actor.path.name match {
|
||||
case c @ Some(crs) if (crs.child == actor) ⇒ c
|
||||
case _ ⇒ None
|
||||
def getByName(name: String): Option[ChildRestartStats] = c.get(name) match {
|
||||
case s @ Some(_: ChildRestartStats) ⇒ s.asInstanceOf[Option[ChildRestartStats]]
|
||||
case _ ⇒ None
|
||||
}
|
||||
|
||||
def children: Iterable[ActorRef] = c.values.view.map(_.child)
|
||||
def getByRef(actor: ActorRef): Option[ChildRestartStats] = c.get(actor.path.name) match {
|
||||
case c @ Some(crs: ChildRestartStats) if (crs.child == actor) ⇒ c.asInstanceOf[Option[ChildRestartStats]]
|
||||
case _ ⇒ None
|
||||
}
|
||||
|
||||
def stats: Iterable[ChildRestartStats] = c.values
|
||||
def children: Iterable[ActorRef] = c.values.view.collect { case ChildRestartStats(child, _, _) ⇒ child }
|
||||
|
||||
def stats: Iterable[ChildRestartStats] = c.values.collect { case c: ChildRestartStats ⇒ c }
|
||||
|
||||
def shallDie(actor: ActorRef): ChildrenContainer = copy(toDie = toDie + actor)
|
||||
|
||||
def reserve(name: String): ChildrenContainer = reason match {
|
||||
case Termination ⇒ throw new IllegalStateException("cannot reserve actor name '" + name + "': terminating")
|
||||
case _ ⇒
|
||||
if (c contains name)
|
||||
throw new InvalidActorNameException("actor name " + name + " is not unique!")
|
||||
else copy(c = c.updated(name, ChildNameReserved))
|
||||
}
|
||||
|
||||
def unreserve(name: String): ChildrenContainer = c.get(name) match {
|
||||
case Some(ChildNameReserved) ⇒ copy(c = c - name)
|
||||
case _ ⇒ this
|
||||
}
|
||||
|
||||
override def toString =
|
||||
if (c.size > 20) c.size + " children"
|
||||
else c.mkString("children (" + toDie.size + " terminating):\n ", "\n ", "\n") + toDie
|
||||
|
|
@ -316,10 +431,13 @@ private[akka] class ActorCell(
|
|||
val system: ActorSystemImpl,
|
||||
val self: InternalActorRef,
|
||||
val props: Props,
|
||||
@volatile var parent: InternalActorRef) extends UntypedActorContext {
|
||||
import AbstractActorCell.mailboxOffset
|
||||
@volatile var parent: InternalActorRef) extends UntypedActorContext with Cell {
|
||||
|
||||
import AbstractActorCell.{ mailboxOffset, childrenOffset, nextNameOffset }
|
||||
import ActorCell._
|
||||
|
||||
final def isLocal = true
|
||||
|
||||
final def systemImpl = system
|
||||
|
||||
protected final def guardian = self
|
||||
|
|
@ -353,7 +471,46 @@ private[akka] class ActorCell(
|
|||
var receiveTimeoutData: (Long, Cancellable) = emptyReceiveTimeoutData
|
||||
|
||||
@volatile
|
||||
var childrenRefs: ChildrenContainer = EmptyChildrenContainer
|
||||
private var _childrenRefsDoNotCallMeDirectly: ChildrenContainer = EmptyChildrenContainer
|
||||
|
||||
def childrenRefs: ChildrenContainer = Unsafe.instance.getObjectVolatile(this, childrenOffset).asInstanceOf[ChildrenContainer]
|
||||
|
||||
private def swapChildrenRefs(oldChildren: ChildrenContainer, newChildren: ChildrenContainer): Boolean =
|
||||
Unsafe.instance.compareAndSwapObject(this, childrenOffset, oldChildren, newChildren)
|
||||
|
||||
@tailrec private def reserveChild(name: String): Boolean = {
|
||||
val c = childrenRefs
|
||||
swapChildrenRefs(c, c.reserve(name)) || reserveChild(name)
|
||||
}
|
||||
|
||||
@tailrec private def unreserveChild(name: String): Boolean = {
|
||||
val c = childrenRefs
|
||||
swapChildrenRefs(c, c.unreserve(name)) || unreserveChild(name)
|
||||
}
|
||||
|
||||
@tailrec private def addChild(ref: ActorRef): Boolean = {
|
||||
val c = childrenRefs
|
||||
swapChildrenRefs(c, c.add(ref)) || addChild(ref)
|
||||
}
|
||||
|
||||
@tailrec private def shallDie(ref: ActorRef): Boolean = {
|
||||
val c = childrenRefs
|
||||
swapChildrenRefs(c, c.shallDie(ref)) || shallDie(ref)
|
||||
}
|
||||
|
||||
@tailrec private def removeChild(ref: ActorRef): ChildrenContainer = {
|
||||
val c = childrenRefs
|
||||
val n = c.remove(ref)
|
||||
if (swapChildrenRefs(c, n)) n
|
||||
else removeChild(ref)
|
||||
}
|
||||
|
||||
@tailrec private def setChildrenTerminationReason(reason: SuspendReason): Boolean = {
|
||||
childrenRefs match {
|
||||
case c: TerminatingChildrenContainer ⇒ swapChildrenRefs(c, c.copy(reason = reason)) || setChildrenTerminationReason(reason)
|
||||
case _ ⇒ false
|
||||
}
|
||||
}
|
||||
|
||||
private def isTerminating = childrenRefs match {
|
||||
case TerminatingChildrenContainer(_, _, Termination) ⇒ true
|
||||
|
|
@ -365,7 +522,7 @@ private[akka] class ActorCell(
|
|||
case _ ⇒ true
|
||||
}
|
||||
|
||||
private def _actorOf(props: Props, name: String): ActorRef = {
|
||||
private def _actorOf(props: Props, name: String, async: Boolean): ActorRef = {
|
||||
if (system.settings.SerializeAllCreators && !props.creator.isInstanceOf[NoSerializationVerificationNeeded]) {
|
||||
val ser = SerializationExtension(system)
|
||||
ser.serialize(props.creator) match {
|
||||
|
|
@ -376,53 +533,74 @@ private[akka] class ActorCell(
|
|||
}
|
||||
}
|
||||
}
|
||||
// in case we are currently terminating, swallow creation requests and return EmptyLocalActorRef
|
||||
if (isTerminating) provider.actorFor(self, Seq(name))
|
||||
/*
|
||||
* in case we are currently terminating, fail external attachChild requests
|
||||
* (internal calls cannot happen anyway because we are suspended)
|
||||
*/
|
||||
if (isTerminating) throw new IllegalStateException("cannot create children while terminating or terminated")
|
||||
else {
|
||||
val actor = provider.actorOf(systemImpl, props, self, self.path / name, false, None, true)
|
||||
childrenRefs = childrenRefs.add(actor)
|
||||
reserveChild(name)
|
||||
// this name will either be unreserved or overwritten with a real child below
|
||||
val actor =
|
||||
try {
|
||||
provider.actorOf(systemImpl, props, self, self.path / name,
|
||||
systemService = false, deploy = None, lookupDeploy = true, async = async)
|
||||
} catch {
|
||||
case NonFatal(e) ⇒
|
||||
unreserveChild(name)
|
||||
throw e
|
||||
}
|
||||
addChild(actor)
|
||||
actor
|
||||
}
|
||||
}
|
||||
|
||||
def actorOf(props: Props): ActorRef = _actorOf(props, randomName())
|
||||
def actorOf(props: Props): ActorRef = _actorOf(props, randomName(), async = false)
|
||||
|
||||
def actorOf(props: Props, name: String): ActorRef = {
|
||||
def actorOf(props: Props, name: String): ActorRef = _actorOf(props, checkName(name), async = false)
|
||||
|
||||
private def checkName(name: String): String = {
|
||||
import ActorPath.ElementRegex
|
||||
name match {
|
||||
case null ⇒ throw new InvalidActorNameException("actor name must not be null")
|
||||
case "" ⇒ throw new InvalidActorNameException("actor name must not be empty")
|
||||
case ElementRegex() ⇒ // this is fine
|
||||
case ElementRegex() ⇒ name
|
||||
case _ ⇒ throw new InvalidActorNameException("illegal actor name '" + name + "', must conform to " + ElementRegex)
|
||||
}
|
||||
childrenRefs.getByName(name) match {
|
||||
case None ⇒ _actorOf(props, name)
|
||||
case _ ⇒ throw new InvalidActorNameException("actor name " + name + " is not unique!")
|
||||
}
|
||||
}
|
||||
|
||||
private[akka] def attachChild(props: Props, name: String): ActorRef =
|
||||
_actorOf(props, checkName(name), async = true)
|
||||
|
||||
private[akka] def attachChild(props: Props): ActorRef =
|
||||
_actorOf(props, randomName(), async = true)
|
||||
|
||||
final def stop(actor: ActorRef): Unit = {
|
||||
if (childrenRefs.getByRef(actor).isDefined) childrenRefs = childrenRefs.shallDie(actor)
|
||||
val started = actor match {
|
||||
case r: RepointableRef ⇒ r.isStarted
|
||||
case _ ⇒ true
|
||||
}
|
||||
if (childrenRefs.getByRef(actor).isDefined && started) shallDie(actor)
|
||||
actor.asInstanceOf[InternalActorRef].stop()
|
||||
}
|
||||
|
||||
var currentMessage: Envelope = _
|
||||
var actor: Actor = _
|
||||
private var behaviorStack: List[Actor.Receive] = emptyBehaviorStack
|
||||
@volatile var _mailboxDoNotCallMeDirectly: Mailbox = _ //This must be volatile since it isn't protected by the mailbox status
|
||||
var nextNameSequence: Long = 0
|
||||
var watching: Set[ActorRef] = emptyActorRefSet
|
||||
var watchedBy: Set[ActorRef] = emptyActorRefSet
|
||||
|
||||
//Not thread safe, so should only be used inside the actor that inhabits this ActorCell
|
||||
@volatile private var _nextNameDoNotCallMeDirectly = 0L
|
||||
final protected def randomName(): String = {
|
||||
val n = nextNameSequence
|
||||
nextNameSequence = n + 1
|
||||
Helpers.base64(n)
|
||||
@tailrec def inc(): Long = {
|
||||
val current = Unsafe.instance.getLongVolatile(this, nextNameOffset)
|
||||
if (Unsafe.instance.compareAndSwapLong(this, nextNameOffset, current, current + 1)) current
|
||||
else inc()
|
||||
}
|
||||
Helpers.base64(inc())
|
||||
}
|
||||
|
||||
@inline
|
||||
final val dispatcher: MessageDispatcher = system.dispatchers.lookup(props.dispatcher)
|
||||
@volatile private var _mailboxDoNotCallMeDirectly: Mailbox = _ //This must be volatile since it isn't protected by the mailbox status
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
|
|
@ -442,6 +620,12 @@ private[akka] class ActorCell(
|
|||
else oldMailbox
|
||||
}
|
||||
|
||||
final def hasMessages: Boolean = mailbox.hasMessages
|
||||
|
||||
final def numberOfMessages: Int = mailbox.numberOfMessages
|
||||
|
||||
val dispatcher: MessageDispatcher = system.dispatchers.lookup(props.dispatcher)
|
||||
|
||||
/**
|
||||
* UntypedActorContext impl
|
||||
*/
|
||||
|
|
@ -449,20 +633,22 @@ private[akka] class ActorCell(
|
|||
|
||||
final def isTerminated: Boolean = mailbox.isClosed
|
||||
|
||||
final def start(): Unit = {
|
||||
final def start(): this.type = {
|
||||
|
||||
/*
|
||||
* Create the mailbox and enqueue the Create() message to ensure that
|
||||
* this is processed before anything else.
|
||||
*/
|
||||
swapMailbox(dispatcher.createMailbox(this))
|
||||
mailbox.setActor(this)
|
||||
|
||||
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
|
||||
mailbox.systemEnqueue(self, Create())
|
||||
|
||||
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
|
||||
parent.sendSystemMessage(akka.dispatch.Supervise(self))
|
||||
|
||||
// This call is expected to start off the actor by scheduling its mailbox.
|
||||
dispatcher.attach(this)
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
|
||||
|
|
@ -500,8 +686,10 @@ private[akka] class ActorCell(
|
|||
final def getChildren(): java.lang.Iterable[ActorRef] =
|
||||
scala.collection.JavaConverters.asJavaIterableConverter(children).asJava
|
||||
|
||||
final def tell(message: Any, sender: ActorRef): Unit =
|
||||
dispatcher.dispatch(this, Envelope(message, if (sender eq null) system.deadLetters else sender)(system))
|
||||
def tell(message: Any, sender: ActorRef): Unit =
|
||||
dispatcher.dispatch(this, Envelope(message, if (sender eq null) system.deadLetters else sender, system))
|
||||
|
||||
override def sendSystemMessage(message: SystemMessage): Unit = dispatcher.systemDispatch(this, message)
|
||||
|
||||
final def sender: ActorRef = currentMessage match {
|
||||
case null ⇒ system.deadLetters
|
||||
|
|
@ -564,7 +752,7 @@ private[akka] class ActorCell(
|
|||
}
|
||||
childrenRefs match {
|
||||
case ct: TerminatingChildrenContainer ⇒
|
||||
childrenRefs = ct.copy(reason = Recreation(cause))
|
||||
setChildrenTerminationReason(Recreation(cause))
|
||||
dispatcher suspend this
|
||||
case _ ⇒
|
||||
doRecreate(cause, failedActor)
|
||||
|
|
@ -622,7 +810,7 @@ private[akka] class ActorCell(
|
|||
|
||||
childrenRefs match {
|
||||
case ct: TerminatingChildrenContainer ⇒
|
||||
childrenRefs = ct.copy(reason = Termination)
|
||||
setChildrenTerminationReason(Termination)
|
||||
// do not process normal messages while waiting for all children to terminate
|
||||
dispatcher suspend this
|
||||
if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "stopping"))
|
||||
|
|
@ -631,7 +819,8 @@ private[akka] class ActorCell(
|
|||
}
|
||||
|
||||
def supervise(child: ActorRef): Unit = if (!isTerminating) {
|
||||
if (childrenRefs.getByRef(child).isEmpty) childrenRefs = childrenRefs.add(child)
|
||||
if (childrenRefs.getByRef(child).isEmpty) addChild(child)
|
||||
handleSupervise(child)
|
||||
if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, clazz(actor), "now supervising " + child))
|
||||
}
|
||||
|
||||
|
|
@ -646,6 +835,7 @@ private[akka] class ActorCell(
|
|||
case Terminate() ⇒ terminate()
|
||||
case Supervise(child) ⇒ supervise(child)
|
||||
case ChildTerminated(child) ⇒ handleChildTerminated(child)
|
||||
case NoMessage ⇒ // only here to suppress warning
|
||||
}
|
||||
} catch {
|
||||
case e @ (_: InterruptedException | NonFatal(_)) ⇒ handleInvokeFailure(e, "error while processing " + message)
|
||||
|
|
@ -706,6 +896,7 @@ private[akka] class ActorCell(
|
|||
|
||||
msg.message match {
|
||||
case Failed(cause) ⇒ handleFailure(sender, cause)
|
||||
case t: Terminated ⇒ watching -= t.actor; receiveMessage(t)
|
||||
case Kill ⇒ throw new ActorKilledException("Kill")
|
||||
case PoisonPill ⇒ self.stop()
|
||||
case SelectParent(m) ⇒ parent.tell(m, msg.sender)
|
||||
|
|
@ -794,8 +985,7 @@ private[akka] class ActorCell(
|
|||
final def handleChildTerminated(child: ActorRef): Unit = try {
|
||||
childrenRefs match {
|
||||
case tc @ TerminatingChildrenContainer(_, _, reason) ⇒
|
||||
val n = tc.remove(child)
|
||||
childrenRefs = n
|
||||
val n = removeChild(child)
|
||||
actor.supervisorStrategy.handleChildTerminated(this, child, children)
|
||||
if (!n.isInstanceOf[TerminatingChildrenContainer]) reason match {
|
||||
case Recreation(cause) ⇒ doRecreate(cause, actor) // doRecreate since this is the continuation of "recreate"
|
||||
|
|
@ -803,7 +993,7 @@ private[akka] class ActorCell(
|
|||
case _ ⇒
|
||||
}
|
||||
case _ ⇒
|
||||
childrenRefs = childrenRefs.remove(child)
|
||||
removeChild(child)
|
||||
actor.supervisorStrategy.handleChildTerminated(this, child, children)
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -816,6 +1006,11 @@ private[akka] class ActorCell(
|
|||
}
|
||||
}
|
||||
|
||||
protected def handleSupervise(child: ActorRef): Unit = child match {
|
||||
case r: RepointableActorRef ⇒ r.activate()
|
||||
case _ ⇒
|
||||
}
|
||||
|
||||
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
|
||||
final def restart(cause: Throwable): Unit = dispatcher.systemDispatch(this, Recreate(cause))
|
||||
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ final class ChildActorPath(val parent: ActorPath, val name: String) extends Acto
|
|||
|
||||
// TODO RK investigate Phil’s hash from scala.collection.mutable.HashTable.improve
|
||||
override def hashCode: Int = {
|
||||
import scala.util.MurmurHash._
|
||||
import akka.routing.MurmurHash._
|
||||
|
||||
@tailrec
|
||||
def rec(p: ActorPath, h: Int, c: Int, k: Int): Int = p match {
|
||||
|
|
|
|||
|
|
@ -163,10 +163,24 @@ private[akka] trait ActorRefScope {
|
|||
def isLocal: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Refs which are statically known to be local inherit from this Scope
|
||||
*/
|
||||
private[akka] trait LocalRef extends ActorRefScope {
|
||||
final def isLocal = true
|
||||
}
|
||||
|
||||
/**
|
||||
* RepointableActorRef (and potentially others) may change their locality at
|
||||
* runtime, meaning that isLocal might not be stable. RepointableActorRef has
|
||||
* the feature that it starts out “not fully started” (but you can send to it),
|
||||
* which is why `isStarted` features here; it is not improbable that cluster
|
||||
* actor refs will have the same behavior.
|
||||
*/
|
||||
private[akka] trait RepointableRef extends ActorRefScope {
|
||||
def isStarted: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal trait for assembling all the functionality needed internally on
|
||||
* ActorRefs. NOTE THAT THIS IS NOT A STABLE EXTERNAL INTERFACE!
|
||||
|
|
@ -210,6 +224,16 @@ private[akka] abstract class InternalActorRef extends ActorRef with ScalaActorRe
|
|||
def isLocal: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Common trait of all actor refs which actually have a Cell, most notably
|
||||
* LocalActorRef and RepointableActorRef. The former specializes the return
|
||||
* type of `underlying` so that follow-up calls can use invokevirtual instead
|
||||
* of invokeinterface.
|
||||
*/
|
||||
private[akka] abstract class ActorRefWithCell extends InternalActorRef { this: ActorRefScope ⇒
|
||||
def underlying: Cell
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an internal look-up failure token, not useful for anything else.
|
||||
*/
|
||||
|
|
@ -228,21 +252,21 @@ private[akka] class LocalActorRef private[akka] (
|
|||
_props: Props,
|
||||
_supervisor: InternalActorRef,
|
||||
override val path: ActorPath)
|
||||
extends InternalActorRef with LocalRef {
|
||||
extends ActorRefWithCell with LocalRef {
|
||||
|
||||
/*
|
||||
* actorCell.start() publishes actorCell & this to the dispatcher, which
|
||||
* means that messages may be processed theoretically before the constructor
|
||||
* ends. The JMM guarantees visibility for final fields only after the end
|
||||
* of the constructor, so publish the actorCell safely by making it a
|
||||
* @volatile var which is NOT TO BE WRITTEN TO. The alternative would be to
|
||||
* move start() outside of the constructor, which would basically require
|
||||
* us to use purely factory methods for creating LocalActorRefs.
|
||||
* Safe publication of this class’s fields is guaranteed by mailbox.setActor()
|
||||
* which is called indirectly from actorCell.start() (if you’re wondering why
|
||||
* this is at all important, remember that under the JMM final fields are only
|
||||
* frozen at the _end_ of the constructor, but we are publishing “this” before
|
||||
* that is reached).
|
||||
*/
|
||||
@volatile
|
||||
private var actorCell = newActorCell(_system, this, _props, _supervisor)
|
||||
private val actorCell: ActorCell = newActorCell(_system, this, _props, _supervisor)
|
||||
actorCell.start()
|
||||
|
||||
// ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS ⬅⬅⬅
|
||||
_supervisor.sendSystemMessage(akka.dispatch.Supervise(this))
|
||||
|
||||
protected def newActorCell(system: ActorSystemImpl, ref: InternalActorRef, props: Props, supervisor: InternalActorRef): ActorCell =
|
||||
new ActorCell(system, ref, props, supervisor)
|
||||
|
||||
|
|
@ -313,9 +337,9 @@ private[akka] class LocalActorRef private[akka] (
|
|||
|
||||
// ========= AKKA PROTECTED FUNCTIONS =========
|
||||
|
||||
protected[akka] def underlying: ActorCell = actorCell
|
||||
def underlying: ActorCell = actorCell
|
||||
|
||||
override def sendSystemMessage(message: SystemMessage): Unit = underlying.dispatcher.systemDispatch(underlying, message)
|
||||
override def sendSystemMessage(message: SystemMessage): Unit = actorCell.sendSystemMessage(message)
|
||||
|
||||
override def !(message: Any)(implicit sender: ActorRef = null): Unit = actorCell.tell(message, sender)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ trait ActorRefProvider {
|
|||
/**
|
||||
* Reference to the supervisor used for all top-level user actors.
|
||||
*/
|
||||
def guardian: InternalActorRef
|
||||
def guardian: LocalActorRef
|
||||
|
||||
/**
|
||||
* Reference to the supervisor used for all top-level system actors.
|
||||
*/
|
||||
def systemGuardian: InternalActorRef
|
||||
def systemGuardian: LocalActorRef
|
||||
|
||||
/**
|
||||
* Dead letter destination for this provider.
|
||||
|
|
@ -104,7 +104,8 @@ trait ActorRefProvider {
|
|||
path: ActorPath,
|
||||
systemService: Boolean,
|
||||
deploy: Option[Deploy],
|
||||
lookupDeploy: Boolean): InternalActorRef
|
||||
lookupDeploy: Boolean,
|
||||
async: Boolean): InternalActorRef
|
||||
|
||||
/**
|
||||
* Create actor reference for a specified local or remote path. If no such
|
||||
|
|
@ -481,11 +482,10 @@ class LocalActorRefProvider(
|
|||
}
|
||||
}
|
||||
|
||||
lazy val guardian: InternalActorRef =
|
||||
actorOf(system, guardianProps, rootGuardian, rootPath / "user", true, None, false)
|
||||
lazy val guardian: LocalActorRef = new LocalActorRef(system, guardianProps, rootGuardian, rootPath / "user")
|
||||
|
||||
lazy val systemGuardian: InternalActorRef =
|
||||
actorOf(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, rootPath / "system", true, None, false)
|
||||
lazy val systemGuardian: LocalActorRef =
|
||||
new LocalActorRef(system, guardianProps.withCreator(new SystemGuardian), rootGuardian, rootPath / "system")
|
||||
|
||||
lazy val tempContainer = new VirtualPathContainer(system.provider, tempNode, rootGuardian, log)
|
||||
|
||||
|
|
@ -539,22 +539,20 @@ class LocalActorRefProvider(
|
|||
}
|
||||
|
||||
def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, path: ActorPath,
|
||||
systemService: Boolean, deploy: Option[Deploy], lookupDeploy: Boolean): InternalActorRef = {
|
||||
systemService: Boolean, deploy: Option[Deploy], lookupDeploy: Boolean, async: Boolean): InternalActorRef = {
|
||||
props.routerConfig match {
|
||||
case NoRouter ⇒ new LocalActorRef(system, props, supervisor, path) // create a local actor
|
||||
case NoRouter ⇒
|
||||
if (async) new RepointableActorRef(system, props, supervisor, path).initialize()
|
||||
else new LocalActorRef(system, props, supervisor, path)
|
||||
case router ⇒
|
||||
val lookup = if (lookupDeploy) deployer.lookup(path) else None
|
||||
val fromProps = Iterator(props.deploy.copy(routerConfig = props.deploy.routerConfig withFallback router))
|
||||
val d = fromProps ++ deploy.iterator ++ lookup.iterator reduce ((a, b) ⇒ b withFallback a)
|
||||
new RoutedActorRef(system, props.withRouter(d.routerConfig), supervisor, path)
|
||||
val ref = new RoutedActorRef(system, props.withRouter(d.routerConfig), supervisor, path).initialize()
|
||||
if (async) ref else ref.activate()
|
||||
}
|
||||
}
|
||||
|
||||
def getExternalAddressFor(addr: Address): Option[Address] = if (addr == rootPath.address) Some(addr) else None
|
||||
}
|
||||
|
||||
private[akka] class GuardianCell(_system: ActorSystemImpl, _self: InternalActorRef, _props: Props, _parent: InternalActorRef)
|
||||
extends ActorCell(_system, _self, _props, _parent) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -422,6 +422,13 @@ abstract class ExtendedActorSystem extends ActorSystem {
|
|||
* creation.
|
||||
*/
|
||||
def dynamicAccess: DynamicAccess
|
||||
|
||||
/**
|
||||
* For debugging: traverse actor hierarchy and make string representation.
|
||||
* Careful, this may OOM on large actor systems, and it is only meant for
|
||||
* helping debugging in case something already went terminally wrong.
|
||||
*/
|
||||
private[akka] def printTree: String
|
||||
}
|
||||
|
||||
private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config, classLoader: ClassLoader) extends ExtendedActorSystem {
|
||||
|
|
@ -479,20 +486,11 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
|
|||
|
||||
protected def systemImpl: ActorSystemImpl = this
|
||||
|
||||
private[akka] def systemActorOf(props: Props, name: String): ActorRef = {
|
||||
implicit val timeout = settings.CreationTimeout
|
||||
Await.result((systemGuardian ? CreateChild(props, name)).mapTo[ActorRef], timeout.duration)
|
||||
}
|
||||
private[akka] def systemActorOf(props: Props, name: String): ActorRef = systemGuardian.underlying.attachChild(props, name)
|
||||
|
||||
def actorOf(props: Props, name: String): ActorRef = {
|
||||
implicit val timeout = settings.CreationTimeout
|
||||
Await.result((guardian ? CreateChild(props, name)).mapTo[ActorRef], timeout.duration)
|
||||
}
|
||||
def actorOf(props: Props, name: String): ActorRef = guardian.underlying.attachChild(props, name)
|
||||
|
||||
def actorOf(props: Props): ActorRef = {
|
||||
implicit val timeout = settings.CreationTimeout
|
||||
Await.result((guardian ? CreateRandomNameChild(props)).mapTo[ActorRef], timeout.duration)
|
||||
}
|
||||
def actorOf(props: Props): ActorRef = guardian.underlying.attachChild(props)
|
||||
|
||||
def stop(actor: ActorRef): Unit = {
|
||||
implicit val timeout = settings.CreationTimeout
|
||||
|
|
@ -539,10 +537,10 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
|
|||
def dequeue() = null
|
||||
def hasMessages = false
|
||||
def numberOfMessages = 0
|
||||
def cleanUp(owner: ActorContext, deadLetters: MessageQueue): Unit = ()
|
||||
def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = ()
|
||||
}
|
||||
//FIXME Why do we need this at all?
|
||||
val deadLetterMailbox: Mailbox = new Mailbox(null, deadLetterQueue) {
|
||||
val deadLetterMailbox: Mailbox = new Mailbox(deadLetterQueue) {
|
||||
becomeClosed()
|
||||
def systemEnqueue(receiver: ActorRef, handle: SystemMessage): Unit =
|
||||
deadLetters ! DeadLetter(handle, receiver, receiver)
|
||||
|
|
@ -557,8 +555,8 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
|
|||
|
||||
def terminationFuture: Future[Unit] = provider.terminationFuture
|
||||
def lookupRoot: InternalActorRef = provider.rootGuardian
|
||||
def guardian: InternalActorRef = provider.guardian
|
||||
def systemGuardian: InternalActorRef = provider.systemGuardian
|
||||
def guardian: LocalActorRef = provider.guardian
|
||||
def systemGuardian: LocalActorRef = provider.systemGuardian
|
||||
|
||||
def /(actorName: String): ActorPath = guardian.path / actorName
|
||||
def /(path: Iterable[String]): ActorPath = guardian.path / path
|
||||
|
|
@ -682,6 +680,31 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
|
|||
|
||||
override def toString: String = lookupRoot.path.root.address.toString
|
||||
|
||||
override def printTree: String = {
|
||||
def printNode(node: ActorRef, indent: String): String = {
|
||||
node match {
|
||||
case wc: ActorRefWithCell ⇒
|
||||
val cell = wc.underlying
|
||||
indent + "-> " + node.path.name + " " + Logging.simpleName(node) + " " +
|
||||
(cell match {
|
||||
case real: ActorCell ⇒ if (real.actor ne null) real.actor.getClass else "null"
|
||||
case _ ⇒ Logging.simpleName(cell)
|
||||
}) +
|
||||
" " + (cell.childrenRefs match {
|
||||
case ActorCell.TerminatingChildrenContainer(_, toDie, reason) ⇒
|
||||
"Terminating(" + reason + ")" +
|
||||
(toDie.toSeq.sorted mkString ("\n" + indent + " toDie: ", "\n" + indent + " ", ""))
|
||||
case x ⇒ Logging.simpleName(x)
|
||||
}) +
|
||||
(if (cell.childrenRefs.children.isEmpty) "" else "\n") +
|
||||
(cell.childrenRefs.children.toSeq.sorted map (printNode(_, indent + " |")) mkString ("\n"))
|
||||
case _ ⇒
|
||||
indent + node.path.name + " " + Logging.simpleName(node)
|
||||
}
|
||||
}
|
||||
printNode(actorFor("/"), "")
|
||||
}
|
||||
|
||||
final class TerminationCallbacks extends Runnable with Awaitable[Unit] {
|
||||
private val lock = new ReentrantGuard
|
||||
private var callbacks: List[Runnable] = _ //non-volatile since guarded by the lock
|
||||
|
|
|
|||
|
|
@ -9,11 +9,22 @@ import scala.collection.JavaConversions._
|
|||
import java.lang.{ Iterable ⇒ JIterable }
|
||||
import akka.util.Duration
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] sealed trait ChildStats
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] case object ChildNameReserved extends ChildStats
|
||||
|
||||
/**
|
||||
* ChildRestartStats is the statistics kept by every parent Actor for every child Actor
|
||||
* and is used for SupervisorStrategies to know how to deal with problems that occur for the children.
|
||||
*/
|
||||
case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L) {
|
||||
case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L)
|
||||
extends ChildStats {
|
||||
|
||||
//FIXME How about making ChildRestartStats immutable and then move these methods into the actual supervisor strategies?
|
||||
def requestRestartPermission(retriesWindow: (Option[Int], Option[Int])): Boolean =
|
||||
|
|
|
|||
214
akka-actor/src/main/scala/akka/actor/RepointableActorRef.scala
Normal file
214
akka-actor/src/main/scala/akka/actor/RepointableActorRef.scala
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.actor
|
||||
|
||||
import akka.util.Unsafe
|
||||
import scala.annotation.tailrec
|
||||
import akka.dispatch.SystemMessage
|
||||
import akka.dispatch.Mailbox
|
||||
import akka.dispatch.Terminate
|
||||
import akka.dispatch.Envelope
|
||||
import akka.dispatch.Supervise
|
||||
import akka.dispatch.Create
|
||||
import akka.dispatch.MessageDispatcher
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import akka.event.Logging.Warning
|
||||
import scala.collection.mutable.Queue
|
||||
|
||||
/**
|
||||
* This actor ref starts out with some dummy cell (by default just enqueuing
|
||||
* messages into vectors protected by ReentrantLock), it must be initialize()’d
|
||||
* before it can be sent to, and it will be activate()’d by its supervisor in
|
||||
* response to the Supervise() message, which will replace the contained Cell
|
||||
* with a fully functional one, transfer all messages from dummy to real queue
|
||||
* and swap out the cell ref.
|
||||
*/
|
||||
private[akka] class RepointableActorRef(
|
||||
val system: ActorSystemImpl,
|
||||
val props: Props,
|
||||
val supervisor: InternalActorRef,
|
||||
val path: ActorPath)
|
||||
extends ActorRefWithCell with RepointableRef {
|
||||
|
||||
import AbstractActorRef.cellOffset
|
||||
|
||||
@volatile private var _cellDoNotCallMeDirectly: Cell = _
|
||||
|
||||
def underlying: Cell = Unsafe.instance.getObjectVolatile(this, cellOffset).asInstanceOf[Cell]
|
||||
|
||||
@tailrec final def swapCell(next: Cell): Cell = {
|
||||
val old = underlying
|
||||
if (Unsafe.instance.compareAndSwapObject(this, cellOffset, old, next)) old else swapCell(next)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize: make a dummy cell which holds just a mailbox, then tell our
|
||||
* supervisor that we exist so that he can create the real Cell in
|
||||
* handleSupervise().
|
||||
*
|
||||
* Call twice on your own peril!
|
||||
*
|
||||
* This is protected so that others can have different initialization.
|
||||
*/
|
||||
def initialize(): this.type = {
|
||||
swapCell(new UnstartedCell(system, this, props, supervisor))
|
||||
supervisor.sendSystemMessage(Supervise(this))
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is supposed to be called by the supervisor in handleSupervise()
|
||||
* to replace the UnstartedCell with the real one. It assumes no concurrent
|
||||
* modification of the `underlying` field, though it is safe to send messages
|
||||
* at any time.
|
||||
*/
|
||||
def activate(): this.type = {
|
||||
underlying match {
|
||||
case u: UnstartedCell ⇒ u.replaceWith(newCell())
|
||||
case _ ⇒ // this happens routinely for things which were created async=false
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by activate() to obtain the cell which is to replace the
|
||||
* unstarted cell. The cell must be fully functional.
|
||||
*/
|
||||
def newCell(): Cell = new ActorCell(system, this, props, supervisor).start()
|
||||
|
||||
def suspend(): Unit = underlying.suspend()
|
||||
|
||||
def resume(): Unit = underlying.resume()
|
||||
|
||||
def stop(): Unit = underlying.stop()
|
||||
|
||||
def restart(cause: Throwable): Unit = underlying.restart(cause)
|
||||
|
||||
def isStarted: Boolean = !underlying.isInstanceOf[UnstartedCell]
|
||||
|
||||
def isTerminated: Boolean = underlying.isTerminated
|
||||
|
||||
def provider: ActorRefProvider = system.provider
|
||||
|
||||
def isLocal: Boolean = underlying.isLocal
|
||||
|
||||
def getParent: InternalActorRef = underlying.parent
|
||||
|
||||
def getChild(name: Iterator[String]): InternalActorRef =
|
||||
if (name.hasNext) {
|
||||
name.next match {
|
||||
case ".." ⇒ getParent.getChild(name)
|
||||
case "" ⇒ getChild(name)
|
||||
case other ⇒
|
||||
underlying.childrenRefs.getByName(other) match {
|
||||
case Some(crs) ⇒ crs.child.asInstanceOf[InternalActorRef].getChild(name)
|
||||
case None ⇒ Nobody
|
||||
}
|
||||
}
|
||||
} else this
|
||||
|
||||
def !(message: Any)(implicit sender: ActorRef = null) = underlying.tell(message, sender)
|
||||
|
||||
def sendSystemMessage(message: SystemMessage) = underlying.sendSystemMessage(message)
|
||||
|
||||
@throws(classOf[java.io.ObjectStreamException])
|
||||
protected def writeReplace(): AnyRef = SerializedActorRef(path)
|
||||
}
|
||||
|
||||
private[akka] class UnstartedCell(val systemImpl: ActorSystemImpl, val self: RepointableActorRef, val props: Props, val supervisor: InternalActorRef)
|
||||
extends Cell {
|
||||
|
||||
/*
|
||||
* This lock protects all accesses to this cell’s queues. It also ensures
|
||||
* safe switching to the started ActorCell.
|
||||
*/
|
||||
val lock = new ReentrantLock
|
||||
|
||||
// use Envelope to keep on-send checks in the same place
|
||||
val queue: Queue[Envelope] = Queue()
|
||||
val systemQueue: Queue[SystemMessage] = Queue()
|
||||
|
||||
def replaceWith(cell: Cell): Unit = {
|
||||
lock.lock()
|
||||
try {
|
||||
/*
|
||||
* The CallingThreadDispatcher nicely dives under the ReentrantLock and
|
||||
* breaks things by enqueueing into stale queues from within the message
|
||||
* processing which happens in-line for sendSystemMessage() and tell().
|
||||
* Since this is the only possible way to f*ck things up within this
|
||||
* lock, double-tap (well, N-tap, really); concurrent modification is
|
||||
* still not possible because we’re the only thread accessing the queues.
|
||||
*/
|
||||
var interrupted = false
|
||||
while (systemQueue.nonEmpty || queue.nonEmpty) {
|
||||
while (systemQueue.nonEmpty) {
|
||||
val msg = systemQueue.dequeue()
|
||||
try cell.sendSystemMessage(msg)
|
||||
catch {
|
||||
case _: InterruptedException ⇒ interrupted = true
|
||||
}
|
||||
}
|
||||
if (queue.nonEmpty) {
|
||||
val envelope = queue.dequeue()
|
||||
try cell.tell(envelope.message, envelope.sender)
|
||||
catch {
|
||||
case _: InterruptedException ⇒ interrupted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (interrupted) throw new InterruptedException
|
||||
} finally try
|
||||
self.swapCell(cell)
|
||||
finally
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
def system: ActorSystem = systemImpl
|
||||
def suspend(): Unit = {}
|
||||
def resume(): Unit = {}
|
||||
def restart(cause: Throwable): Unit = {}
|
||||
def stop(): Unit = sendSystemMessage(Terminate())
|
||||
def isTerminated: Boolean = false
|
||||
def parent: InternalActorRef = supervisor
|
||||
def childrenRefs: ActorCell.ChildrenContainer = ActorCell.EmptyChildrenContainer
|
||||
def tell(message: Any, sender: ActorRef): Unit = {
|
||||
lock.lock()
|
||||
try {
|
||||
if (self.underlying eq this) queue enqueue Envelope(message, sender, system)
|
||||
else self.underlying.tell(message, sender)
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
def sendSystemMessage(msg: SystemMessage): Unit = {
|
||||
lock.lock()
|
||||
try {
|
||||
if (self.underlying eq this) systemQueue enqueue msg
|
||||
else self.underlying.sendSystemMessage(msg)
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
def isLocal = true
|
||||
def hasMessages: Boolean = {
|
||||
lock.lock()
|
||||
try {
|
||||
if (self.underlying eq this) !queue.isEmpty
|
||||
else self.underlying.hasMessages
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
def numberOfMessages: Int = {
|
||||
lock.lock()
|
||||
try {
|
||||
if (self.underlying eq this) queue.size
|
||||
else self.underlying.numberOfMessages
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -592,7 +592,7 @@ case class TypedProps[T <: AnyRef] protected[TypedProps] (
|
|||
/**
|
||||
* Returns the akka.actor.Props representation of this TypedProps
|
||||
*/
|
||||
def actorProps(): Props = if (dispatcher == Props().dispatcher) Props() else Props(dispatcher = dispatcher)
|
||||
def actorProps(): Props = if (dispatcher == Props.default.dispatcher) Props.default else Props(dispatcher = dispatcher)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ import akka.event.Logging.LogEventException
|
|||
import akka.jsr166y.{ ForkJoinTask, ForkJoinPool }
|
||||
import akka.util.{ Unsafe, Duration, NonFatal, Index }
|
||||
|
||||
final case class Envelope(val message: Any, val sender: ActorRef)(system: ActorSystem) {
|
||||
if (message.isInstanceOf[AnyRef]) {
|
||||
final case class Envelope private (val message: Any, val sender: ActorRef)
|
||||
|
||||
object Envelope {
|
||||
def apply(message: Any, sender: ActorRef, system: ActorSystem): Envelope = {
|
||||
val msg = message.asInstanceOf[AnyRef]
|
||||
if (msg eq null) throw new InvalidMessageException("Message is null")
|
||||
if (system.settings.SerializeAllMessages && !msg.isInstanceOf[NoSerializationVerificationNeeded]) {
|
||||
|
|
@ -30,6 +32,7 @@ final case class Envelope(val message: Any, val sender: ActorRef)(system: ActorS
|
|||
}
|
||||
}
|
||||
}
|
||||
new Envelope(message, sender)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -228,8 +231,8 @@ private[akka] object MessageDispatcher {
|
|||
} {
|
||||
val status = if (a.isTerminated) " (terminated)" else " (alive)"
|
||||
val messages = a match {
|
||||
case l: LocalActorRef ⇒ " " + l.underlying.mailbox.numberOfMessages + " messages"
|
||||
case _ ⇒ " " + a.getClass
|
||||
case r: ActorRefWithCell ⇒ " " + r.underlying.numberOfMessages + " messages"
|
||||
case _ ⇒ " " + a.getClass
|
||||
}
|
||||
val parent = a match {
|
||||
case i: InternalActorRef ⇒ ", parent: " + i.getParent
|
||||
|
|
@ -265,7 +268,7 @@ abstract class MessageDispatcher(val prerequisites: DispatcherPrerequisites) ext
|
|||
/**
|
||||
* Creates and returns a mailbox for the given actor.
|
||||
*/
|
||||
protected[akka] def createMailbox(actor: ActorCell): Mailbox //FIXME should this really be private[akka]?
|
||||
protected[akka] def createMailbox(actor: Cell): Mailbox //FIXME should this really be private[akka]?
|
||||
|
||||
/**
|
||||
* Identifier of this dispatcher, corresponds to the full key
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import annotation.tailrec
|
|||
import akka.util.{ Duration, Helpers }
|
||||
import java.util.{ Comparator, Iterator }
|
||||
import java.util.concurrent.{ Executor, LinkedBlockingQueue, ConcurrentLinkedQueue, ConcurrentSkipListSet }
|
||||
import akka.actor.ActorSystemImpl
|
||||
|
||||
/**
|
||||
* An executor based event driven dispatcher which will try to redistribute work from busy actors to idle actors. It is assumed
|
||||
|
|
@ -46,24 +47,25 @@ class BalancingDispatcher(
|
|||
/**
|
||||
* INTERNAL USE ONLY
|
||||
*/
|
||||
private[akka] val messageQueue: MessageQueue = mailboxType.create(None)
|
||||
private[akka] val messageQueue: MessageQueue = mailboxType.create(None, None)
|
||||
|
||||
private class SharingMailbox(_actor: ActorCell, _messageQueue: MessageQueue) extends Mailbox(_actor, _messageQueue) with DefaultSystemMessageQueue {
|
||||
private class SharingMailbox(val system: ActorSystemImpl, _messageQueue: MessageQueue)
|
||||
extends Mailbox(_messageQueue) with DefaultSystemMessageQueue {
|
||||
override def cleanUp(): Unit = {
|
||||
val dlq = actor.systemImpl.deadLetterMailbox
|
||||
val dlq = system.deadLetterMailbox
|
||||
//Don't call the original implementation of this since it scraps all messages, and we don't want to do that
|
||||
var message = systemDrain(NoMessage)
|
||||
while (message ne null) {
|
||||
// message must be “virgin” before being able to systemEnqueue again
|
||||
val next = message.next
|
||||
message.next = null
|
||||
dlq.systemEnqueue(actor.self, message)
|
||||
dlq.systemEnqueue(system.deadLetters, message)
|
||||
message = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected[akka] override def createMailbox(actor: ActorCell): Mailbox = new SharingMailbox(actor, messageQueue)
|
||||
protected[akka] override def createMailbox(actor: akka.actor.Cell): Mailbox = new SharingMailbox(actor.systemImpl, messageQueue)
|
||||
|
||||
protected[akka] override def register(actor: ActorCell): Unit = {
|
||||
super.register(actor)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,8 @@ class Dispatcher(
|
|||
/**
|
||||
* INTERNAL USE ONLY
|
||||
*/
|
||||
protected[akka] def createMailbox(actor: ActorCell): Mailbox = new Mailbox(actor, mailboxType.create(Some(actor))) with DefaultSystemMessageQueue
|
||||
protected[akka] def createMailbox(actor: akka.actor.Cell): Mailbox =
|
||||
new Mailbox(mailboxType.create(Some(actor.self), Some(actor.system))) with DefaultSystemMessageQueue
|
||||
|
||||
/**
|
||||
* INTERNAL USE ONLY
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package akka.dispatch
|
|||
import akka.AkkaException
|
||||
import java.util.{ Comparator, PriorityQueue, Queue, Deque }
|
||||
import akka.util._
|
||||
import akka.actor.{ ActorCell, ActorRef, Cell }
|
||||
import java.util.concurrent._
|
||||
import annotation.tailrec
|
||||
import akka.event.Logging.Error
|
||||
|
|
@ -41,11 +42,32 @@ private[akka] object Mailbox {
|
|||
*
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] abstract class Mailbox(val actor: ActorCell, val messageQueue: MessageQueue)
|
||||
private[akka] abstract class Mailbox(val messageQueue: MessageQueue)
|
||||
extends SystemMessageQueue with Runnable {
|
||||
|
||||
import Mailbox._
|
||||
|
||||
/*
|
||||
* This is needed for actually executing the mailbox, i.e. invoking the
|
||||
* ActorCell. There are situations (e.g. RepointableActorRef) where a Mailbox
|
||||
* is constructed but we know that we will not execute it, in which case this
|
||||
* will be null. It must be a var to support switching into an “active”
|
||||
* mailbox, should the owning ActorRef turn local.
|
||||
*
|
||||
* ANOTHER THING, IMPORTANT:
|
||||
*
|
||||
* actorCell.start() publishes actorCell & self to the dispatcher, which
|
||||
* means that messages may be processed theoretically before self’s constructor
|
||||
* ends. The JMM guarantees visibility for final fields only after the end
|
||||
* of the constructor, so safe publication requires that THIS WRITE BELOW
|
||||
* stay as it is.
|
||||
*/
|
||||
@volatile
|
||||
var actor: ActorCell = _
|
||||
def setActor(cell: ActorCell): Unit = actor = cell
|
||||
|
||||
def dispatcher: MessageDispatcher = actor.dispatcher
|
||||
|
||||
/**
|
||||
* Try to enqueue the message to this queue, or throw an exception.
|
||||
*/
|
||||
|
|
@ -230,11 +252,12 @@ private[akka] abstract class Mailbox(val actor: ActorCell, val messageQueue: Mes
|
|||
* if we closed the mailbox, we must dump the remaining system messages
|
||||
* to deadLetters (this is essential for DeathWatch)
|
||||
*/
|
||||
val dlm = actor.systemImpl.deadLetterMailbox
|
||||
while (nextMessage ne null) {
|
||||
val msg = nextMessage
|
||||
nextMessage = nextMessage.next
|
||||
msg.next = null
|
||||
try actor.systemImpl.deadLetterMailbox.systemEnqueue(actor.self, msg)
|
||||
try dlm.systemEnqueue(actor.self, msg)
|
||||
catch {
|
||||
case NonFatal(e) ⇒ actor.system.eventStream.publish(
|
||||
Error(e, actor.self.path.toString, this.getClass, "error while enqueuing " + msg + " to deadLetters: " + e.getMessage))
|
||||
|
|
@ -244,9 +267,6 @@ private[akka] abstract class Mailbox(val actor: ActorCell, val messageQueue: Mes
|
|||
if (failure ne null) actor.handleInvokeFailure(failure, failure.getMessage)
|
||||
}
|
||||
|
||||
@inline
|
||||
final def dispatcher: MessageDispatcher = actor.dispatcher
|
||||
|
||||
/**
|
||||
* Overridable callback to clean up the mailbox,
|
||||
* called when an actor is unregistered.
|
||||
|
|
@ -265,7 +285,7 @@ private[akka] abstract class Mailbox(val actor: ActorCell, val messageQueue: Mes
|
|||
}
|
||||
|
||||
if (messageQueue ne null) // needed for CallingThreadDispatcher, which never calls Mailbox.run()
|
||||
messageQueue.cleanUp(actor, actor.systemImpl.deadLetterQueue)
|
||||
messageQueue.cleanUp(actor.self, actor.systemImpl.deadLetterQueue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +323,7 @@ trait MessageQueue {
|
|||
* which is passed in. The owner of this MessageQueue is passed in if
|
||||
* available (e.g. for creating DeadLetters()), “/deadletters” otherwise.
|
||||
*/
|
||||
def cleanUp(owner: ActorContext, deadLetters: MessageQueue): Unit
|
||||
def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -331,10 +351,11 @@ private[akka] trait DefaultSystemMessageQueue { self: Mailbox ⇒
|
|||
@tailrec
|
||||
final def systemEnqueue(receiver: ActorRef, message: SystemMessage): Unit = {
|
||||
assert(message.next eq null)
|
||||
if (Mailbox.debug) println(actor.self + " having enqueued " + message)
|
||||
if (Mailbox.debug) println(receiver + " having enqueued " + message)
|
||||
val head = systemQueueGet
|
||||
if (head == NoMessage) actor.system.deadLetterMailbox.systemEnqueue(receiver, message)
|
||||
else {
|
||||
if (head == NoMessage) {
|
||||
if (actor ne null) actor.systemImpl.deadLetterMailbox.systemEnqueue(receiver, message)
|
||||
} else {
|
||||
/*
|
||||
* this write is safely published by the compareAndSet contained within
|
||||
* systemQueuePut; “Intra-Thread Semantics” on page 12 of the JSR133 spec
|
||||
|
|
@ -366,11 +387,11 @@ trait QueueBasedMessageQueue extends MessageQueue {
|
|||
def queue: Queue[Envelope]
|
||||
def numberOfMessages = queue.size
|
||||
def hasMessages = !queue.isEmpty
|
||||
def cleanUp(owner: ActorContext, deadLetters: MessageQueue): Unit = {
|
||||
def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = {
|
||||
if (hasMessages) {
|
||||
var envelope = dequeue
|
||||
while (envelope ne null) {
|
||||
deadLetters.enqueue(owner.self, envelope)
|
||||
deadLetters.enqueue(owner, envelope)
|
||||
envelope = dequeue
|
||||
}
|
||||
}
|
||||
|
|
@ -445,10 +466,20 @@ trait BoundedDequeBasedMessageQueueSemantics extends DequeBasedMessageQueue {
|
|||
}
|
||||
|
||||
/**
|
||||
* MailboxType is a factory to create MessageQueues for an optionally provided ActorContext
|
||||
* MailboxType is a factory to create MessageQueues for an optionally
|
||||
* provided ActorContext.
|
||||
*
|
||||
* <b>Possibly Important Notice</b>
|
||||
*
|
||||
* When implementing a custom mailbox type, be aware that there is special
|
||||
* semantics attached to `system.actorOf()` in that sending to the returned
|
||||
* ActorRef may—for a short period of time—enqueue the messages first in a
|
||||
* dummy queue. Top-level actors are created in two steps, and only after the
|
||||
* guardian actor has performed that second step will all previously sent
|
||||
* messages be transferred from the dummy queue into the real mailbox.
|
||||
*/
|
||||
trait MailboxType {
|
||||
def create(owner: Option[ActorContext]): MessageQueue
|
||||
def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -458,7 +489,7 @@ case class UnboundedMailbox() extends MailboxType {
|
|||
|
||||
def this(settings: ActorSystem.Settings, config: Config) = this()
|
||||
|
||||
final override def create(owner: Option[ActorContext]): MessageQueue =
|
||||
final override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue =
|
||||
new ConcurrentLinkedQueue[Envelope]() with QueueBasedMessageQueue with UnboundedMessageQueueSemantics {
|
||||
final def queue: Queue[Envelope] = this
|
||||
}
|
||||
|
|
@ -475,7 +506,7 @@ case class BoundedMailbox( final val capacity: Int, final val pushTimeOut: Durat
|
|||
if (capacity < 0) throw new IllegalArgumentException("The capacity for BoundedMailbox can not be negative")
|
||||
if (pushTimeOut eq null) throw new IllegalArgumentException("The push time-out for BoundedMailbox can not be null")
|
||||
|
||||
final override def create(owner: Option[ActorContext]): MessageQueue =
|
||||
final override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue =
|
||||
new LinkedBlockingQueue[Envelope](capacity) with QueueBasedMessageQueue with BoundedMessageQueueSemantics {
|
||||
final def queue: BlockingQueue[Envelope] = this
|
||||
final val pushTimeOut = BoundedMailbox.this.pushTimeOut
|
||||
|
|
@ -488,7 +519,7 @@ case class BoundedMailbox( final val capacity: Int, final val pushTimeOut: Durat
|
|||
*/
|
||||
class UnboundedPriorityMailbox( final val cmp: Comparator[Envelope], final val initialCapacity: Int) extends MailboxType {
|
||||
def this(cmp: Comparator[Envelope]) = this(cmp, 11)
|
||||
final override def create(owner: Option[ActorContext]): MessageQueue =
|
||||
final override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue =
|
||||
new PriorityBlockingQueue[Envelope](initialCapacity, cmp) with QueueBasedMessageQueue with UnboundedMessageQueueSemantics {
|
||||
final def queue: Queue[Envelope] = this
|
||||
}
|
||||
|
|
@ -503,7 +534,7 @@ class BoundedPriorityMailbox( final val cmp: Comparator[Envelope], final val cap
|
|||
if (capacity < 0) throw new IllegalArgumentException("The capacity for BoundedMailbox can not be negative")
|
||||
if (pushTimeOut eq null) throw new IllegalArgumentException("The push time-out for BoundedMailbox can not be null")
|
||||
|
||||
final override def create(owner: Option[ActorContext]): MessageQueue =
|
||||
final override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue =
|
||||
new BoundedBlockingQueue[Envelope](capacity, new PriorityQueue[Envelope](11, cmp)) with QueueBasedMessageQueue with BoundedMessageQueueSemantics {
|
||||
final def queue: BlockingQueue[Envelope] = this
|
||||
final val pushTimeOut = BoundedPriorityMailbox.this.pushTimeOut
|
||||
|
|
@ -517,7 +548,7 @@ case class UnboundedDequeBasedMailbox() extends MailboxType {
|
|||
|
||||
def this(settings: ActorSystem.Settings, config: Config) = this()
|
||||
|
||||
final override def create(owner: Option[ActorContext]): MessageQueue =
|
||||
final override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue =
|
||||
new LinkedBlockingDeque[Envelope]() with DequeBasedMessageQueue with UnboundedDequeBasedMessageQueueSemantics {
|
||||
final val queue = this
|
||||
}
|
||||
|
|
@ -534,7 +565,7 @@ case class BoundedDequeBasedMailbox( final val capacity: Int, final val pushTime
|
|||
if (capacity < 0) throw new IllegalArgumentException("The capacity for BoundedDequeBasedMailbox can not be negative")
|
||||
if (pushTimeOut eq null) throw new IllegalArgumentException("The push time-out for BoundedDequeBasedMailbox can not be null")
|
||||
|
||||
final override def create(owner: Option[ActorContext]): MessageQueue =
|
||||
final override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue =
|
||||
new LinkedBlockingDeque[Envelope](capacity) with DequeBasedMessageQueue with BoundedDequeBasedMessageQueueSemantics {
|
||||
final val queue = this
|
||||
final val pushTimeOut = BoundedDequeBasedMailbox.this.pushTimeOut
|
||||
|
|
|
|||
|
|
@ -324,7 +324,17 @@ trait ActorClassification { this: ActorEventBus with ActorClassifier ⇒
|
|||
case some ⇒ some 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)
|
||||
def subscribe(subscriber: Subscriber, to: Classifier): Boolean =
|
||||
if (subscriber eq null) throw new IllegalArgumentException("Subscriber is null")
|
||||
else if (to eq null) throw new IllegalArgumentException("Classifier is null")
|
||||
else associate(to, subscriber)
|
||||
|
||||
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean =
|
||||
if (subscriber eq null) throw new IllegalArgumentException("Subscriber is null")
|
||||
else if (from eq null) throw new IllegalArgumentException("Classifier is null")
|
||||
else dissociate(from, subscriber)
|
||||
|
||||
def unsubscribe(subscriber: Subscriber): Unit =
|
||||
if (subscriber eq null) throw new IllegalArgumentException("Subscriber is null")
|
||||
else dissociate(subscriber)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,17 +39,20 @@ class EventStream(private val debug: Boolean = false) extends LoggingBus with Su
|
|||
}
|
||||
|
||||
override def subscribe(subscriber: ActorRef, channel: Class[_]): Boolean = {
|
||||
if (subscriber eq null) throw new IllegalArgumentException("subscriber is null")
|
||||
if (debug) publish(Logging.Debug(simpleName(this), this.getClass, "subscribing " + subscriber + " to channel " + channel))
|
||||
super.subscribe(subscriber, channel)
|
||||
}
|
||||
|
||||
override def unsubscribe(subscriber: ActorRef, channel: Class[_]): Boolean = {
|
||||
if (subscriber eq null) throw new IllegalArgumentException("subscriber is null")
|
||||
val ret = super.unsubscribe(subscriber, channel)
|
||||
if (debug) publish(Logging.Debug(simpleName(this), this.getClass, "unsubscribing " + subscriber + " from channel " + channel))
|
||||
ret
|
||||
}
|
||||
|
||||
override def unsubscribe(subscriber: ActorRef) {
|
||||
if (subscriber eq null) throw new IllegalArgumentException("subscriber is null")
|
||||
super.unsubscribe(subscriber)
|
||||
if (debug) publish(Logging.Debug(simpleName(this), this.getClass, "unsubscribing " + subscriber + " from all channels"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,42 +23,28 @@ import scala.runtime.ScalaRunTime
|
|||
* send a message to on (or more) of these actors.
|
||||
*/
|
||||
private[akka] class RoutedActorRef(_system: ActorSystemImpl, _props: Props, _supervisor: InternalActorRef, _path: ActorPath)
|
||||
extends LocalActorRef(
|
||||
_system,
|
||||
_props.copy(creator = () ⇒ _props.routerConfig.createActor(), dispatcher = _props.routerConfig.routerDispatcher),
|
||||
_supervisor,
|
||||
_path) {
|
||||
extends RepointableActorRef(_system, _props, _supervisor, _path) {
|
||||
|
||||
/*
|
||||
* CAUTION: RoutedActorRef is PROBLEMATIC
|
||||
* ======================================
|
||||
*
|
||||
* We are constructing/assembling the children outside of the scope of the
|
||||
* Router actor, inserting them in its childrenRef list, which is not at all
|
||||
* synchronized. This is done exactly once at start-up, all other accesses
|
||||
* are done from the Router actor. This means that the only thing which is
|
||||
* really hairy is making sure that the Router does not touch its childrenRefs
|
||||
* before we are done with them: lock the monitor of the actor cell (hence the
|
||||
* override of newActorCell) and use that to block the Router constructor for
|
||||
* as long as it takes to setup the RoutedActorRef itself.
|
||||
*
|
||||
* ===> I M P O R T A N T N O T I C E <===
|
||||
*
|
||||
* DO NOT THROW ANY EXCEPTIONS BEFORE THE FOLLOWING TRY-BLOCK WITHOUT
|
||||
* EXITING THE MONITOR OF THE actorCell!
|
||||
*
|
||||
* This is important, just don’t do it! No kidding.
|
||||
*/
|
||||
override def newActorCell(
|
||||
system: ActorSystemImpl,
|
||||
ref: InternalActorRef,
|
||||
props: Props,
|
||||
supervisor: InternalActorRef): ActorCell = {
|
||||
val cell = super.newActorCell(system, ref, props, supervisor)
|
||||
Unsafe.instance.monitorEnter(cell)
|
||||
cell
|
||||
// verify that a BalancingDispatcher is not used with a Router
|
||||
if (_props.routerConfig != NoRouter && _system.dispatchers.isBalancingDispatcher(_props.routerConfig.routerDispatcher)) {
|
||||
throw new ConfigurationException(
|
||||
"Configuration for " + this +
|
||||
" is invalid - you can not use a 'BalancingDispatcher' as a Router's dispatcher, you can however use it for the routees.")
|
||||
}
|
||||
|
||||
_props.routerConfig.verifyConfig()
|
||||
|
||||
override def newCell(): Cell = new RoutedActorCell(system, this, props, supervisor)
|
||||
|
||||
}
|
||||
|
||||
private[akka] class RoutedActorCell(_system: ActorSystemImpl, _ref: InternalActorRef, _props: Props, _supervisor: InternalActorRef)
|
||||
extends ActorCell(
|
||||
_system,
|
||||
_ref,
|
||||
_props.copy(creator = () ⇒ _props.routerConfig.createActor(), dispatcher = _props.routerConfig.routerDispatcher),
|
||||
_supervisor) {
|
||||
|
||||
private[akka] val routerConfig = _props.routerConfig
|
||||
private[akka] val routeeProps = _props.copy(routerConfig = NoRouter)
|
||||
private[akka] val resizeInProgress = new AtomicBoolean
|
||||
|
|
@ -72,39 +58,28 @@ private[akka] class RoutedActorRef(_system: ActorSystemImpl, _props: Props, _sup
|
|||
private var _routeeProvider: RouteeProvider = _
|
||||
def routeeProvider = _routeeProvider
|
||||
|
||||
val route =
|
||||
try {
|
||||
// verify that a BalancingDispatcher is not used with a Router
|
||||
if (_props.routerConfig != NoRouter && _system.dispatchers.isBalancingDispatcher(_props.routerConfig.routerDispatcher)) {
|
||||
actorContext.stop(actorContext.self)
|
||||
throw new ConfigurationException(
|
||||
"Configuration for actor [" + _path.toString +
|
||||
"] is invalid - you can not use a 'BalancingDispatcher' as a Router's dispatcher, you can however use it for the routees.")
|
||||
}
|
||||
|
||||
_routeeProvider = routerConfig.createRouteeProvider(actorContext)
|
||||
val r = routerConfig.createRoute(routeeProps, routeeProvider)
|
||||
// initial resize, before message send
|
||||
routerConfig.resizer foreach { r ⇒
|
||||
if (r.isTimeForResize(resizeCounter.getAndIncrement()))
|
||||
r.resize(routeeProps, routeeProvider)
|
||||
}
|
||||
r
|
||||
} finally {
|
||||
assert(Thread.holdsLock(actorContext))
|
||||
Unsafe.instance.monitorExit(actorContext) // unblock Router’s constructor
|
||||
val route = {
|
||||
_routeeProvider = routerConfig.createRouteeProvider(this)
|
||||
val r = routerConfig.createRoute(routeeProps, routeeProvider)
|
||||
// initial resize, before message send
|
||||
routerConfig.resizer foreach { r ⇒
|
||||
if (r.isTimeForResize(resizeCounter.getAndIncrement()))
|
||||
r.resize(routeeProps, routeeProvider)
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
if (routerConfig.resizer.isEmpty && _routees.isEmpty)
|
||||
throw new ActorInitializationException("router " + routerConfig + " did not register routees!")
|
||||
|
||||
start()
|
||||
|
||||
/*
|
||||
* end of construction
|
||||
*/
|
||||
|
||||
def applyRoute(sender: ActorRef, message: Any): Iterable[Destination] = message match {
|
||||
case _: AutoReceivedMessage ⇒ Destination(this, this) :: Nil
|
||||
case Terminated(_) ⇒ Destination(this, this) :: Nil
|
||||
case _: AutoReceivedMessage ⇒ Destination(self, self) :: Nil
|
||||
case CurrentRoutees ⇒
|
||||
sender ! RouterRoutees(_routees)
|
||||
Nil
|
||||
|
|
@ -122,7 +97,7 @@ private[akka] class RoutedActorRef(_system: ActorSystemImpl, _props: Props, _sup
|
|||
private[akka] def addRoutees(newRoutees: IndexedSeq[ActorRef]): Unit = {
|
||||
_routees = _routees ++ newRoutees
|
||||
// subscribe to Terminated messages for all route destinations, to be handled by Router actor
|
||||
newRoutees foreach underlying.watch
|
||||
newRoutees foreach watch
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -133,13 +108,13 @@ private[akka] class RoutedActorRef(_system: ActorSystemImpl, _props: Props, _sup
|
|||
*/
|
||||
private[akka] def removeRoutees(abandonedRoutees: IndexedSeq[ActorRef]): Unit = {
|
||||
_routees = _routees diff abandonedRoutees
|
||||
abandonedRoutees foreach underlying.unwatch
|
||||
abandonedRoutees foreach unwatch
|
||||
}
|
||||
|
||||
override def !(message: Any)(implicit sender: ActorRef = null): Unit = {
|
||||
override def tell(message: Any, sender: ActorRef): Unit = {
|
||||
resize()
|
||||
|
||||
val s = if (sender eq null) underlying.system.deadLetters else sender
|
||||
val s = if (sender eq null) system.deadLetters else sender
|
||||
|
||||
val msg = message match {
|
||||
case Broadcast(m) ⇒ m
|
||||
|
|
@ -147,15 +122,18 @@ private[akka] class RoutedActorRef(_system: ActorSystemImpl, _props: Props, _sup
|
|||
}
|
||||
|
||||
applyRoute(s, message) match {
|
||||
case Destination(_, x) :: Nil if x eq this ⇒ super.!(message)(s)
|
||||
case refs ⇒ refs foreach (p ⇒ p.recipient.!(msg)(p.sender))
|
||||
case Destination(_, x) :: Nil if x == self ⇒ super.tell(message, s)
|
||||
case refs ⇒
|
||||
refs foreach (p ⇒
|
||||
if (p.recipient == self) super.tell(msg, p.sender)
|
||||
else p.recipient.!(msg)(p.sender))
|
||||
}
|
||||
}
|
||||
|
||||
def resize(): Unit = {
|
||||
for (r ← routerConfig.resizer) {
|
||||
if (r.isTimeForResize(resizeCounter.getAndIncrement()) && resizeInProgress.compareAndSet(false, true))
|
||||
super.!(Router.Resize)
|
||||
super.tell(Router.Resize, self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -212,6 +190,11 @@ trait RouterConfig {
|
|||
*/
|
||||
def resizer: Option[Resizer] = None
|
||||
|
||||
/**
|
||||
* Check that everything is there which is needed. Called in constructor of RoutedActorRef to fail early.
|
||||
*/
|
||||
def verifyConfig(): Unit = {}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -227,7 +210,7 @@ class RouteeProvider(val context: ActorContext, val resizer: Option[Resizer]) {
|
|||
* Not thread safe, but intended to be called from protected points, such as
|
||||
* `RouterConfig.createRoute` and `Resizer.resize`.
|
||||
*/
|
||||
def registerRoutees(routees: IndexedSeq[ActorRef]): Unit = routedRef.addRoutees(routees)
|
||||
def registerRoutees(routees: IndexedSeq[ActorRef]): Unit = routedCell.addRoutees(routees)
|
||||
|
||||
/**
|
||||
* Adds the routees to the router.
|
||||
|
|
@ -247,7 +230,7 @@ class RouteeProvider(val context: ActorContext, val resizer: Option[Resizer]) {
|
|||
* Not thread safe, but intended to be called from protected points, such as
|
||||
* `Resizer.resize`.
|
||||
*/
|
||||
def unregisterRoutees(routees: IndexedSeq[ActorRef]): Unit = routedRef.removeRoutees(routees)
|
||||
def unregisterRoutees(routees: IndexedSeq[ActorRef]): Unit = routedCell.removeRoutees(routees)
|
||||
|
||||
def createRoutees(props: Props, nrOfInstances: Int, routees: Iterable[String]): IndexedSeq[ActorRef] =
|
||||
(nrOfInstances, routees) match {
|
||||
|
|
@ -264,9 +247,9 @@ class RouteeProvider(val context: ActorContext, val resizer: Option[Resizer]) {
|
|||
/**
|
||||
* All routees of the router
|
||||
*/
|
||||
def routees: IndexedSeq[ActorRef] = routedRef.routees
|
||||
def routees: IndexedSeq[ActorRef] = routedCell.routees
|
||||
|
||||
private def routedRef = context.self.asInstanceOf[RoutedActorRef]
|
||||
private def routedCell = context.asInstanceOf[RoutedActorCell]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -298,12 +281,9 @@ trait CustomRoute {
|
|||
*/
|
||||
trait Router extends Actor {
|
||||
|
||||
// make sure that we synchronize properly to get the childrenRefs into our CPU cache
|
||||
val ref = context.synchronized {
|
||||
self match {
|
||||
case x: RoutedActorRef ⇒ x
|
||||
case _ ⇒ throw new ActorInitializationException("Router actor can only be used in RoutedActorRef")
|
||||
}
|
||||
val ref = context match {
|
||||
case x: RoutedActorCell ⇒ x
|
||||
case _ ⇒ throw new ActorInitializationException("Router actor can only be used in RoutedActorRef, not in " + context.getClass)
|
||||
}
|
||||
|
||||
final def receive = ({
|
||||
|
|
@ -417,8 +397,10 @@ class FromConfig(val routerDispatcher: String = Dispatchers.DefaultDispatcherId)
|
|||
|
||||
def this() = this(Dispatchers.DefaultDispatcherId)
|
||||
|
||||
def createRoute(props: Props, routeeProvider: RouteeProvider): Route =
|
||||
throw new ConfigurationException("router " + routeeProvider.context.self + " needs external configuration from file (e.g. application.conf)")
|
||||
override def verifyConfig(): Unit =
|
||||
throw new ConfigurationException("router needs external configuration from file (e.g. application.conf)")
|
||||
|
||||
def createRoute(props: Props, routeeProvider: RouteeProvider): Route = null
|
||||
|
||||
def supervisorStrategy: SupervisorStrategy = Router.defaultSupervisorStrategy
|
||||
}
|
||||
|
|
@ -774,9 +756,11 @@ trait SmallestMailboxLike { this: RouterConfig ⇒
|
|||
* routers based on mailbox and actor internal state.
|
||||
*/
|
||||
protected def isProcessingMessage(a: ActorRef): Boolean = a match {
|
||||
case x: LocalActorRef ⇒
|
||||
val cell = x.underlying
|
||||
cell.mailbox.isScheduled && cell.currentMessage != null
|
||||
case x: ActorRefWithCell ⇒
|
||||
x.underlying match {
|
||||
case cell: ActorCell ⇒ cell.mailbox.isScheduled && cell.currentMessage != null
|
||||
case _ ⇒ false
|
||||
}
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
|
|
@ -788,8 +772,8 @@ trait SmallestMailboxLike { this: RouterConfig ⇒
|
|||
* routers based on mailbox and actor internal state.
|
||||
*/
|
||||
protected def hasMessages(a: ActorRef): Boolean = a match {
|
||||
case x: LocalActorRef ⇒ x.underlying.mailbox.hasMessages
|
||||
case _ ⇒ false
|
||||
case x: ActorRefWithCell ⇒ x.underlying.hasMessages
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -799,8 +783,12 @@ trait SmallestMailboxLike { this: RouterConfig ⇒
|
|||
* routers based on mailbox and actor internal state.
|
||||
*/
|
||||
protected def isSuspended(a: ActorRef): Boolean = a match {
|
||||
case x: LocalActorRef ⇒ x.underlying.mailbox.isSuspended
|
||||
case _ ⇒ false
|
||||
case x: ActorRefWithCell ⇒
|
||||
x.underlying match {
|
||||
case cell: ActorCell ⇒ cell.mailbox.isSuspended
|
||||
case _ ⇒ true
|
||||
}
|
||||
case _ ⇒ false
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -810,8 +798,8 @@ trait SmallestMailboxLike { this: RouterConfig ⇒
|
|||
* routers based on mailbox and actor internal state.
|
||||
*/
|
||||
protected def numberOfMessages(a: ActorRef): Int = a match {
|
||||
case x: LocalActorRef ⇒ x.underlying.mailbox.numberOfMessages
|
||||
case _ ⇒ 0
|
||||
case x: ActorRefWithCell ⇒ x.underlying.numberOfMessages
|
||||
case _ ⇒ 0
|
||||
}
|
||||
|
||||
def createRoute(props: Props, routeeProvider: RouteeProvider): Route = {
|
||||
|
|
@ -1283,12 +1271,20 @@ case class DefaultResizer(
|
|||
*/
|
||||
def pressure(routees: IndexedSeq[ActorRef]): Int = {
|
||||
routees count {
|
||||
case a: LocalActorRef ⇒
|
||||
val cell = a.underlying
|
||||
pressureThreshold match {
|
||||
case 1 ⇒ cell.mailbox.isScheduled && cell.mailbox.hasMessages
|
||||
case i if i < 1 ⇒ cell.mailbox.isScheduled && cell.currentMessage != null
|
||||
case threshold ⇒ cell.mailbox.numberOfMessages >= threshold
|
||||
case a: ActorRefWithCell ⇒
|
||||
a.underlying match {
|
||||
case cell: ActorCell ⇒
|
||||
pressureThreshold match {
|
||||
case 1 ⇒ cell.mailbox.isScheduled && cell.mailbox.hasMessages
|
||||
case i if i < 1 ⇒ cell.mailbox.isScheduled && cell.currentMessage != null
|
||||
case threshold ⇒ cell.mailbox.numberOfMessages >= threshold
|
||||
}
|
||||
case cell ⇒
|
||||
pressureThreshold match {
|
||||
case 1 ⇒ cell.hasMessages
|
||||
case i if i < 1 ⇒ true // unstarted cells are always busy, for example
|
||||
case threshold ⇒ cell.numberOfMessages >= threshold
|
||||
}
|
||||
}
|
||||
case x ⇒
|
||||
false
|
||||
|
|
|
|||
|
|
@ -9,16 +9,22 @@ import TimeUnit._
|
|||
import java.lang.{ Double ⇒ JDouble }
|
||||
|
||||
//TODO add @SerialVersionUID(1L) when SI-4804 is fixed
|
||||
case class Deadline private (time: Duration) {
|
||||
case class Deadline private (time: Duration) extends Ordered[Deadline] {
|
||||
def +(other: Duration): Deadline = copy(time = time + other)
|
||||
def -(other: Duration): Deadline = copy(time = time - other)
|
||||
def -(other: Deadline): Duration = time - other.time
|
||||
def timeLeft: Duration = this - Deadline.now
|
||||
def hasTimeLeft(): Boolean = !isOverdue() //Code reuse FTW
|
||||
def isOverdue(): Boolean = (time.toNanos - System.nanoTime()) < 0
|
||||
def compare(that: Deadline) = this.time compare that.time
|
||||
}
|
||||
|
||||
object Deadline {
|
||||
def now: Deadline = Deadline(Duration(System.nanoTime, NANOSECONDS))
|
||||
|
||||
implicit object DeadlineIsOrdered extends Ordering[Deadline] {
|
||||
def compare(a: Deadline, b: Deadline) = a compare b
|
||||
}
|
||||
}
|
||||
|
||||
object Duration {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ object Agent {
|
|||
*/
|
||||
class Agent[T](initialValue: T, system: ActorSystem) {
|
||||
private val ref = Ref(initialValue)
|
||||
private val updater = system.actorOf(Props(new AgentUpdater(this, ref))).asInstanceOf[LocalActorRef] //TODO can we avoid this somehow?
|
||||
private val updater = system.actorOf(Props(new AgentUpdater(this, ref))).asInstanceOf[InternalActorRef] //TODO can we avoid this somehow?
|
||||
|
||||
/**
|
||||
* Read the internal state of the agent.
|
||||
|
|
|
|||
|
|
@ -8,9 +8,19 @@
|
|||
akka {
|
||||
|
||||
cluster {
|
||||
# node to join - the full URI defined by a string on the form of "akka://system@hostname:port"
|
||||
# leave as empty string if the node should be a singleton cluster
|
||||
node-to-join = ""
|
||||
# Initial contact points of the cluster. Nodes to join at startup if auto-join = on.
|
||||
# The seed nodes also play the role of deputy nodes (the nodes responsible
|
||||
# for breaking network partitions).
|
||||
# Comma separated full URIs defined by a string on the form of "akka://system@hostname:port"
|
||||
# Leave as empty if the node should be a singleton cluster.
|
||||
seed-nodes = []
|
||||
|
||||
# how long to wait for one of the seed nodes to reply to initial join request
|
||||
seed-node-timeout = 5s
|
||||
|
||||
# Automatic join the seed-nodes at startup.
|
||||
# If seed-nodes is empty it will join itself and become a single node cluster.
|
||||
auto-join = on
|
||||
|
||||
# should the 'leader' in the cluster be allowed to automatically mark unreachable nodes as DOWN?
|
||||
auto-down = on
|
||||
|
|
@ -36,6 +46,10 @@ akka {
|
|||
# how often should the node move nodes, marked as unreachable by the failure detector, out of the membership ring?
|
||||
unreachable-nodes-reaper-interval = 1s
|
||||
|
||||
# A joining node stops sending heartbeats to the node to join if it hasn't become member
|
||||
# of the cluster within this deadline.
|
||||
join-timeout = 60s
|
||||
|
||||
failure-detector {
|
||||
|
||||
# defines the failure detector threshold
|
||||
|
|
@ -43,9 +57,23 @@ akka {
|
|||
# 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
|
||||
threshold = 8
|
||||
threshold = 8.0
|
||||
|
||||
implementation-class = ""
|
||||
# Minimum standard deviation to use for the normal distribution in
|
||||
# AccrualFailureDetector. Too low standard deviation might result in
|
||||
# too much sensitivity for sudden, but normal, deviations in heartbeat
|
||||
# inter arrival times.
|
||||
min-std-deviation = 100 ms
|
||||
|
||||
# Number of potentially lost/delayed heartbeats that will be
|
||||
# accepted before considering it to be an anomaly.
|
||||
# It is a factor of heartbeat-interval.
|
||||
# This margin is important to be able to survive sudden, occasional,
|
||||
# pauses in heartbeat arrivals, due to for example garbage collect or
|
||||
# network drop.
|
||||
acceptable-heartbeat-pause = 3s
|
||||
|
||||
implementation-class = "akka.cluster.AccrualFailureDetector"
|
||||
|
||||
max-sample-size = 1000
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,50 +7,98 @@ package akka.cluster
|
|||
import akka.actor.{ ActorSystem, Address, ExtendedActorSystem }
|
||||
import akka.remote.RemoteActorRefProvider
|
||||
import akka.event.Logging
|
||||
|
||||
import scala.collection.immutable.Map
|
||||
import scala.annotation.tailrec
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.TimeUnit.NANOSECONDS
|
||||
import akka.util.Duration
|
||||
import akka.util.duration._
|
||||
|
||||
object AccrualFailureDetector {
|
||||
private def realClock: () ⇒ Long = () ⇒ NANOSECONDS.toMillis(System.nanoTime)
|
||||
}
|
||||
/**
|
||||
* 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/>
|
||||
* Default threshold is 8, but can be configured in the Akka config.
|
||||
*
|
||||
* The suspicion level of failure is given by a value called φ (phi).
|
||||
* The basic idea of the φ failure detector is to express the value of φ on a scale that
|
||||
* is dynamically adjusted to reflect current network conditions. A configurable
|
||||
* threshold is used to decide if φ is considered to be a failure.
|
||||
*
|
||||
* The value of φ is calculated as:
|
||||
*
|
||||
* {{{
|
||||
* φ = -log10(1 - F(timeSinceLastHeartbeat)
|
||||
* }}}
|
||||
* where F is the cumulative distribution function of a normal distribution with mean
|
||||
* and standard deviation estimated from historical heartbeat inter-arrival times.
|
||||
*
|
||||
*
|
||||
* @param system Belongs to the [[akka.actor.ActorSystem]]. Used for logging.
|
||||
*
|
||||
* @param threshold 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
|
||||
*
|
||||
* @param maxSampleSize Number of samples to use for calculation of mean and standard deviation of
|
||||
* inter-arrival times.
|
||||
*
|
||||
* @param minStdDeviation Minimum standard deviation to use for the normal distribution used when calculating phi.
|
||||
* Too low standard deviation might result in too much sensitivity for sudden, but normal, deviations
|
||||
* in heartbeat inter arrival times.
|
||||
*
|
||||
* @param acceptableHeartbeatPause Duration corresponding to number of potentially lost/delayed
|
||||
* heartbeats that will be accepted before considering it to be an anomaly.
|
||||
* This margin is important to be able to survive sudden, occasional, pauses in heartbeat
|
||||
* arrivals, due to for example garbage collect or network drop.
|
||||
*
|
||||
* @param firstHeartbeatEstimate Bootstrap the stats with heartbeats that corresponds to
|
||||
* to this duration, with a with rather high standard deviation (since environment is unknown
|
||||
* in the beginning)
|
||||
*
|
||||
* @param clock The clock, returning current time in milliseconds, but can be faked for testing
|
||||
* purposes. It is only used for measuring intervals (duration).
|
||||
*
|
||||
*/
|
||||
class AccrualFailureDetector(
|
||||
val system: ActorSystem,
|
||||
val threshold: Int = 8,
|
||||
val maxSampleSize: Int = 1000,
|
||||
val timeMachine: () ⇒ Long = System.currentTimeMillis) extends FailureDetector {
|
||||
val threshold: Double,
|
||||
val maxSampleSize: Int,
|
||||
val minStdDeviation: Duration,
|
||||
val acceptableHeartbeatPause: Duration,
|
||||
val firstHeartbeatEstimate: Duration,
|
||||
val clock: () ⇒ Long = AccrualFailureDetector.realClock) extends FailureDetector {
|
||||
|
||||
import AccrualFailureDetector._
|
||||
|
||||
/**
|
||||
* Constructor that picks configuration from the settings.
|
||||
*/
|
||||
def this(
|
||||
system: ActorSystem,
|
||||
settings: ClusterSettings,
|
||||
timeMachine: () ⇒ Long = System.currentTimeMillis) =
|
||||
settings: ClusterSettings) =
|
||||
this(
|
||||
system,
|
||||
settings.FailureDetectorThreshold,
|
||||
settings.FailureDetectorMaxSampleSize,
|
||||
timeMachine)
|
||||
|
||||
private final val PhiFactor = 1.0 / math.log(10.0)
|
||||
settings.FailureDetectorAcceptableHeartbeatPause,
|
||||
settings.FailureDetectorMinStdDeviation,
|
||||
settings.HeartbeatInterval,
|
||||
AccrualFailureDetector.realClock)
|
||||
|
||||
private val log = Logging(system, "FailureDetector")
|
||||
|
||||
/**
|
||||
* Holds the failure statistics for a specific node Address.
|
||||
*/
|
||||
private case class FailureStats(mean: Double = 0.0, variance: Double = 0.0, deviation: Double = 0.0)
|
||||
|
||||
// guess statistics for first heartbeat,
|
||||
// important so that connections with only one heartbeat becomes unavailble
|
||||
private val failureStatsFirstHeartbeat = FailureStats(mean = 1000.0)
|
||||
// important so that connections with only one heartbeat becomes unavailable
|
||||
private val firstHeartbeat: HeartbeatHistory = {
|
||||
// bootstrap with 2 entries with rather high standard deviation
|
||||
val mean = firstHeartbeatEstimate.toMillis
|
||||
val stdDeviation = mean / 4
|
||||
HeartbeatHistory(maxSampleSize) :+ (mean - stdDeviation) :+ (mean + stdDeviation)
|
||||
}
|
||||
|
||||
private val acceptableHeartbeatPauseMillis = acceptableHeartbeatPause.toMillis
|
||||
|
||||
/**
|
||||
* Implement using optimistic lockless concurrency, all state is represented
|
||||
|
|
@ -58,8 +106,7 @@ class AccrualFailureDetector(
|
|||
*/
|
||||
private case class State(
|
||||
version: Long = 0L,
|
||||
failureStats: Map[Address, FailureStats] = Map.empty[Address, FailureStats],
|
||||
intervalHistory: Map[Address, IndexedSeq[Long]] = Map.empty[Address, IndexedSeq[Long]],
|
||||
history: Map[Address, HeartbeatHistory] = Map.empty,
|
||||
timestamps: Map[Address, Long] = Map.empty[Address, Long],
|
||||
explicitRemovals: Set[Address] = Set.empty[Address])
|
||||
|
||||
|
|
@ -78,96 +125,76 @@ class AccrualFailureDetector(
|
|||
final def heartbeat(connection: Address) {
|
||||
log.debug("Heartbeat from connection [{}] ", connection)
|
||||
|
||||
val timestamp = clock()
|
||||
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 newState = oldState copy (
|
||||
version = oldState.version + 1,
|
||||
failureStats = oldState.failureStats + (connection -> failureStatsFirstHeartbeat),
|
||||
intervalHistory = oldState.intervalHistory + (connection -> IndexedSeq.empty[Long]),
|
||||
timestamps = oldState.timestamps + (connection -> timeMachine()),
|
||||
explicitRemovals = oldState.explicitRemovals - connection)
|
||||
|
||||
// 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 = timeMachine()
|
||||
val interval = timestamp - latestTimestamp.get
|
||||
|
||||
val newIntervalsForConnection = (oldState.intervalHistory.get(connection) match {
|
||||
case Some(history) if history.size >= maxSampleSize ⇒
|
||||
// reached max history, drop first interval
|
||||
history drop 1
|
||||
case Some(history) ⇒ history
|
||||
case _ ⇒ IndexedSeq.empty[Long]
|
||||
}) :+ interval
|
||||
|
||||
val newFailureStats = {
|
||||
val newMean: Double = newIntervalsForConnection.sum.toDouble / newIntervalsForConnection.size
|
||||
|
||||
val oldConnectionFailureStats = oldState.failureStats.get(connection).getOrElse {
|
||||
throw new IllegalStateException("Can't calculate new failure statistics due to missing heartbeat history")
|
||||
}
|
||||
|
||||
val deviationSum = (0.0d /: newIntervalsForConnection) { (mean, interval) ⇒
|
||||
mean + interval.toDouble - newMean
|
||||
}
|
||||
|
||||
val newVariance: Double = deviationSum / newIntervalsForConnection.size
|
||||
val newDeviation: Double = math.sqrt(newVariance)
|
||||
|
||||
val newFailureStats = oldConnectionFailureStats copy (mean = newMean, deviation = newDeviation, variance = newVariance)
|
||||
oldState.failureStats + (connection -> newFailureStats)
|
||||
}
|
||||
|
||||
val newState = oldState copy (version = oldState.version + 1,
|
||||
failureStats = newFailureStats,
|
||||
intervalHistory = oldState.intervalHistory + (connection -> newIntervalsForConnection),
|
||||
timestamps = oldState.timestamps + (connection -> timestamp), // record new timestamp,
|
||||
explicitRemovals = oldState.explicitRemovals - connection)
|
||||
|
||||
// if we won the race then update else try again
|
||||
if (!state.compareAndSet(oldState, newState)) heartbeat(connection) // recur
|
||||
val newHistory = oldState.timestamps.get(connection) match {
|
||||
case None ⇒
|
||||
// this is heartbeat from a new connection
|
||||
// add starter records for this new connection
|
||||
firstHeartbeat
|
||||
case Some(latestTimestamp) ⇒
|
||||
// this is a known connection
|
||||
val interval = timestamp - latestTimestamp
|
||||
oldState.history(connection) :+ interval
|
||||
}
|
||||
|
||||
val newState = oldState copy (version = oldState.version + 1,
|
||||
history = oldState.history + (connection -> newHistory),
|
||||
timestamps = oldState.timestamps + (connection -> timestamp), // record new timestamp,
|
||||
explicitRemovals = oldState.explicitRemovals - connection)
|
||||
|
||||
// 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/>
|
||||
* The suspicion level of the accrual failure detector.
|
||||
*
|
||||
* If a connection does not have any records in failure detector then it is
|
||||
* considered healthy.
|
||||
* <p/>
|
||||
* Implementations of 'Cumulative Distribution Function' for Exponential Distribution.
|
||||
* For a discussion on the math read [https://issues.apache.org/jira/browse/CASSANDRA-2597].
|
||||
*/
|
||||
def phi(connection: Address): Double = {
|
||||
val oldState = state.get
|
||||
val oldTimestamp = oldState.timestamps.get(connection)
|
||||
|
||||
val phi =
|
||||
// if connection has been removed explicitly
|
||||
if (oldState.explicitRemovals.contains(connection)) Double.MaxValue
|
||||
else if (oldTimestamp.isEmpty) 0.0 // treat unmanaged connections, e.g. with zero heartbeats, as healthy connections
|
||||
else {
|
||||
val timestampDiff = timeMachine() - oldTimestamp.get
|
||||
// if connection has been removed explicitly
|
||||
if (oldState.explicitRemovals.contains(connection)) Double.MaxValue
|
||||
else if (oldTimestamp.isEmpty) 0.0 // treat unmanaged connections, e.g. with zero heartbeats, as healthy connections
|
||||
else {
|
||||
val timeDiff = clock() - oldTimestamp.get
|
||||
|
||||
val mean = oldState.failureStats.get(connection) match {
|
||||
case Some(FailureStats(mean, _, _)) ⇒ mean
|
||||
case _ ⇒ throw new IllegalStateException("Can't calculate Failure Detector Phi value for a node that have no heartbeat history")
|
||||
}
|
||||
val history = oldState.history(connection)
|
||||
val mean = history.mean
|
||||
val stdDeviation = ensureValidStdDeviation(history.stdDeviation)
|
||||
|
||||
if (mean == 0.0) 0.0
|
||||
else PhiFactor * timestampDiff / mean
|
||||
}
|
||||
val φ = phi(timeDiff, mean + acceptableHeartbeatPauseMillis, stdDeviation)
|
||||
|
||||
// FIXME change to debug log level, when failure detector is stable
|
||||
log.info("Phi value [{}] and threshold [{}] for connection [{}] ", phi, threshold, connection)
|
||||
phi
|
||||
// FIXME change to debug log level, when failure detector is stable
|
||||
if (φ > 1.0) log.info("Phi value [{}] for connection [{}], after [{} ms], based on [{}]",
|
||||
φ, connection, timeDiff, "N(" + mean + ", " + stdDeviation + ")")
|
||||
|
||||
φ
|
||||
}
|
||||
}
|
||||
|
||||
private[cluster] def phi(timeDiff: Long, mean: Double, stdDeviation: Double): Double = {
|
||||
val cdf = cumulativeDistributionFunction(timeDiff, mean, stdDeviation)
|
||||
-math.log10(1.0 - cdf)
|
||||
}
|
||||
|
||||
private val minStdDeviationMillis = minStdDeviation.toMillis
|
||||
|
||||
private def ensureValidStdDeviation(stdDeviation: Double): Double = math.max(stdDeviation, minStdDeviationMillis)
|
||||
|
||||
/**
|
||||
* Cumulative distribution function for N(mean, stdDeviation) normal distribution.
|
||||
* This is an approximation defined in β Mathematics Handbook.
|
||||
*/
|
||||
private[cluster] def cumulativeDistributionFunction(x: Double, mean: Double, stdDeviation: Double): Double = {
|
||||
val y = (x - mean) / stdDeviation
|
||||
// Cumulative distribution function for N(0, 1)
|
||||
1.0 / (1.0 + math.exp(-y * (1.5976 + 0.070566 * y * y)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -178,10 +205,9 @@ class AccrualFailureDetector(
|
|||
log.debug("Remove connection [{}] ", connection)
|
||||
val oldState = state.get
|
||||
|
||||
if (oldState.failureStats.contains(connection)) {
|
||||
if (oldState.history.contains(connection)) {
|
||||
val newState = oldState copy (version = oldState.version + 1,
|
||||
failureStats = oldState.failureStats - connection,
|
||||
intervalHistory = oldState.intervalHistory - connection,
|
||||
history = oldState.history - connection,
|
||||
timestamps = oldState.timestamps - connection,
|
||||
explicitRemovals = oldState.explicitRemovals + connection)
|
||||
|
||||
|
|
@ -190,3 +216,66 @@ class AccrualFailureDetector(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[cluster] object HeartbeatHistory {
|
||||
|
||||
/**
|
||||
* Create an empty HeartbeatHistory, without any history.
|
||||
* Can only be used as starting point for appending intervals.
|
||||
* The stats (mean, variance, stdDeviation) are not defined for
|
||||
* for empty HeartbeatHistory, i.e. throws AritmeticException.
|
||||
*/
|
||||
def apply(maxSampleSize: Int): HeartbeatHistory = HeartbeatHistory(
|
||||
maxSampleSize = maxSampleSize,
|
||||
intervals = IndexedSeq.empty,
|
||||
intervalSum = 0L,
|
||||
squaredIntervalSum = 0L)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the heartbeat statistics for a specific node Address.
|
||||
* It is capped by the number of samples specified in `maxSampleSize`.
|
||||
*
|
||||
* The stats (mean, variance, stdDeviation) are not defined for
|
||||
* for empty HeartbeatHistory, i.e. throws AritmeticException.
|
||||
*/
|
||||
private[cluster] case class HeartbeatHistory private (
|
||||
maxSampleSize: Int,
|
||||
intervals: IndexedSeq[Long],
|
||||
intervalSum: Long,
|
||||
squaredIntervalSum: Long) {
|
||||
|
||||
if (maxSampleSize < 1)
|
||||
throw new IllegalArgumentException("maxSampleSize must be >= 1, got [%s]" format maxSampleSize)
|
||||
if (intervalSum < 0L)
|
||||
throw new IllegalArgumentException("intervalSum must be >= 0, got [%s]" format intervalSum)
|
||||
if (squaredIntervalSum < 0L)
|
||||
throw new IllegalArgumentException("squaredIntervalSum must be >= 0, got [%s]" format squaredIntervalSum)
|
||||
|
||||
def mean: Double = intervalSum.toDouble / intervals.size
|
||||
|
||||
def variance: Double = (squaredIntervalSum.toDouble / intervals.size) - (mean * mean)
|
||||
|
||||
def stdDeviation: Double = math.sqrt(variance)
|
||||
|
||||
@tailrec
|
||||
final def :+(interval: Long): HeartbeatHistory = {
|
||||
if (intervals.size < maxSampleSize)
|
||||
HeartbeatHistory(
|
||||
maxSampleSize,
|
||||
intervals = intervals :+ interval,
|
||||
intervalSum = intervalSum + interval,
|
||||
squaredIntervalSum = squaredIntervalSum + pow2(interval))
|
||||
else
|
||||
dropOldest :+ interval // recur
|
||||
}
|
||||
|
||||
private def dropOldest: HeartbeatHistory = HeartbeatHistory(
|
||||
maxSampleSize,
|
||||
intervals = intervals drop 1,
|
||||
intervalSum = intervalSum - intervals.head,
|
||||
squaredIntervalSum = squaredIntervalSum - pow2(intervals.head))
|
||||
|
||||
private def pow2(x: Long) = x * x
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -13,24 +13,29 @@ import akka.actor.AddressFromURIString
|
|||
|
||||
class ClusterSettings(val config: Config, val systemName: String) {
|
||||
import config._
|
||||
final val FailureDetectorThreshold = getInt("akka.cluster.failure-detector.threshold")
|
||||
|
||||
final val FailureDetectorThreshold = getDouble("akka.cluster.failure-detector.threshold")
|
||||
final val FailureDetectorMaxSampleSize = getInt("akka.cluster.failure-detector.max-sample-size")
|
||||
final val FailureDetectorImplementationClass: Option[String] = getString("akka.cluster.failure-detector.implementation-class") match {
|
||||
case "" ⇒ None
|
||||
case fqcn ⇒ Some(fqcn)
|
||||
}
|
||||
final val NodeToJoin: Option[Address] = getString("akka.cluster.node-to-join") match {
|
||||
case "" ⇒ None
|
||||
case AddressFromURIString(addr) ⇒ Some(addr)
|
||||
}
|
||||
final val PeriodicTasksInitialDelay = Duration(getMilliseconds("akka.cluster.periodic-tasks-initial-delay"), MILLISECONDS)
|
||||
final val GossipInterval = Duration(getMilliseconds("akka.cluster.gossip-interval"), MILLISECONDS)
|
||||
final val HeartbeatInterval = Duration(getMilliseconds("akka.cluster.heartbeat-interval"), MILLISECONDS)
|
||||
final val LeaderActionsInterval = Duration(getMilliseconds("akka.cluster.leader-actions-interval"), MILLISECONDS)
|
||||
final val UnreachableNodesReaperInterval = Duration(getMilliseconds("akka.cluster.unreachable-nodes-reaper-interval"), MILLISECONDS)
|
||||
final val NrOfGossipDaemons = getInt("akka.cluster.nr-of-gossip-daemons")
|
||||
final val NrOfDeputyNodes = getInt("akka.cluster.nr-of-deputy-nodes")
|
||||
final val AutoDown = getBoolean("akka.cluster.auto-down")
|
||||
final val SchedulerTickDuration = Duration(getMilliseconds("akka.cluster.scheduler.tick-duration"), MILLISECONDS)
|
||||
final val SchedulerTicksPerWheel = getInt("akka.cluster.scheduler.ticks-per-wheel")
|
||||
final val FailureDetectorImplementationClass = getString("akka.cluster.failure-detector.implementation-class")
|
||||
final val FailureDetectorMinStdDeviation: Duration =
|
||||
Duration(getMilliseconds("akka.cluster.failure-detector.min-std-deviation"), MILLISECONDS)
|
||||
final val FailureDetectorAcceptableHeartbeatPause: Duration =
|
||||
Duration(getMilliseconds("akka.cluster.failure-detector.acceptable-heartbeat-pause"), MILLISECONDS)
|
||||
|
||||
final val SeedNodes: IndexedSeq[Address] = getStringList("akka.cluster.seed-nodes").asScala.map {
|
||||
case AddressFromURIString(addr) ⇒ addr
|
||||
}.toIndexedSeq
|
||||
final val SeedNodeTimeout: Duration = Duration(getMilliseconds("akka.cluster.seed-node-timeout"), MILLISECONDS)
|
||||
final val PeriodicTasksInitialDelay: Duration = Duration(getMilliseconds("akka.cluster.periodic-tasks-initial-delay"), MILLISECONDS)
|
||||
final val GossipInterval: Duration = Duration(getMilliseconds("akka.cluster.gossip-interval"), MILLISECONDS)
|
||||
final val HeartbeatInterval: Duration = Duration(getMilliseconds("akka.cluster.heartbeat-interval"), MILLISECONDS)
|
||||
final val LeaderActionsInterval: Duration = Duration(getMilliseconds("akka.cluster.leader-actions-interval"), MILLISECONDS)
|
||||
final val UnreachableNodesReaperInterval: Duration = Duration(getMilliseconds("akka.cluster.unreachable-nodes-reaper-interval"), MILLISECONDS)
|
||||
final val NrOfGossipDaemons: Int = getInt("akka.cluster.nr-of-gossip-daemons")
|
||||
final val NrOfDeputyNodes: Int = getInt("akka.cluster.nr-of-deputy-nodes")
|
||||
final val AutoJoin: Boolean = getBoolean("akka.cluster.auto-join")
|
||||
final val AutoDown: Boolean = getBoolean("akka.cluster.auto-down")
|
||||
final val JoinTimeout: Duration = Duration(getMilliseconds("akka.cluster.join-timeout"), MILLISECONDS)
|
||||
final val SchedulerTickDuration: Duration = Duration(getMilliseconds("akka.cluster.scheduler.tick-duration"), MILLISECONDS)
|
||||
final val SchedulerTicksPerWheel: Int = getInt("akka.cluster.scheduler.ticks-per-wheel")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ abstract class ClientDowningNodeThatIsUnreachableSpec
|
|||
"Client of a 4 node cluster" must {
|
||||
|
||||
"be able to DOWN a node that is UNREACHABLE (killed)" taggedAs LongRunningTest in {
|
||||
val thirdAddress = node(third).address
|
||||
val thirdAddress = address(third)
|
||||
awaitClusterUp(first, second, third, fourth)
|
||||
|
||||
runOn(first) {
|
||||
|
|
@ -47,23 +47,23 @@ abstract class ClientDowningNodeThatIsUnreachableSpec
|
|||
|
||||
// mark 'third' node as DOWN
|
||||
cluster.down(thirdAddress)
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(thirdAddress))
|
||||
cluster.latestGossip.members.exists(_.address == thirdAddress) must be(false)
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
}
|
||||
|
||||
runOn(second, fourth) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(thirdAddress))
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion")
|
||||
enterBarrier("await-completion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ abstract class ClientDowningNodeThatIsUpSpec
|
|||
"Client of a 4 node cluster" must {
|
||||
|
||||
"be able to DOWN a node that is UP (healthy and available)" taggedAs LongRunningTest in {
|
||||
val thirdAddress = node(third).address
|
||||
val thirdAddress = address(third)
|
||||
awaitClusterUp(first, second, third, fourth)
|
||||
|
||||
runOn(first) {
|
||||
// mark 'third' node as DOWN
|
||||
cluster.down(thirdAddress)
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
markNodeAsUnavailable(thirdAddress)
|
||||
|
||||
|
|
@ -52,16 +52,16 @@ abstract class ClientDowningNodeThatIsUpSpec
|
|||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
}
|
||||
|
||||
runOn(second, fourth) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(thirdAddress))
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion")
|
||||
enterBarrier("await-completion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.cluster
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.util.duration._
|
||||
import akka.testkit._
|
||||
|
||||
object ClusterAccrualFailureDetectorMultiJvmSpec extends MultiNodeConfig {
|
||||
val first = role("first")
|
||||
val second = role("second")
|
||||
val third = role("third")
|
||||
|
||||
commonConfig(debugConfig(on = false).
|
||||
withFallback(ConfigFactory.parseString("akka.cluster.failure-detector.threshold = 4")).
|
||||
withFallback(MultiNodeClusterSpec.clusterConfig))
|
||||
}
|
||||
|
||||
class ClusterAccrualFailureDetectorMultiJvmNode1 extends ClusterAccrualFailureDetectorSpec with AccrualFailureDetectorStrategy
|
||||
class ClusterAccrualFailureDetectorMultiJvmNode2 extends ClusterAccrualFailureDetectorSpec with AccrualFailureDetectorStrategy
|
||||
class ClusterAccrualFailureDetectorMultiJvmNode3 extends ClusterAccrualFailureDetectorSpec with AccrualFailureDetectorStrategy
|
||||
|
||||
abstract class ClusterAccrualFailureDetectorSpec
|
||||
extends MultiNodeSpec(ClusterAccrualFailureDetectorMultiJvmSpec)
|
||||
with MultiNodeClusterSpec {
|
||||
|
||||
import ClusterAccrualFailureDetectorMultiJvmSpec._
|
||||
|
||||
"A heartbeat driven Failure Detector" must {
|
||||
|
||||
"receive heartbeats so that all member nodes in the cluster are marked 'available'" taggedAs LongRunningTest in {
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
5.seconds.dilated.sleep // let them heartbeat
|
||||
cluster.failureDetector.isAvailable(first) must be(true)
|
||||
cluster.failureDetector.isAvailable(second) must be(true)
|
||||
cluster.failureDetector.isAvailable(third) must be(true)
|
||||
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"mark node as 'unavailable' if a node in the cluster is shut down (and its heartbeats stops)" taggedAs LongRunningTest in {
|
||||
runOn(first) {
|
||||
testConductor.shutdown(third, 0)
|
||||
}
|
||||
|
||||
enterBarrier("third-shutdown")
|
||||
|
||||
runOn(first, second) {
|
||||
// remaning nodes should detect failure...
|
||||
awaitCond(!cluster.failureDetector.isAvailable(third), 15.seconds)
|
||||
// other connections still ok
|
||||
cluster.failureDetector.isAvailable(first) must be(true)
|
||||
cluster.failureDetector.isAvailable(second) must be(true)
|
||||
}
|
||||
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,12 +46,12 @@ abstract class ConvergenceSpec
|
|||
// doesn't join immediately
|
||||
}
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"not reach convergence while any nodes are unreachable" taggedAs LongRunningTest in {
|
||||
val thirdAddress = node(third).address
|
||||
testConductor.enter("before-shutdown")
|
||||
val thirdAddress = address(third)
|
||||
enterBarrier("before-shutdown")
|
||||
|
||||
runOn(first) {
|
||||
// kill 'third' node
|
||||
|
|
@ -60,15 +60,13 @@ abstract class ConvergenceSpec
|
|||
}
|
||||
|
||||
runOn(first, second) {
|
||||
val firstAddress = node(first).address
|
||||
val secondAddress = node(second).address
|
||||
|
||||
within(28 seconds) {
|
||||
// third becomes unreachable
|
||||
awaitCond(cluster.latestGossip.overview.unreachable.size == 1)
|
||||
awaitCond(cluster.latestGossip.members.size == 2)
|
||||
awaitCond(cluster.latestGossip.members.forall(_.status == MemberStatus.Up))
|
||||
awaitSeenSameState(Seq(firstAddress, secondAddress))
|
||||
awaitSeenSameState(first, second)
|
||||
// still one unreachable
|
||||
cluster.latestGossip.overview.unreachable.size must be(1)
|
||||
cluster.latestGossip.overview.unreachable.head.address must be(thirdAddress)
|
||||
|
|
@ -78,30 +76,26 @@ abstract class ConvergenceSpec
|
|||
}
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
|
||||
"not move a new joining node to Up while there is no convergence" taggedAs LongRunningTest in {
|
||||
runOn(fourth) {
|
||||
// try to join
|
||||
cluster.join(node(first).address)
|
||||
cluster.join(first)
|
||||
}
|
||||
|
||||
val firstAddress = node(first).address
|
||||
val secondAddress = node(second).address
|
||||
val fourthAddress = node(fourth).address
|
||||
|
||||
def memberStatus(address: Address): Option[MemberStatus] =
|
||||
cluster.latestGossip.members.collectFirst { case m if m.address == address ⇒ m.status }
|
||||
|
||||
def assertNotMovedUp: Unit = {
|
||||
within(20 seconds) {
|
||||
awaitCond(cluster.latestGossip.members.size == 3)
|
||||
awaitSeenSameState(Seq(firstAddress, secondAddress, fourthAddress))
|
||||
memberStatus(firstAddress) must be(Some(MemberStatus.Up))
|
||||
memberStatus(secondAddress) must be(Some(MemberStatus.Up))
|
||||
awaitSeenSameState(first, second, fourth)
|
||||
memberStatus(first) must be(Some(MemberStatus.Up))
|
||||
memberStatus(second) must be(Some(MemberStatus.Up))
|
||||
// leader is not allowed to move the new node to Up
|
||||
memberStatus(fourthAddress) must be(Some(MemberStatus.Joining))
|
||||
memberStatus(fourth) must be(Some(MemberStatus.Joining))
|
||||
// still no convergence
|
||||
cluster.convergence.isDefined must be(false)
|
||||
}
|
||||
|
|
@ -116,7 +110,7 @@ abstract class ConvergenceSpec
|
|||
}
|
||||
}
|
||||
|
||||
testConductor.enter("after-3")
|
||||
enterBarrier("after-3")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ trait AccrualFailureDetectorStrategy extends FailureDetectorStrategy { self: Mul
|
|||
|
||||
override val failureDetector: FailureDetector = new AccrualFailureDetector(system, new ClusterSettings(system.settings.config, system.name))
|
||||
|
||||
override def markNodeAsAvailable(address: Address): Unit = { /* no-op */ }
|
||||
override def markNodeAsAvailable(address: Address): Unit = ()
|
||||
|
||||
override def markNodeAsUnavailable(address: Address): Unit = { /* no-op */ }
|
||||
override def markNodeAsUnavailable(address: Address): Unit = ()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.cluster
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.util.duration._
|
||||
import akka.testkit._
|
||||
|
||||
object GossipingAccrualFailureDetectorMultiJvmSpec extends MultiNodeConfig {
|
||||
val first = role("first")
|
||||
val second = role("second")
|
||||
val third = role("third")
|
||||
|
||||
commonConfig(debugConfig(on = false).
|
||||
withFallback(ConfigFactory.parseString("akka.cluster.failure-detector.threshold = 4")).
|
||||
withFallback(MultiNodeClusterSpec.clusterConfig))
|
||||
}
|
||||
|
||||
class GossipingWithAccrualFailureDetectorMultiJvmNode1 extends GossipingAccrualFailureDetectorSpec with AccrualFailureDetectorStrategy
|
||||
class GossipingWithAccrualFailureDetectorMultiJvmNode2 extends GossipingAccrualFailureDetectorSpec with AccrualFailureDetectorStrategy
|
||||
class GossipingWithAccrualFailureDetectorMultiJvmNode3 extends GossipingAccrualFailureDetectorSpec with AccrualFailureDetectorStrategy
|
||||
|
||||
abstract class GossipingAccrualFailureDetectorSpec
|
||||
extends MultiNodeSpec(GossipingAccrualFailureDetectorMultiJvmSpec)
|
||||
with MultiNodeClusterSpec {
|
||||
|
||||
import GossipingAccrualFailureDetectorMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
"A Gossip-driven Failure Detector" must {
|
||||
|
||||
"receive gossip heartbeats so that all member nodes in the cluster are marked 'available'" taggedAs LongRunningTest in {
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
5.seconds.dilated.sleep // let them gossip
|
||||
cluster.failureDetector.isAvailable(firstAddress) must be(true)
|
||||
cluster.failureDetector.isAvailable(secondAddress) must be(true)
|
||||
cluster.failureDetector.isAvailable(thirdAddress) must be(true)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
}
|
||||
|
||||
"mark node as 'unavailable' if a node in the cluster is shut down (and its heartbeats stops)" taggedAs LongRunningTest in {
|
||||
runOn(first) {
|
||||
testConductor.shutdown(third, 0)
|
||||
}
|
||||
|
||||
runOn(first, second) {
|
||||
// remaning nodes should detect failure...
|
||||
awaitCond(!cluster.failureDetector.isAvailable(thirdAddress), 10.seconds)
|
||||
// other connections still ok
|
||||
cluster.failureDetector.isAvailable(firstAddress) must be(true)
|
||||
cluster.failureDetector.isAvailable(secondAddress) must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.cluster
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.BeforeAndAfter
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.testkit._
|
||||
import akka.util.duration._
|
||||
import akka.util.Deadline
|
||||
|
||||
object JoinInProgressMultiJvmSpec extends MultiNodeConfig {
|
||||
val first = role("first")
|
||||
val second = role("second")
|
||||
|
||||
commonConfig(
|
||||
debugConfig(on = false)
|
||||
.withFallback(ConfigFactory.parseString("""
|
||||
akka.cluster {
|
||||
# simulate delay in gossip by turning it off
|
||||
gossip-interval = 300 s
|
||||
failure-detector {
|
||||
threshold = 4
|
||||
acceptable-heartbeat-pause = 1 second
|
||||
}
|
||||
}""") // increase the leader action task interval
|
||||
.withFallback(MultiNodeClusterSpec.clusterConfig)))
|
||||
}
|
||||
|
||||
class JoinInProgressMultiJvmNode1 extends JoinInProgressSpec with AccrualFailureDetectorStrategy
|
||||
class JoinInProgressMultiJvmNode2 extends JoinInProgressSpec with AccrualFailureDetectorStrategy
|
||||
|
||||
abstract class JoinInProgressSpec
|
||||
extends MultiNodeSpec(JoinInProgressMultiJvmSpec)
|
||||
with MultiNodeClusterSpec {
|
||||
|
||||
import JoinInProgressMultiJvmSpec._
|
||||
|
||||
"A cluster node" must {
|
||||
"send heartbeats immediately when joining to avoid false failure detection due to delayed gossip" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first) {
|
||||
startClusterNode()
|
||||
}
|
||||
|
||||
enterBarrier("first-started")
|
||||
|
||||
runOn(second) {
|
||||
cluster.join(first)
|
||||
}
|
||||
|
||||
runOn(first) {
|
||||
val until = Deadline.now + 5.seconds
|
||||
while (!until.isOverdue) {
|
||||
200.millis.sleep
|
||||
cluster.failureDetector.isAvailable(second) must be(true)
|
||||
}
|
||||
}
|
||||
|
||||
enterBarrier("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.cluster
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.BeforeAndAfter
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.testkit._
|
||||
import akka.util.duration._
|
||||
|
||||
object JoinSeedNodeMultiJvmSpec extends MultiNodeConfig {
|
||||
val seed1 = role("seed1")
|
||||
val seed2 = role("seed2")
|
||||
val ordinary1 = role("ordinary1")
|
||||
val ordinary2 = role("ordinary2")
|
||||
|
||||
commonConfig(debugConfig(on = false).
|
||||
withFallback(ConfigFactory.parseString("akka.cluster.auto-join = on")).
|
||||
withFallback(MultiNodeClusterSpec.clusterConfig))
|
||||
}
|
||||
|
||||
class JoinSeedNodeMultiJvmNode1 extends JoinSeedNodeSpec with FailureDetectorPuppetStrategy
|
||||
class JoinSeedNodeMultiJvmNode2 extends JoinSeedNodeSpec with FailureDetectorPuppetStrategy
|
||||
class JoinSeedNodeMultiJvmNode3 extends JoinSeedNodeSpec with FailureDetectorPuppetStrategy
|
||||
class JoinSeedNodeMultiJvmNode4 extends JoinSeedNodeSpec with FailureDetectorPuppetStrategy
|
||||
|
||||
abstract class JoinSeedNodeSpec
|
||||
extends MultiNodeSpec(JoinSeedNodeMultiJvmSpec)
|
||||
with MultiNodeClusterSpec {
|
||||
|
||||
import JoinSeedNodeMultiJvmSpec._
|
||||
|
||||
override def seedNodes = IndexedSeq(seed1, seed2)
|
||||
|
||||
"A cluster with configured seed nodes" must {
|
||||
"start the seed nodes sequentially" taggedAs LongRunningTest in {
|
||||
runOn(seed1) {
|
||||
startClusterNode()
|
||||
}
|
||||
enterBarrier("seed1-started")
|
||||
|
||||
runOn(seed2) {
|
||||
startClusterNode()
|
||||
}
|
||||
enterBarrier("seed2-started")
|
||||
|
||||
runOn(seed1, seed2) {
|
||||
awaitUpConvergence(2)
|
||||
}
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"join the seed nodes at startup" taggedAs LongRunningTest in {
|
||||
|
||||
startClusterNode()
|
||||
enterBarrier("all-started")
|
||||
|
||||
awaitUpConvergence(4)
|
||||
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,10 +33,6 @@ abstract class JoinTwoClustersSpec
|
|||
|
||||
import JoinTwoClustersMultiJvmSpec._
|
||||
|
||||
lazy val a1Address = node(a1).address
|
||||
lazy val b1Address = node(b1).address
|
||||
lazy val c1Address = node(c1).address
|
||||
|
||||
"Three different clusters (A, B and C)" must {
|
||||
|
||||
"be able to 'elect' a single leader after joining (A -> B)" taggedAs LongRunningTest in {
|
||||
|
|
@ -44,16 +40,16 @@ abstract class JoinTwoClustersSpec
|
|||
runOn(a1, b1, c1) {
|
||||
startClusterNode()
|
||||
}
|
||||
testConductor.enter("first-started")
|
||||
enterBarrier("first-started")
|
||||
|
||||
runOn(a1, a2) {
|
||||
cluster.join(a1Address)
|
||||
cluster.join(a1)
|
||||
}
|
||||
runOn(b1, b2) {
|
||||
cluster.join(b1Address)
|
||||
cluster.join(b1)
|
||||
}
|
||||
runOn(c1, c2) {
|
||||
cluster.join(c1Address)
|
||||
cluster.join(c1)
|
||||
}
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 2)
|
||||
|
|
@ -62,10 +58,10 @@ abstract class JoinTwoClustersSpec
|
|||
assertLeader(b1, b2)
|
||||
assertLeader(c1, c2)
|
||||
|
||||
testConductor.enter("two-members")
|
||||
enterBarrier("two-members")
|
||||
|
||||
runOn(b2) {
|
||||
cluster.join(a1Address)
|
||||
cluster.join(a1)
|
||||
}
|
||||
|
||||
runOn(a1, a2, b1, b2) {
|
||||
|
|
@ -75,20 +71,20 @@ abstract class JoinTwoClustersSpec
|
|||
assertLeader(a1, a2, b1, b2)
|
||||
assertLeader(c1, c2)
|
||||
|
||||
testConductor.enter("four-members")
|
||||
enterBarrier("four-members")
|
||||
}
|
||||
|
||||
"be able to 'elect' a single leader after joining (C -> A + B)" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(b2) {
|
||||
cluster.join(c1Address)
|
||||
cluster.join(c1)
|
||||
}
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 6)
|
||||
|
||||
assertLeader(a1, a2, b1, b2, c1, c2)
|
||||
|
||||
testConductor.enter("six-members")
|
||||
enterBarrier("six-members")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ abstract class LeaderDowningNodeThatIsUnreachableSpec
|
|||
"be able to DOWN a 'last' node that is UNREACHABLE" taggedAs LongRunningTest in {
|
||||
awaitClusterUp(first, second, third, fourth)
|
||||
|
||||
val fourthAddress = node(fourth).address
|
||||
val fourthAddress = address(fourth)
|
||||
runOn(first) {
|
||||
// kill 'fourth' node
|
||||
testConductor.shutdown(fourth, 0)
|
||||
testConductor.enter("down-fourth-node")
|
||||
enterBarrier("down-fourth-node")
|
||||
|
||||
// mark the node as unreachable in the failure detector
|
||||
markNodeAsUnavailable(fourthAddress)
|
||||
|
|
@ -57,26 +57,26 @@ abstract class LeaderDowningNodeThatIsUnreachableSpec
|
|||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("down-fourth-node")
|
||||
enterBarrier("down-fourth-node")
|
||||
}
|
||||
|
||||
runOn(second, third) {
|
||||
testConductor.enter("down-fourth-node")
|
||||
enterBarrier("down-fourth-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(fourthAddress), 30.seconds)
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion-1")
|
||||
enterBarrier("await-completion-1")
|
||||
}
|
||||
|
||||
"be able to DOWN a 'middle' node that is UNREACHABLE" taggedAs LongRunningTest in {
|
||||
val secondAddress = node(second).address
|
||||
val secondAddress = address(second)
|
||||
|
||||
testConductor.enter("before-down-second-node")
|
||||
enterBarrier("before-down-second-node")
|
||||
runOn(first) {
|
||||
// kill 'second' node
|
||||
testConductor.shutdown(second, 0)
|
||||
testConductor.enter("down-second-node")
|
||||
enterBarrier("down-second-node")
|
||||
|
||||
// mark the node as unreachable in the failure detector
|
||||
markNodeAsUnavailable(secondAddress)
|
||||
|
|
@ -87,16 +87,16 @@ abstract class LeaderDowningNodeThatIsUnreachableSpec
|
|||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("down-second-node")
|
||||
enterBarrier("down-second-node")
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("down-second-node")
|
||||
enterBarrier("down-second-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 2, canNotBePartOfMemberRing = Seq(secondAddress), 30 seconds)
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion-2")
|
||||
enterBarrier("await-completion-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ abstract class LeaderElectionSpec
|
|||
assertLeaderIn(sortedRoles)
|
||||
}
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
def shutdownLeaderAndVerifyNewLeader(alreadyShutdown: Int): Unit = {
|
||||
|
|
@ -63,44 +63,46 @@ abstract class LeaderElectionSpec
|
|||
myself match {
|
||||
|
||||
case `controller` ⇒
|
||||
val leaderAddress = node(leader).address
|
||||
testConductor.enter("before-shutdown")
|
||||
val leaderAddress = address(leader)
|
||||
enterBarrier("before-shutdown")
|
||||
testConductor.shutdown(leader, 0)
|
||||
testConductor.enter("after-shutdown", "after-down", "completed")
|
||||
enterBarrier("after-shutdown", "after-down", "completed")
|
||||
markNodeAsUnavailable(leaderAddress)
|
||||
|
||||
case `leader` ⇒
|
||||
testConductor.enter("before-shutdown", "after-shutdown")
|
||||
enterBarrier("before-shutdown", "after-shutdown")
|
||||
// this node will be shutdown by the controller and doesn't participate in more barriers
|
||||
|
||||
case `aUser` ⇒
|
||||
val leaderAddress = node(leader).address
|
||||
testConductor.enter("before-shutdown", "after-shutdown")
|
||||
val leaderAddress = address(leader)
|
||||
enterBarrier("before-shutdown", "after-shutdown")
|
||||
// user marks the shutdown leader as DOWN
|
||||
cluster.down(leaderAddress)
|
||||
testConductor.enter("after-down", "completed")
|
||||
enterBarrier("after-down", "completed")
|
||||
markNodeAsUnavailable(leaderAddress)
|
||||
|
||||
case _ if remainingRoles.contains(myself) ⇒
|
||||
// remaining cluster nodes, not shutdown
|
||||
testConductor.enter("before-shutdown", "after-shutdown", "after-down")
|
||||
enterBarrier("before-shutdown", "after-shutdown", "after-down")
|
||||
|
||||
awaitUpConvergence(currentRoles.size - 1)
|
||||
val nextExpectedLeader = remainingRoles.head
|
||||
cluster.isLeader must be(myself == nextExpectedLeader)
|
||||
assertLeaderIn(remainingRoles)
|
||||
|
||||
testConductor.enter("completed")
|
||||
enterBarrier("completed")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"be able to 're-elect' a single leader after leader has left" taggedAs LongRunningTest in {
|
||||
shutdownLeaderAndVerifyNewLeader(alreadyShutdown = 0)
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
|
||||
"be able to 're-elect' a single leader after leader has left (again)" taggedAs LongRunningTest in {
|
||||
shutdownLeaderAndVerifyNewLeader(alreadyShutdown = 1)
|
||||
enterBarrier("after-3")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.cluster
|
||||
|
||||
import scala.collection.immutable.SortedSet
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.testkit._
|
||||
import akka.util.duration._
|
||||
|
||||
object LeaderLeavingMultiJvmSpec extends MultiNodeConfig {
|
||||
val first = role("first")
|
||||
val second = role("second")
|
||||
val third = role("third")
|
||||
|
||||
commonConfig(
|
||||
debugConfig(on = false)
|
||||
.withFallback(ConfigFactory.parseString("""
|
||||
akka.cluster {
|
||||
leader-actions-interval = 5 s # increase the leader action task frequency to make sure we get a chance to test the LEAVING state
|
||||
unreachable-nodes-reaper-interval = 30 s
|
||||
}""")
|
||||
.withFallback(MultiNodeClusterSpec.clusterConfig)))
|
||||
}
|
||||
|
||||
class LeaderLeavingMultiJvmNode1 extends LeaderLeavingSpec with FailureDetectorPuppetStrategy
|
||||
class LeaderLeavingMultiJvmNode2 extends LeaderLeavingSpec with FailureDetectorPuppetStrategy
|
||||
class LeaderLeavingMultiJvmNode3 extends LeaderLeavingSpec with FailureDetectorPuppetStrategy
|
||||
|
||||
abstract class LeaderLeavingSpec
|
||||
extends MultiNodeSpec(LeaderLeavingMultiJvmSpec)
|
||||
with MultiNodeClusterSpec {
|
||||
|
||||
import LeaderLeavingMultiJvmSpec._
|
||||
|
||||
val leaderHandoffWaitingTime = 30.seconds.dilated
|
||||
|
||||
"A LEADER that is LEAVING" must {
|
||||
|
||||
"be moved to LEAVING, then to EXITING, then to REMOVED, then be shut down and then a new LEADER should be elected" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
val oldLeaderAddress = cluster.leader
|
||||
|
||||
if (cluster.isLeader) {
|
||||
|
||||
cluster.leave(oldLeaderAddress)
|
||||
enterBarrier("leader-left")
|
||||
|
||||
// verify that a NEW LEADER have taken over
|
||||
awaitCond(!cluster.isLeader)
|
||||
|
||||
// verify that the LEADER is shut down
|
||||
awaitCond(!cluster.isRunning, 30.seconds.dilated)
|
||||
|
||||
// verify that the LEADER is REMOVED
|
||||
awaitCond(cluster.status == MemberStatus.Removed)
|
||||
|
||||
} else {
|
||||
|
||||
enterBarrier("leader-left")
|
||||
|
||||
// verify that the LEADER is LEAVING
|
||||
awaitCond(cluster.latestGossip.members.exists(m ⇒ m.status == MemberStatus.Leaving && m.address == oldLeaderAddress), leaderHandoffWaitingTime) // wait on LEAVING
|
||||
|
||||
// verify that the LEADER is EXITING
|
||||
awaitCond(cluster.latestGossip.members.exists(m ⇒ m.status == MemberStatus.Exiting && m.address == oldLeaderAddress), leaderHandoffWaitingTime) // wait on EXITING
|
||||
|
||||
// verify that the LEADER is no longer part of the 'members' set
|
||||
awaitCond(cluster.latestGossip.members.forall(_.address != oldLeaderAddress), leaderHandoffWaitingTime)
|
||||
|
||||
// verify that the LEADER is not part of the 'unreachable' set
|
||||
awaitCond(cluster.latestGossip.overview.unreachable.forall(_.address != oldLeaderAddress), leaderHandoffWaitingTime)
|
||||
|
||||
// verify that we have a new LEADER
|
||||
awaitCond(cluster.leader != oldLeaderAddress, leaderHandoffWaitingTime)
|
||||
}
|
||||
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,37 +37,33 @@ abstract class MembershipChangeListenerExitingSpec
|
|||
|
||||
import MembershipChangeListenerExitingMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
"A registered MembershipChangeListener" must {
|
||||
"be notified when new node is EXITING" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
testConductor.enter("registered-listener")
|
||||
cluster.leave(secondAddress)
|
||||
enterBarrier("registered-listener")
|
||||
cluster.leave(second)
|
||||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
val exitingLatch = TestLatch()
|
||||
cluster.registerListener(new MembershipChangeListener {
|
||||
def notify(members: SortedSet[Member]) {
|
||||
if (members.size == 3 && members.exists(m ⇒ m.address == secondAddress && m.status == MemberStatus.Exiting))
|
||||
if (members.size == 3 && members.exists(m ⇒ m.address == address(second) && m.status == MemberStatus.Exiting))
|
||||
exitingLatch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
exitingLatch.await
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,34 +30,31 @@ abstract class MembershipChangeListenerJoinSpec
|
|||
|
||||
import MembershipChangeListenerJoinMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
|
||||
"A registered MembershipChangeListener" must {
|
||||
"be notified when new node is JOINING" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first) {
|
||||
val joinLatch = TestLatch()
|
||||
val expectedAddresses = Set(firstAddress, secondAddress)
|
||||
val expectedAddresses = Set(first, second) map address
|
||||
cluster.registerListener(new MembershipChangeListener {
|
||||
def notify(members: SortedSet[Member]) {
|
||||
if (members.map(_.address) == expectedAddresses && members.exists(_.status == MemberStatus.Joining))
|
||||
joinLatch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
|
||||
joinLatch.await
|
||||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("registered-listener")
|
||||
cluster.join(firstAddress)
|
||||
enterBarrier("registered-listener")
|
||||
cluster.join(first)
|
||||
}
|
||||
|
||||
awaitUpConvergence(2)
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.typesafe.config.ConfigFactory
|
|||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.testkit._
|
||||
import akka.actor.Address
|
||||
|
||||
object MembershipChangeListenerLeavingMultiJvmSpec extends MultiNodeConfig {
|
||||
val first = role("first")
|
||||
|
|
@ -34,39 +35,35 @@ abstract class MembershipChangeListenerLeavingSpec
|
|||
|
||||
import MembershipChangeListenerLeavingMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
"A registered MembershipChangeListener" must {
|
||||
"be notified when new node is LEAVING" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
testConductor.enter("registered-listener")
|
||||
cluster.leave(secondAddress)
|
||||
enterBarrier("registered-listener")
|
||||
cluster.leave(second)
|
||||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
val latch = TestLatch()
|
||||
val expectedAddresses = Set(firstAddress, secondAddress, thirdAddress)
|
||||
val expectedAddresses = Set(first, second, third) map address
|
||||
cluster.registerListener(new MembershipChangeListener {
|
||||
def notify(members: SortedSet[Member]) {
|
||||
if (members.map(_.address) == expectedAddresses &&
|
||||
members.exists(m ⇒ m.address == secondAddress && m.status == MemberStatus.Leaving))
|
||||
members.exists(m ⇒ m.address == address(second) && m.status == MemberStatus.Leaving))
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
latch.await
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@ abstract class MembershipChangeListenerUpSpec
|
|||
|
||||
import MembershipChangeListenerUpMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
"A set of connected cluster systems" must {
|
||||
|
||||
"(when two nodes) after cluster convergence updates the membership table then all MembershipChangeListeners should be triggered" taggedAs LongRunningTest in {
|
||||
|
|
@ -39,44 +35,44 @@ abstract class MembershipChangeListenerUpSpec
|
|||
|
||||
runOn(first, second) {
|
||||
val latch = TestLatch()
|
||||
val expectedAddresses = Set(firstAddress, secondAddress)
|
||||
val expectedAddresses = Set(first, second) map address
|
||||
cluster.registerListener(new MembershipChangeListener {
|
||||
def notify(members: SortedSet[Member]) {
|
||||
if (members.map(_.address) == expectedAddresses && members.forall(_.status == MemberStatus.Up))
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("listener-1-registered")
|
||||
cluster.join(firstAddress)
|
||||
enterBarrier("listener-1-registered")
|
||||
cluster.join(first)
|
||||
latch.await
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("listener-1-registered")
|
||||
enterBarrier("listener-1-registered")
|
||||
}
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"(when three nodes) after cluster convergence updates the membership table then all MembershipChangeListeners should be triggered" taggedAs LongRunningTest in {
|
||||
|
||||
val latch = TestLatch()
|
||||
val expectedAddresses = Set(firstAddress, secondAddress, thirdAddress)
|
||||
val expectedAddresses = Set(first, second, third) map address
|
||||
cluster.registerListener(new MembershipChangeListener {
|
||||
def notify(members: SortedSet[Member]) {
|
||||
if (members.map(_.address) == expectedAddresses && members.forall(_.status == MemberStatus.Up))
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("listener-2-registered")
|
||||
enterBarrier("listener-2-registered")
|
||||
|
||||
runOn(third) {
|
||||
cluster.join(firstAddress)
|
||||
cluster.join(first)
|
||||
}
|
||||
|
||||
latch.await
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ import akka.util.duration._
|
|||
import akka.util.Duration
|
||||
import org.scalatest.Suite
|
||||
import org.scalatest.TestFailedException
|
||||
import scala.util.control.NoStackTrace
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import akka.actor.ActorPath
|
||||
import akka.actor.RootActorPath
|
||||
|
||||
object MultiNodeClusterSpec {
|
||||
def clusterConfig: Config = ConfigFactory.parseString("""
|
||||
akka.cluster {
|
||||
auto-join = off
|
||||
auto-down = off
|
||||
gossip-interval = 200 ms
|
||||
heartbeat-interval = 400 ms
|
||||
leader-actions-interval = 200 ms
|
||||
unreachable-nodes-reaper-interval = 200 ms
|
||||
periodic-tasks-initial-delay = 300 ms
|
||||
nr-of-deputy-nodes = 2
|
||||
}
|
||||
akka.test {
|
||||
single-expect-default = 5 s
|
||||
|
|
@ -36,6 +38,27 @@ trait MultiNodeClusterSpec extends FailureDetectorStrategy with Suite { self: Mu
|
|||
|
||||
override def initialParticipants = roles.size
|
||||
|
||||
private val cachedAddresses = new ConcurrentHashMap[RoleName, Address]
|
||||
|
||||
/**
|
||||
* Lookup the Address for the role.
|
||||
*
|
||||
* Implicit conversion from RoleName to Address.
|
||||
*
|
||||
* It is cached, which has the implication that stopping
|
||||
* and then restarting a role (jvm) with another address is not
|
||||
* supported.
|
||||
*/
|
||||
implicit def address(role: RoleName): Address = {
|
||||
cachedAddresses.get(role) match {
|
||||
case null ⇒
|
||||
val address = node(role).address
|
||||
cachedAddresses.put(role, address)
|
||||
address
|
||||
case address ⇒ address
|
||||
}
|
||||
}
|
||||
|
||||
// Cluster tests are written so that if previous step (test method) failed
|
||||
// it will most likely not be possible to run next step. This ensures
|
||||
// fail fast of steps after the first failure.
|
||||
|
|
@ -54,10 +77,22 @@ trait MultiNodeClusterSpec extends FailureDetectorStrategy with Suite { self: Mu
|
|||
throw t
|
||||
}
|
||||
|
||||
/**
|
||||
* Make it possible to override/configure seedNodes from tests without
|
||||
* specifying in config. Addresses are unknown before startup time.
|
||||
*/
|
||||
protected def seedNodes: IndexedSeq[RoleName] = IndexedSeq.empty
|
||||
|
||||
/**
|
||||
* The cluster node instance. Needs to be lazily created.
|
||||
*/
|
||||
private lazy val clusterNode = new Cluster(system.asInstanceOf[ExtendedActorSystem], failureDetector)
|
||||
private lazy val clusterNode = new Cluster(system.asInstanceOf[ExtendedActorSystem], failureDetector) {
|
||||
override def seedNodes: IndexedSeq[Address] = {
|
||||
val testSeedNodes = MultiNodeClusterSpec.this.seedNodes
|
||||
if (testSeedNodes.isEmpty) super.seedNodes
|
||||
else testSeedNodes map address
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cluster node to use.
|
||||
|
|
@ -65,10 +100,15 @@ trait MultiNodeClusterSpec extends FailureDetectorStrategy with Suite { self: Mu
|
|||
def cluster: Cluster = clusterNode
|
||||
|
||||
/**
|
||||
* Use this method instead of 'cluster.self'
|
||||
* for the initial startup of the cluster node.
|
||||
* Use this method for the initial startup of the cluster node.
|
||||
*/
|
||||
def startClusterNode(): Unit = cluster.self
|
||||
def startClusterNode(): Unit = {
|
||||
if (cluster.latestGossip.members.isEmpty) {
|
||||
cluster join myself
|
||||
awaitCond(cluster.latestGossip.members.exists(_.address == address(myself)))
|
||||
} else
|
||||
cluster.self
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the cluster with the specified member
|
||||
|
|
@ -92,14 +132,14 @@ trait MultiNodeClusterSpec extends FailureDetectorStrategy with Suite { self: Mu
|
|||
// make sure that the node-to-join is started before other join
|
||||
startClusterNode()
|
||||
}
|
||||
testConductor.enter(roles.head.name + "-started")
|
||||
enterBarrier(roles.head.name + "-started")
|
||||
if (roles.tail.contains(myself)) {
|
||||
cluster.join(node(roles.head).address)
|
||||
cluster.join(roles.head)
|
||||
}
|
||||
if (upConvergence && roles.contains(myself)) {
|
||||
awaitUpConvergence(numberOfMembers = roles.length)
|
||||
}
|
||||
testConductor.enter(roles.map(_.name).mkString("-") + "-joined")
|
||||
enterBarrier(roles.map(_.name).mkString("-") + "-joined")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -150,7 +190,7 @@ trait MultiNodeClusterSpec extends FailureDetectorStrategy with Suite { self: Mu
|
|||
/**
|
||||
* Wait until the specified nodes have seen the same gossip overview.
|
||||
*/
|
||||
def awaitSeenSameState(addresses: Seq[Address]): Unit = {
|
||||
def awaitSeenSameState(addresses: Address*): Unit = {
|
||||
awaitCond {
|
||||
val seen = cluster.latestGossip.overview.seen
|
||||
val seenVectorClocks = addresses.flatMap(seen.get(_))
|
||||
|
|
@ -168,10 +208,9 @@ trait MultiNodeClusterSpec extends FailureDetectorStrategy with Suite { self: Mu
|
|||
*/
|
||||
implicit val clusterOrdering: Ordering[RoleName] = new Ordering[RoleName] {
|
||||
import Member.addressOrdering
|
||||
def compare(x: RoleName, y: RoleName) = addressOrdering.compare(node(x).address, node(y).address)
|
||||
def compare(x: RoleName, y: RoleName) = addressOrdering.compare(address(x), address(y))
|
||||
}
|
||||
|
||||
def roleName(address: Address): Option[RoleName] = {
|
||||
roles.find(node(_).address == address)
|
||||
}
|
||||
def roleName(addr: Address): Option[RoleName] = roles.find(address(_) == addr)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ object NodeJoinMultiJvmSpec extends MultiNodeConfig {
|
|||
commonConfig(
|
||||
debugConfig(on = false)
|
||||
.withFallback(ConfigFactory.parseString("akka.cluster.leader-actions-interval = 5 s") // increase the leader action task interval
|
||||
.withFallback(MultiNodeClusterSpec.clusterConfig)))
|
||||
.withFallback(MultiNodeClusterSpec.clusterConfig)))
|
||||
}
|
||||
|
||||
class NodeJoinMultiJvmNode1 extends NodeJoinSpec with FailureDetectorPuppetStrategy
|
||||
|
|
@ -29,9 +29,6 @@ abstract class NodeJoinSpec
|
|||
|
||||
import NodeJoinMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
|
||||
"A cluster node" must {
|
||||
"join another cluster and get status JOINING - when sending a 'Join' command" taggedAs LongRunningTest in {
|
||||
|
||||
|
|
@ -39,13 +36,15 @@ abstract class NodeJoinSpec
|
|||
startClusterNode()
|
||||
}
|
||||
|
||||
enterBarrier("first-started")
|
||||
|
||||
runOn(second) {
|
||||
cluster.join(firstAddress)
|
||||
cluster.join(first)
|
||||
}
|
||||
|
||||
awaitCond(cluster.latestGossip.members.exists { member ⇒ member.address == secondAddress && member.status == MemberStatus.Joining })
|
||||
awaitCond(cluster.latestGossip.members.exists { member ⇒ member.address == address(second) && member.status == MemberStatus.Joining })
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ object NodeLeavingAndExitingAndBeingRemovedMultiJvmSpec extends MultiNodeConfig
|
|||
commonConfig(debugConfig(on = false).withFallback(MultiNodeClusterSpec.clusterConfig))
|
||||
}
|
||||
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode1 extends NodeLeavingAndExitingAndBeingRemovedSpec with AccrualFailureDetectorStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode2 extends NodeLeavingAndExitingAndBeingRemovedSpec with AccrualFailureDetectorStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode3 extends NodeLeavingAndExitingAndBeingRemovedSpec with AccrualFailureDetectorStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode1 extends NodeLeavingAndExitingAndBeingRemovedSpec with FailureDetectorPuppetStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode2 extends NodeLeavingAndExitingAndBeingRemovedSpec with FailureDetectorPuppetStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode3 extends NodeLeavingAndExitingAndBeingRemovedSpec with FailureDetectorPuppetStrategy
|
||||
|
||||
abstract class NodeLeavingAndExitingAndBeingRemovedSpec
|
||||
extends MultiNodeSpec(NodeLeavingAndExitingAndBeingRemovedMultiJvmSpec)
|
||||
|
|
@ -28,38 +28,34 @@ abstract class NodeLeavingAndExitingAndBeingRemovedSpec
|
|||
|
||||
import NodeLeavingAndExitingAndBeingRemovedMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
val reaperWaitingTime = 30.seconds.dilated
|
||||
|
||||
"A node that is LEAVING a non-singleton cluster" must {
|
||||
|
||||
// FIXME make it work and remove ignore
|
||||
"be moved to EXITING and then to REMOVED by the reaper" taggedAs LongRunningTest ignore {
|
||||
"eventually set to REMOVED by the reaper, and removed from membership ring and seen table" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
cluster.leave(secondAddress)
|
||||
cluster.leave(second)
|
||||
}
|
||||
testConductor.enter("second-left")
|
||||
enterBarrier("second-left")
|
||||
|
||||
runOn(first, third) {
|
||||
// verify that the 'second' node is no longer part of the 'members' set
|
||||
awaitCond(cluster.latestGossip.members.forall(_.address != secondAddress), reaperWaitingTime)
|
||||
awaitCond(cluster.latestGossip.members.forall(_.address != address(second)), reaperWaitingTime)
|
||||
|
||||
// verify that the 'second' node is part of the 'unreachable' set
|
||||
awaitCond(cluster.latestGossip.overview.unreachable.exists(_.status == MemberStatus.Removed), reaperWaitingTime)
|
||||
|
||||
// verify node that got removed is 'second' node
|
||||
val isRemoved = cluster.latestGossip.overview.unreachable.find(_.status == MemberStatus.Removed)
|
||||
isRemoved must be('defined)
|
||||
isRemoved.get.address must be(secondAddress)
|
||||
// verify that the 'second' node is not part of the 'unreachable' set
|
||||
awaitCond(cluster.latestGossip.overview.unreachable.forall(_.address != address(second)), reaperWaitingTime)
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
runOn(second) {
|
||||
// verify that the second node is shut down and has status REMOVED
|
||||
awaitCond(!cluster.isRunning, reaperWaitingTime)
|
||||
awaitCond(cluster.status == MemberStatus.Removed, reaperWaitingTime)
|
||||
}
|
||||
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,21 +36,16 @@ abstract class NodeLeavingAndExitingSpec
|
|||
|
||||
import NodeLeavingAndExitingMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
"A node that is LEAVING a non-singleton cluster" must {
|
||||
|
||||
// FIXME make it work and remove ignore
|
||||
"be moved to EXITING by the leader" taggedAs LongRunningTest ignore {
|
||||
"be moved to EXITING by the leader" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
cluster.leave(secondAddress)
|
||||
cluster.leave(second)
|
||||
}
|
||||
testConductor.enter("second-left")
|
||||
enterBarrier("second-left")
|
||||
|
||||
runOn(first, third) {
|
||||
|
||||
|
|
@ -60,16 +55,16 @@ abstract class NodeLeavingAndExitingSpec
|
|||
awaitCond(cluster.latestGossip.members.exists(_.status == MemberStatus.Leaving)) // wait on LEAVING
|
||||
val hasLeft = cluster.latestGossip.members.find(_.status == MemberStatus.Leaving) // verify node that left
|
||||
hasLeft must be('defined)
|
||||
hasLeft.get.address must be(secondAddress)
|
||||
hasLeft.get.address must be(address(second))
|
||||
|
||||
// 2. Verify that 'second' node is set to EXITING
|
||||
awaitCond(cluster.latestGossip.members.exists(_.status == MemberStatus.Exiting)) // wait on EXITING
|
||||
val hasExited = cluster.latestGossip.members.find(_.status == MemberStatus.Exiting) // verify node that exited
|
||||
hasExited must be('defined)
|
||||
hasExited.get.address must be(secondAddress)
|
||||
hasExited.get.address must be(address(second))
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,31 +30,26 @@ abstract class NodeLeavingSpec
|
|||
|
||||
import NodeLeavingMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
"A node that is LEAVING a non-singleton cluster" must {
|
||||
|
||||
// FIXME make it work and remove ignore
|
||||
"be marked as LEAVING in the converged membership table" taggedAs LongRunningTest ignore {
|
||||
"be marked as LEAVING in the converged membership table" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
cluster.leave(secondAddress)
|
||||
cluster.leave(second)
|
||||
}
|
||||
testConductor.enter("second-left")
|
||||
enterBarrier("second-left")
|
||||
|
||||
runOn(first, third) {
|
||||
awaitCond(cluster.latestGossip.members.exists(_.status == MemberStatus.Leaving))
|
||||
|
||||
val hasLeft = cluster.latestGossip.members.find(_.status == MemberStatus.Leaving)
|
||||
hasLeft must be('defined)
|
||||
hasLeft.get.address must be(secondAddress)
|
||||
hasLeft.get.address must be(address(second))
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@ abstract class NodeMembershipSpec
|
|||
|
||||
import NodeMembershipMultiJvmSpec._
|
||||
|
||||
lazy val firstAddress = node(first).address
|
||||
lazy val secondAddress = node(second).address
|
||||
lazy val thirdAddress = node(third).address
|
||||
|
||||
"A set of connected cluster systems" must {
|
||||
|
||||
"(when two nodes) start gossiping to each other so that both nodes gets the same gossip info" taggedAs LongRunningTest in {
|
||||
|
|
@ -38,35 +34,35 @@ abstract class NodeMembershipSpec
|
|||
runOn(first) {
|
||||
startClusterNode()
|
||||
}
|
||||
testConductor.enter("first-started")
|
||||
enterBarrier("first-started")
|
||||
|
||||
runOn(first, second) {
|
||||
cluster.join(firstAddress)
|
||||
cluster.join(first)
|
||||
awaitCond(cluster.latestGossip.members.size == 2)
|
||||
assertMembers(cluster.latestGossip.members, firstAddress, secondAddress)
|
||||
assertMembers(cluster.latestGossip.members, first, second)
|
||||
awaitCond {
|
||||
cluster.latestGossip.members.forall(_.status == MemberStatus.Up)
|
||||
}
|
||||
awaitCond(cluster.convergence.isDefined)
|
||||
}
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"(when three nodes) start gossiping to each other so that all nodes gets the same gossip info" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(third) {
|
||||
cluster.join(firstAddress)
|
||||
cluster.join(first)
|
||||
}
|
||||
|
||||
awaitCond(cluster.latestGossip.members.size == 3)
|
||||
assertMembers(cluster.latestGossip.members, firstAddress, secondAddress, thirdAddress)
|
||||
assertMembers(cluster.latestGossip.members, first, second, third)
|
||||
awaitCond {
|
||||
cluster.latestGossip.members.forall(_.status == MemberStatus.Up)
|
||||
}
|
||||
awaitCond(cluster.convergence.isDefined)
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ abstract class NodeUpSpec
|
|||
|
||||
awaitClusterUp(first, second)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"be unaffected when joining again" taggedAs LongRunningTest in {
|
||||
|
|
@ -45,12 +45,12 @@ abstract class NodeUpSpec
|
|||
unexpected.set(members)
|
||||
}
|
||||
})
|
||||
testConductor.enter("listener-registered")
|
||||
enterBarrier("listener-registered")
|
||||
|
||||
runOn(second) {
|
||||
cluster.join(node(first).address)
|
||||
cluster.join(first)
|
||||
}
|
||||
testConductor.enter("joined-again")
|
||||
enterBarrier("joined-again")
|
||||
|
||||
// let it run for a while to make sure that nothing bad happens
|
||||
for (n ← 1 to 20) {
|
||||
|
|
@ -59,7 +59,7 @@ abstract class NodeUpSpec
|
|||
cluster.latestGossip.members.forall(_.status == MemberStatus.Up) must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ object SingletonClusterMultiJvmSpec extends MultiNodeConfig {
|
|||
commonConfig(debugConfig(on = false).
|
||||
withFallback(ConfigFactory.parseString("""
|
||||
akka.cluster {
|
||||
auto-join = on
|
||||
auto-down = on
|
||||
failure-detector.threshold = 4
|
||||
}
|
||||
|
|
@ -38,17 +39,25 @@ abstract class SingletonClusterSpec
|
|||
|
||||
"A cluster of 2 nodes" must {
|
||||
|
||||
"not be singleton cluster when joined" taggedAs LongRunningTest in {
|
||||
"become singleton cluster when started with 'auto-join=on' and 'seed-nodes=[]'" taggedAs LongRunningTest in {
|
||||
startClusterNode()
|
||||
awaitUpConvergence(1)
|
||||
cluster.isSingletonCluster must be(true)
|
||||
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"not be singleton cluster when joined with other node" taggedAs LongRunningTest in {
|
||||
awaitClusterUp(first, second)
|
||||
cluster.isSingletonCluster must be(false)
|
||||
assertLeader(first, second)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
|
||||
"become singleton cluster when one node is shutdown" taggedAs LongRunningTest in {
|
||||
runOn(first) {
|
||||
val secondAddress = node(second).address
|
||||
val secondAddress = address(second)
|
||||
testConductor.shutdown(second, 0)
|
||||
|
||||
markNodeAsUnavailable(secondAddress)
|
||||
|
|
@ -58,7 +67,7 @@ abstract class SingletonClusterSpec
|
|||
assertLeader(first)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-3")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ object SunnyWeatherMultiJvmSpec extends MultiNodeConfig {
|
|||
val fourth = role("fourth")
|
||||
val fifth = role("fifth")
|
||||
|
||||
// Note that this test uses default configuration,
|
||||
// not MultiNodeClusterSpec.clusterConfig
|
||||
commonConfig(ConfigFactory.parseString("""
|
||||
akka.cluster {
|
||||
nr-of-deputy-nodes = 0
|
||||
# FIXME remove this (use default) when ticket #2239 has been fixed
|
||||
gossip-interval = 400 ms
|
||||
auto-join = off
|
||||
}
|
||||
akka.loglevel = INFO
|
||||
"""))
|
||||
|
|
@ -63,7 +65,7 @@ abstract class SunnyWeatherSpec
|
|||
})
|
||||
|
||||
for (n ← 1 to 30) {
|
||||
testConductor.enter("period-" + n)
|
||||
enterBarrier("period-" + n)
|
||||
unexpected.get must be(null)
|
||||
awaitUpConvergence(roles.size)
|
||||
assertLeaderIn(roles)
|
||||
|
|
@ -71,7 +73,7 @@ abstract class SunnyWeatherSpec
|
|||
1.seconds.sleep
|
||||
}
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ object TransitionMultiJvmSpec extends MultiNodeConfig {
|
|||
val fifth = role("fifth")
|
||||
|
||||
commonConfig(debugConfig(on = false).
|
||||
withFallback(ConfigFactory.parseString(
|
||||
"akka.cluster.periodic-tasks-initial-delay = 300 s # turn off all periodic tasks")).
|
||||
withFallback(ConfigFactory.parseString("akka.cluster.periodic-tasks-initial-delay = 300 s # turn off all periodic tasks")).
|
||||
withFallback(MultiNodeClusterSpec.clusterConfig))
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +60,7 @@ abstract class TransitionSpec
|
|||
}
|
||||
|
||||
def awaitSeen(addresses: Address*): Unit = awaitCond {
|
||||
seenLatestGossip.map(node(_).address) == addresses.toSet
|
||||
(seenLatestGossip map address) == addresses.toSet
|
||||
}
|
||||
|
||||
def awaitMembers(addresses: Address*): Unit = awaitCond {
|
||||
|
|
@ -69,12 +68,9 @@ abstract class TransitionSpec
|
|||
}
|
||||
|
||||
def awaitMemberStatus(address: Address, status: MemberStatus): Unit = awaitCond {
|
||||
memberStatus(address) == Up
|
||||
memberStatus(address) == status
|
||||
}
|
||||
|
||||
// implicit conversion from RoleName to Address
|
||||
implicit def role2Address(role: RoleName): Address = node(role).address
|
||||
|
||||
// DSL sugar for `role1 gossipTo role2`
|
||||
implicit def roleExtras(role: RoleName): RoleWrapper = new RoleWrapper(role)
|
||||
var gossipBarrierCounter = 0
|
||||
|
|
@ -83,18 +79,18 @@ abstract class TransitionSpec
|
|||
gossipBarrierCounter += 1
|
||||
runOn(toRole) {
|
||||
val g = cluster.latestGossip
|
||||
testConductor.enter("before-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
||||
awaitCond(cluster.latestGossip != g) // received gossip
|
||||
testConductor.enter("after-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
||||
}
|
||||
runOn(fromRole) {
|
||||
testConductor.enter("before-gossip-" + gossipBarrierCounter)
|
||||
cluster.gossipTo(node(toRole).address) // send gossip
|
||||
testConductor.enter("after-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
||||
cluster.gossipTo(toRole) // send gossip
|
||||
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
||||
}
|
||||
runOn(roles.filterNot(r ⇒ r == fromRole || r == toRole): _*) {
|
||||
testConductor.enter("before-gossip-" + gossipBarrierCounter)
|
||||
testConductor.enter("after-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -110,7 +106,7 @@ abstract class TransitionSpec
|
|||
cluster.leaderActions()
|
||||
cluster.status must be(Up)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"perform correct transitions when second joining first" taggedAs LongRunningTest in {
|
||||
|
|
@ -122,43 +118,28 @@ abstract class TransitionSpec
|
|||
awaitMembers(first, second)
|
||||
memberStatus(first) must be(Up)
|
||||
memberStatus(second) must be(Joining)
|
||||
seenLatestGossip must be(Set(first))
|
||||
cluster.convergence.isDefined must be(false)
|
||||
}
|
||||
testConductor.enter("second-joined")
|
||||
enterBarrier("second-joined")
|
||||
|
||||
first gossipTo second
|
||||
runOn(second) {
|
||||
members must be(Set(first, second))
|
||||
memberStatus(first) must be(Up)
|
||||
memberStatus(second) must be(Joining)
|
||||
// we got a conflicting version in second, and therefore not convergence in second
|
||||
seenLatestGossip must be(Set(second))
|
||||
cluster.convergence.isDefined must be(false)
|
||||
}
|
||||
|
||||
second gossipTo first
|
||||
runOn(first) {
|
||||
seenLatestGossip must be(Set(first, second))
|
||||
}
|
||||
|
||||
first gossipTo second
|
||||
runOn(second) {
|
||||
seenLatestGossip must be(Set(first, second))
|
||||
}
|
||||
|
||||
runOn(first, second) {
|
||||
memberStatus(first) must be(Up)
|
||||
memberStatus(second) must be(Joining)
|
||||
seenLatestGossip must be(Set(first, second))
|
||||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
testConductor.enter("convergence-joining-2")
|
||||
enterBarrier("convergence-joining-2")
|
||||
|
||||
runOn(leader(first, second)) {
|
||||
cluster.leaderActions()
|
||||
memberStatus(first) must be(Up)
|
||||
memberStatus(second) must be(Up)
|
||||
}
|
||||
testConductor.enter("leader-actions-2")
|
||||
enterBarrier("leader-actions-2")
|
||||
|
||||
leader(first, second) gossipTo nonLeader(first, second).head
|
||||
runOn(nonLeader(first, second).head) {
|
||||
|
|
@ -176,7 +157,7 @@ abstract class TransitionSpec
|
|||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
|
||||
"perform correct transitions when third joins second" taggedAs LongRunningTest in {
|
||||
|
|
@ -190,51 +171,29 @@ abstract class TransitionSpec
|
|||
memberStatus(third) must be(Joining)
|
||||
seenLatestGossip must be(Set(second))
|
||||
}
|
||||
testConductor.enter("third-joined-second")
|
||||
enterBarrier("third-joined-second")
|
||||
|
||||
second gossipTo first
|
||||
runOn(first) {
|
||||
members must be(Set(first, second, third))
|
||||
cluster.convergence.isDefined must be(false)
|
||||
memberStatus(third) must be(Joining)
|
||||
seenLatestGossip must be(Set(first, second))
|
||||
cluster.convergence.isDefined must be(false)
|
||||
}
|
||||
|
||||
first gossipTo third
|
||||
runOn(third) {
|
||||
members must be(Set(first, second, third))
|
||||
cluster.convergence.isDefined must be(false)
|
||||
memberStatus(third) must be(Joining)
|
||||
// conflicting version
|
||||
seenLatestGossip must be(Set(third))
|
||||
}
|
||||
|
||||
third gossipTo first
|
||||
third gossipTo second
|
||||
runOn(first, second) {
|
||||
seenLatestGossip must be(Set(myself, third))
|
||||
}
|
||||
|
||||
first gossipTo second
|
||||
runOn(second) {
|
||||
seenLatestGossip must be(Set(first, second, third))
|
||||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
runOn(first, third) {
|
||||
cluster.convergence.isDefined must be(false)
|
||||
}
|
||||
|
||||
second gossipTo first
|
||||
second gossipTo third
|
||||
runOn(first, second, third) {
|
||||
seenLatestGossip must be(Set(first, second, third))
|
||||
members must be(Set(first, second, third))
|
||||
memberStatus(first) must be(Up)
|
||||
memberStatus(second) must be(Up)
|
||||
memberStatus(third) must be(Joining)
|
||||
seenLatestGossip must be(Set(first, second, third))
|
||||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("convergence-joining-3")
|
||||
enterBarrier("convergence-joining-3")
|
||||
|
||||
runOn(leader(first, second, third)) {
|
||||
cluster.leaderActions()
|
||||
|
|
@ -242,7 +201,7 @@ abstract class TransitionSpec
|
|||
memberStatus(second) must be(Up)
|
||||
memberStatus(third) must be(Up)
|
||||
}
|
||||
testConductor.enter("leader-actions-3")
|
||||
enterBarrier("leader-actions-3")
|
||||
|
||||
// leader gossipTo first non-leader
|
||||
leader(first, second, third) gossipTo nonLeader(first, second, third).head
|
||||
|
|
@ -255,7 +214,7 @@ abstract class TransitionSpec
|
|||
// first non-leader gossipTo the other non-leader
|
||||
nonLeader(first, second, third).head gossipTo nonLeader(first, second, third).tail.head
|
||||
runOn(nonLeader(first, second, third).head) {
|
||||
cluster.gossipTo(node(nonLeader(first, second, third).tail.head).address)
|
||||
cluster.gossipTo(nonLeader(first, second, third).tail.head)
|
||||
}
|
||||
runOn(nonLeader(first, second, third).tail.head) {
|
||||
memberStatus(third) must be(Up)
|
||||
|
|
@ -281,27 +240,29 @@ abstract class TransitionSpec
|
|||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-3")
|
||||
enterBarrier("after-3")
|
||||
}
|
||||
|
||||
"startup a second separated cluster consisting of nodes fourth and fifth" taggedAs LongRunningTest in {
|
||||
runOn(fourth) {
|
||||
cluster.join(fifth)
|
||||
awaitMembers(fourth, fifth)
|
||||
cluster.gossipTo(fifth)
|
||||
awaitSeen(fourth, fifth)
|
||||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
runOn(fifth) {
|
||||
awaitMembers(fourth, fifth)
|
||||
cluster.gossipTo(fourth)
|
||||
awaitSeen(fourth, fifth)
|
||||
cluster.gossipTo(fourth)
|
||||
}
|
||||
testConductor.enter("fourth-joined")
|
||||
|
||||
fifth gossipTo fourth
|
||||
fourth gossipTo fifth
|
||||
|
||||
runOn(fourth, fifth) {
|
||||
memberStatus(fourth) must be(Joining)
|
||||
memberStatus(fifth) must be(Up)
|
||||
seenLatestGossip must be(Set(fourth, fifth))
|
||||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
testConductor.enter("fourth-joined-fifth")
|
||||
|
||||
testConductor.enter("after-4")
|
||||
enterBarrier("after-4")
|
||||
}
|
||||
|
||||
"perform correct transitions when second cluster (node fourth) joins first cluster (node third)" taggedAs LongRunningTest in {
|
||||
|
|
@ -313,7 +274,7 @@ abstract class TransitionSpec
|
|||
awaitMembers(first, second, third, fourth)
|
||||
seenLatestGossip must be(Set(third))
|
||||
}
|
||||
testConductor.enter("fourth-joined-third")
|
||||
enterBarrier("fourth-joined-third")
|
||||
|
||||
third gossipTo second
|
||||
runOn(second) {
|
||||
|
|
@ -365,7 +326,7 @@ abstract class TransitionSpec
|
|||
memberStatus(fifth) must be(Up)
|
||||
cluster.convergence.isDefined must be(true)
|
||||
|
||||
testConductor.enter("convergence-joining-3")
|
||||
enterBarrier("convergence-joining-3")
|
||||
|
||||
runOn(leader(roles: _*)) {
|
||||
cluster.leaderActions()
|
||||
|
|
@ -378,7 +339,7 @@ abstract class TransitionSpec
|
|||
x gossipTo y
|
||||
}
|
||||
|
||||
testConductor.enter("spread-5")
|
||||
enterBarrier("spread-5")
|
||||
|
||||
seenLatestGossip must be(roles.toSet)
|
||||
memberStatus(first) must be(Up)
|
||||
|
|
@ -388,7 +349,7 @@ abstract class TransitionSpec
|
|||
memberStatus(fifth) must be(Up)
|
||||
cluster.convergence.isDefined must be(true)
|
||||
|
||||
testConductor.enter("after-5")
|
||||
enterBarrier("after-5")
|
||||
}
|
||||
|
||||
"perform correct transitions when second becomes unavailble" taggedAs LongRunningTest in {
|
||||
|
|
@ -399,6 +360,8 @@ abstract class TransitionSpec
|
|||
seenLatestGossip must be(Set(fifth))
|
||||
}
|
||||
|
||||
enterBarrier("after-second-unavailble")
|
||||
|
||||
// spread the word
|
||||
val gossipRound = List(fifth, fourth, third, first, third, fourth, fifth)
|
||||
for (x :: y :: Nil ← gossipRound.sliding(2)) {
|
||||
|
|
@ -415,6 +378,8 @@ abstract class TransitionSpec
|
|||
awaitMemberStatus(second, Down)
|
||||
}
|
||||
|
||||
enterBarrier("after-second-down")
|
||||
|
||||
// spread the word
|
||||
val gossipRound2 = List(third, fourth, fifth, first, third, fourth, fifth)
|
||||
for (x :: y :: Nil ← gossipRound2.sliding(2)) {
|
||||
|
|
@ -428,7 +393,7 @@ abstract class TransitionSpec
|
|||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-6")
|
||||
enterBarrier("after-6")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ package akka.cluster
|
|||
|
||||
import akka.actor.Address
|
||||
import akka.testkit.{ LongRunningTest, AkkaSpec }
|
||||
import scala.collection.immutable.TreeMap
|
||||
import akka.util.duration._
|
||||
import akka.util.Duration
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class AccrualFailureDetectorSpec extends AkkaSpec("""
|
||||
|
|
@ -27,33 +30,72 @@ class AccrualFailureDetectorSpec extends AkkaSpec("""
|
|||
timeGenerator
|
||||
}
|
||||
|
||||
val defaultFakeTimeIntervals = Vector.fill(20)(1000L)
|
||||
def createFailureDetector(
|
||||
threshold: Double = 8.0,
|
||||
maxSampleSize: Int = 1000,
|
||||
minStdDeviation: Duration = 10.millis,
|
||||
acceptableLostDuration: Duration = Duration.Zero,
|
||||
firstHeartbeatEstimate: Duration = 1.second,
|
||||
clock: () ⇒ Long = fakeTimeGenerator(defaultFakeTimeIntervals)): AccrualFailureDetector =
|
||||
new AccrualFailureDetector(system,
|
||||
threshold,
|
||||
maxSampleSize,
|
||||
minStdDeviation,
|
||||
acceptableLostDuration,
|
||||
firstHeartbeatEstimate = firstHeartbeatEstimate,
|
||||
clock = clock)
|
||||
|
||||
"use good enough cumulative distribution function" in {
|
||||
val fd = createFailureDetector()
|
||||
fd.cumulativeDistributionFunction(0.0, 0, 1) must be(0.5 plusOrMinus (0.001))
|
||||
fd.cumulativeDistributionFunction(0.6, 0, 1) must be(0.7257 plusOrMinus (0.001))
|
||||
fd.cumulativeDistributionFunction(1.5, 0, 1) must be(0.9332 plusOrMinus (0.001))
|
||||
fd.cumulativeDistributionFunction(2.0, 0, 1) must be(0.97725 plusOrMinus (0.01))
|
||||
fd.cumulativeDistributionFunction(2.5, 0, 1) must be(0.9379 plusOrMinus (0.1))
|
||||
fd.cumulativeDistributionFunction(3.5, 0, 1) must be(0.99977 plusOrMinus (0.1))
|
||||
fd.cumulativeDistributionFunction(4.0, 0, 1) must be(0.99997 plusOrMinus (0.1))
|
||||
|
||||
for (x :: y :: Nil ← (0.0 to 4.0 by 0.1).toList.sliding(2)) {
|
||||
fd.cumulativeDistributionFunction(x, 0, 1) must be < (
|
||||
fd.cumulativeDistributionFunction(y, 0, 1))
|
||||
}
|
||||
|
||||
fd.cumulativeDistributionFunction(2.2, 2.0, 0.3) must be(0.7475 plusOrMinus (0.001))
|
||||
}
|
||||
|
||||
"return realistic phi values" in {
|
||||
val fd = createFailureDetector()
|
||||
val test = TreeMap(0 -> 0.0, 500 -> 0.1, 1000 -> 0.3, 1200 -> 1.6, 1400 -> 4.7, 1600 -> 10.8, 1700 -> 15.3)
|
||||
for ((timeDiff, expectedPhi) ← test) {
|
||||
fd.phi(timeDiff = timeDiff, mean = 1000.0, stdDeviation = 100.0) must be(expectedPhi plusOrMinus (0.1))
|
||||
}
|
||||
|
||||
// larger stdDeviation results => lower phi
|
||||
fd.phi(timeDiff = 1100, mean = 1000.0, stdDeviation = 500.0) must be < (
|
||||
fd.phi(timeDiff = 1100, mean = 1000.0, stdDeviation = 100.0))
|
||||
}
|
||||
|
||||
"return phi value of 0.0 on startup for each address, when no heartbeats" in {
|
||||
val fd = new AccrualFailureDetector(system)
|
||||
val fd = createFailureDetector()
|
||||
fd.phi(conn) must be(0.0)
|
||||
fd.phi(conn2) must be(0.0)
|
||||
}
|
||||
|
||||
"return phi based on guess when only one heartbeat" in {
|
||||
// 1 second ticks
|
||||
val timeInterval = Vector.fill(30)(1000L)
|
||||
val fd = new AccrualFailureDetector(system,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val timeInterval = List[Long](0, 1000, 1000, 1000, 1000)
|
||||
val fd = createFailureDetector(firstHeartbeatEstimate = 1.seconds,
|
||||
clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn)
|
||||
fd.phi(conn) must be > (0.0)
|
||||
// let time go
|
||||
for (n ← 2 to 8)
|
||||
fd.phi(conn) must be < (4.0)
|
||||
for (n ← 9 to 18)
|
||||
fd.phi(conn) must be < (8.0)
|
||||
|
||||
fd.phi(conn) must be > (8.0)
|
||||
fd.phi(conn) must be(0.3 plusOrMinus 0.2)
|
||||
fd.phi(conn) must be(4.5 plusOrMinus 0.3)
|
||||
fd.phi(conn) must be > (15.0)
|
||||
}
|
||||
|
||||
"return phi value using first interval after second heartbeat" in {
|
||||
val timeInterval = List[Long](0, 100, 100, 100)
|
||||
val fd = new AccrualFailureDetector(system,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val fd = createFailureDetector(clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn)
|
||||
fd.phi(conn) must be > (0.0)
|
||||
|
|
@ -63,8 +105,7 @@ class AccrualFailureDetectorSpec extends AkkaSpec("""
|
|||
|
||||
"mark node as available after a series of successful heartbeats" in {
|
||||
val timeInterval = List[Long](0, 1000, 100, 100)
|
||||
val fd = new AccrualFailureDetector(system,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val fd = createFailureDetector(clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
|
|
@ -75,8 +116,7 @@ class AccrualFailureDetectorSpec extends AkkaSpec("""
|
|||
|
||||
"mark node as dead after explicit removal of connection" in {
|
||||
val timeInterval = List[Long](0, 1000, 100, 100, 100)
|
||||
val fd = new AccrualFailureDetector(system,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val fd = createFailureDetector(clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
|
|
@ -89,8 +129,7 @@ class AccrualFailureDetectorSpec extends AkkaSpec("""
|
|||
|
||||
"mark node as available after explicit removal of connection and receiving heartbeat again" in {
|
||||
val timeInterval = List[Long](0, 1000, 100, 1100, 1100, 1100, 1100, 1100, 100)
|
||||
val fd = new AccrualFailureDetector(system,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val fd = createFailureDetector(clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn) //0
|
||||
|
||||
|
|
@ -112,40 +151,65 @@ class AccrualFailureDetectorSpec extends AkkaSpec("""
|
|||
}
|
||||
|
||||
"mark node as dead if heartbeat are missed" in {
|
||||
val timeInterval = List[Long](0, 1000, 100, 100, 5000)
|
||||
val timeInterval = List[Long](0, 1000, 100, 100, 7000)
|
||||
val ft = fakeTimeGenerator(timeInterval)
|
||||
val fd = new AccrualFailureDetector(system, threshold = 3,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val fd = createFailureDetector(threshold = 3, clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn) //0
|
||||
fd.heartbeat(conn) //1000
|
||||
fd.heartbeat(conn) //1100
|
||||
|
||||
fd.isAvailable(conn) must be(true) //1200
|
||||
fd.isAvailable(conn) must be(false) //6200
|
||||
fd.isAvailable(conn) must be(false) //8200
|
||||
}
|
||||
|
||||
"mark node as available if it starts heartbeat again after being marked dead due to detection of failure" in {
|
||||
val timeInterval = List[Long](0, 1000, 100, 1100, 5000, 100, 1000, 100, 100)
|
||||
val fd = new AccrualFailureDetector(system, threshold = 3,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val timeInterval = List[Long](0, 1000, 100, 1100, 7000, 100, 1000, 100, 100)
|
||||
val fd = createFailureDetector(threshold = 3, clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn) //0
|
||||
fd.heartbeat(conn) //1000
|
||||
fd.heartbeat(conn) //1100
|
||||
fd.isAvailable(conn) must be(true) //1200
|
||||
fd.isAvailable(conn) must be(false) //6200
|
||||
fd.heartbeat(conn) //6300
|
||||
fd.heartbeat(conn) //7300
|
||||
fd.heartbeat(conn) //7400
|
||||
fd.isAvailable(conn) must be(false) //8200
|
||||
fd.heartbeat(conn) //8300
|
||||
fd.heartbeat(conn) //9300
|
||||
fd.heartbeat(conn) //9400
|
||||
|
||||
fd.isAvailable(conn) must be(true) //7500
|
||||
fd.isAvailable(conn) must be(true) //9500
|
||||
}
|
||||
|
||||
"accept some configured missing heartbeats" in {
|
||||
val timeInterval = List[Long](0, 1000, 1000, 1000, 4000, 1000, 1000)
|
||||
val fd = createFailureDetector(acceptableLostDuration = 3.seconds, clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.isAvailable(conn) must be(true)
|
||||
fd.heartbeat(conn)
|
||||
fd.isAvailable(conn) must be(true)
|
||||
}
|
||||
|
||||
"fail after configured acceptable missing heartbeats" in {
|
||||
val timeInterval = List[Long](0, 1000, 1000, 1000, 1000, 1000, 500, 500, 5000)
|
||||
val fd = createFailureDetector(acceptableLostDuration = 3.seconds, clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.heartbeat(conn)
|
||||
fd.isAvailable(conn) must be(true)
|
||||
fd.heartbeat(conn)
|
||||
fd.isAvailable(conn) must be(false)
|
||||
}
|
||||
|
||||
"use maxSampleSize heartbeats" in {
|
||||
val timeInterval = List[Long](0, 100, 100, 100, 100, 600, 1000, 1000, 1000, 1000, 1000)
|
||||
val fd = new AccrualFailureDetector(system, maxSampleSize = 3,
|
||||
timeMachine = fakeTimeGenerator(timeInterval))
|
||||
val fd = createFailureDetector(maxSampleSize = 3, clock = fakeTimeGenerator(timeInterval))
|
||||
|
||||
// 100 ms interval
|
||||
fd.heartbeat(conn) //0
|
||||
|
|
@ -163,4 +227,33 @@ class AccrualFailureDetectorSpec extends AkkaSpec("""
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
"Statistics for heartbeats" must {
|
||||
|
||||
"calculate correct mean and variance" in {
|
||||
val samples = Seq(100, 200, 125, 340, 130)
|
||||
val stats = (HeartbeatHistory(maxSampleSize = 20) /: samples) { (stats, value) ⇒ stats :+ value }
|
||||
stats.mean must be(179.0 plusOrMinus 0.00001)
|
||||
stats.variance must be(7584.0 plusOrMinus 0.00001)
|
||||
}
|
||||
|
||||
"have 0.0 variance for one sample" in {
|
||||
(HeartbeatHistory(600) :+ 1000L).variance must be(0.0 plusOrMinus 0.00001)
|
||||
}
|
||||
|
||||
"be capped by the specified maxSampleSize" in {
|
||||
val history3 = HeartbeatHistory(maxSampleSize = 3) :+ 100 :+ 110 :+ 90
|
||||
history3.mean must be(100.0 plusOrMinus 0.00001)
|
||||
history3.variance must be(66.6666667 plusOrMinus 0.00001)
|
||||
|
||||
val history4 = history3 :+ 140
|
||||
history4.mean must be(113.333333 plusOrMinus 0.00001)
|
||||
history4.variance must be(422.222222 plusOrMinus 0.00001)
|
||||
|
||||
val history5 = history4 :+ 80
|
||||
history5.mean must be(103.333333 plusOrMinus 0.00001)
|
||||
history5.variance must be(688.88888889 plusOrMinus 0.00001)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,21 @@ class ClusterConfigSpec extends AkkaSpec {
|
|||
"be able to parse generic cluster config elements" in {
|
||||
val settings = new ClusterSettings(system.settings.config, system.name)
|
||||
import settings._
|
||||
FailureDetectorThreshold must be(8)
|
||||
FailureDetectorThreshold must be(8.0 plusOrMinus 0.0001)
|
||||
FailureDetectorMaxSampleSize must be(1000)
|
||||
FailureDetectorImplementationClass must be(None)
|
||||
NodeToJoin must be(None)
|
||||
FailureDetectorImplementationClass must be(classOf[AccrualFailureDetector].getName)
|
||||
FailureDetectorMinStdDeviation must be(100 millis)
|
||||
FailureDetectorAcceptableHeartbeatPause must be(3 seconds)
|
||||
SeedNodes must be(Seq.empty[String])
|
||||
SeedNodeTimeout must be(5 seconds)
|
||||
PeriodicTasksInitialDelay must be(1 seconds)
|
||||
GossipInterval must be(1 second)
|
||||
HeartbeatInterval must be(1 second)
|
||||
LeaderActionsInterval must be(1 second)
|
||||
UnreachableNodesReaperInterval must be(1 second)
|
||||
JoinTimeout must be(60 seconds)
|
||||
NrOfGossipDaemons must be(4)
|
||||
NrOfDeputyNodes must be(3)
|
||||
AutoJoin must be(true)
|
||||
AutoDown must be(true)
|
||||
SchedulerTickDuration must be(33 millis)
|
||||
SchedulerTicksPerWheel must be(512)
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import akka.actor.ExtendedActorSystem
|
|||
import akka.actor.Address
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import org.scalatest.BeforeAndAfter
|
||||
import akka.remote.RemoteActorRefProvider
|
||||
|
||||
object ClusterSpec {
|
||||
val config = """
|
||||
akka.cluster {
|
||||
auto-join = off
|
||||
auto-down = off
|
||||
nr-of-deputy-nodes = 3
|
||||
periodic-tasks-initial-delay = 120 seconds // turn off scheduled tasks
|
||||
}
|
||||
akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|
||||
|
|
@ -31,9 +32,23 @@ object ClusterSpec {
|
|||
class ClusterSpec extends AkkaSpec(ClusterSpec.config) with BeforeAndAfter {
|
||||
import ClusterSpec._
|
||||
|
||||
val selfAddress = system.asInstanceOf[ExtendedActorSystem].provider.asInstanceOf[RemoteActorRefProvider].transport.address
|
||||
val addresses = IndexedSeq(
|
||||
selfAddress,
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 1),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 2),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 3),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 4),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 5))
|
||||
|
||||
val deterministicRandom = new AtomicInteger
|
||||
|
||||
val cluster = new Cluster(system.asInstanceOf[ExtendedActorSystem], new FailureDetectorPuppet(system)) {
|
||||
val failureDetector = new FailureDetectorPuppet(system)
|
||||
|
||||
val cluster = new Cluster(system.asInstanceOf[ExtendedActorSystem], failureDetector) {
|
||||
|
||||
// 3 deputy nodes (addresses index 1, 2, 3)
|
||||
override def seedNodes = addresses.slice(1, 4)
|
||||
|
||||
override def selectRandomNode(addresses: IndexedSeq[Address]): Option[Address] = {
|
||||
if (addresses.isEmpty) None
|
||||
|
|
@ -48,14 +63,6 @@ class ClusterSpec extends AkkaSpec(ClusterSpec.config) with BeforeAndAfter {
|
|||
testActor ! GossipTo(address)
|
||||
}
|
||||
|
||||
@volatile
|
||||
var _gossipToUnreachableProbablity = 0.0
|
||||
|
||||
override def gossipToUnreachableProbablity(membersSize: Int, unreachableSize: Int): Double = {
|
||||
if (_gossipToUnreachableProbablity < 0.0) super.gossipToUnreachableProbablity(membersSize, unreachableSize)
|
||||
else _gossipToUnreachableProbablity
|
||||
}
|
||||
|
||||
@volatile
|
||||
var _gossipToDeputyProbablity = 0.0
|
||||
|
||||
|
|
@ -64,41 +71,28 @@ class ClusterSpec extends AkkaSpec(ClusterSpec.config) with BeforeAndAfter {
|
|||
else _gossipToDeputyProbablity
|
||||
}
|
||||
|
||||
@volatile
|
||||
var _unavailable: Set[Address] = Set.empty
|
||||
|
||||
override val failureDetector = new FailureDetectorPuppet(system) {
|
||||
override def isAvailable(connection: Address): Boolean = {
|
||||
if (_unavailable.contains(connection)) false
|
||||
else super.isAvailable(connection)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val selfAddress = cluster.self.address
|
||||
val addresses = IndexedSeq(
|
||||
selfAddress,
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 1),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 2),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 3),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 4),
|
||||
Address("akka", system.name, selfAddress.host.get, selfAddress.port.get + 5))
|
||||
|
||||
def memberStatus(address: Address): Option[MemberStatus] =
|
||||
cluster.latestGossip.members.collectFirst { case m if m.address == address ⇒ m.status }
|
||||
|
||||
before {
|
||||
cluster._gossipToUnreachableProbablity = 0.0
|
||||
cluster._gossipToDeputyProbablity = 0.0
|
||||
cluster._unavailable = Set.empty
|
||||
addresses foreach failureDetector.remove
|
||||
deterministicRandom.set(0)
|
||||
}
|
||||
|
||||
"A Cluster" must {
|
||||
|
||||
"initially be singleton cluster and reach convergence immediately" in {
|
||||
cluster.isSingletonCluster must be(true)
|
||||
"use the address of the remote transport" in {
|
||||
cluster.selfAddress must be(selfAddress)
|
||||
}
|
||||
|
||||
"initially become singleton cluster when joining itself and reach convergence" in {
|
||||
cluster.isSingletonCluster must be(false) // auto-join = off
|
||||
cluster.join(selfAddress)
|
||||
awaitCond(cluster.isSingletonCluster)
|
||||
cluster.self.address must be(selfAddress)
|
||||
cluster.latestGossip.members.map(_.address) must be(Set(selfAddress))
|
||||
memberStatus(selfAddress) must be(Some(MemberStatus.Joining))
|
||||
cluster.convergence.isDefined must be(true)
|
||||
|
|
@ -141,17 +135,6 @@ class ClusterSpec extends AkkaSpec(ClusterSpec.config) with BeforeAndAfter {
|
|||
expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
"use certain probability for gossiping to unreachable node depending on the number of unreachable and live nodes" in {
|
||||
cluster._gossipToUnreachableProbablity = -1.0 // use real impl
|
||||
cluster.gossipToUnreachableProbablity(10, 1) must be < (cluster.gossipToUnreachableProbablity(9, 1))
|
||||
cluster.gossipToUnreachableProbablity(10, 1) must be < (cluster.gossipToUnreachableProbablity(10, 2))
|
||||
cluster.gossipToUnreachableProbablity(10, 5) must be < (cluster.gossipToUnreachableProbablity(10, 9))
|
||||
cluster.gossipToUnreachableProbablity(0, 10) must be <= (1.0)
|
||||
cluster.gossipToUnreachableProbablity(1, 10) must be <= (1.0)
|
||||
cluster.gossipToUnreachableProbablity(10, 0) must be(0.0 plusOrMinus (0.0001))
|
||||
cluster.gossipToUnreachableProbablity(0, 0) must be(0.0 plusOrMinus (0.0001))
|
||||
}
|
||||
|
||||
"use certain probability for gossiping to deputy node depending on the number of unreachable and live nodes" in {
|
||||
cluster._gossipToDeputyProbablity = -1.0 // use real impl
|
||||
cluster.gossipToDeputyProbablity(10, 1, 2) must be < (cluster.gossipToDeputyProbablity(9, 1, 2))
|
||||
|
|
@ -169,7 +152,7 @@ class ClusterSpec extends AkkaSpec(ClusterSpec.config) with BeforeAndAfter {
|
|||
"gossip to duputy node" in {
|
||||
cluster._gossipToDeputyProbablity = 1.0 // always
|
||||
|
||||
// we have configured 2 deputy nodes
|
||||
// we have configured 3 deputy nodes (seedNodes)
|
||||
cluster.gossip() // 1 is deputy
|
||||
cluster.gossip() // 2 is deputy
|
||||
cluster.gossip() // 3 is deputy
|
||||
|
|
@ -186,27 +169,11 @@ class ClusterSpec extends AkkaSpec(ClusterSpec.config) with BeforeAndAfter {
|
|||
|
||||
}
|
||||
|
||||
"gossip to random unreachable node" in {
|
||||
val dead = Set(addresses(1))
|
||||
cluster._unavailable = dead
|
||||
cluster._gossipToUnreachableProbablity = 1.0 // always
|
||||
|
||||
cluster.reapUnreachableMembers()
|
||||
cluster.latestGossip.overview.unreachable.map(_.address) must be(dead)
|
||||
|
||||
cluster.gossip()
|
||||
|
||||
expectMsg(GossipTo(addresses(2))) // first available
|
||||
expectMsg(GossipTo(addresses(1))) // the unavailable
|
||||
|
||||
expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
"gossip to random deputy node if number of live nodes is less than number of deputy nodes" in {
|
||||
cluster._gossipToDeputyProbablity = -1.0 // real impl
|
||||
// 0 and 2 still alive
|
||||
val dead = Set(addresses(1), addresses(3), addresses(4), addresses(5))
|
||||
cluster._unavailable = dead
|
||||
dead foreach failureDetector.markNodeAsUnavailable
|
||||
|
||||
cluster.reapUnreachableMembers()
|
||||
cluster.latestGossip.overview.unreachable.map(_.address) must be(dead)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.cluster
|
||||
|
||||
import akka.actor.{ Address, AddressFromURIString }
|
||||
import java.net.InetSocketAddress
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.WordSpec
|
||||
import scala.collection.immutable.SortedSet
|
||||
import scala.util.Random
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class MemberOrderingSpec extends WordSpec with MustMatchers {
|
||||
import Member.ordering
|
||||
import Member.addressOrdering
|
||||
import MemberStatus._
|
||||
|
||||
"An Ordering[Member]" must {
|
||||
|
||||
"order non-exiting members by host:port" in {
|
||||
val members = SortedSet.empty[Member] +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1112"), Up) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1113"), Joining) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1111"), Up)
|
||||
|
||||
val seq = members.toSeq
|
||||
seq.size must equal(3)
|
||||
seq(0) must equal(Member(AddressFromURIString("akka://sys@darkstar:1111"), Up))
|
||||
seq(1) must equal(Member(AddressFromURIString("akka://sys@darkstar:1112"), Up))
|
||||
seq(2) must equal(Member(AddressFromURIString("akka://sys@darkstar:1113"), Joining))
|
||||
}
|
||||
|
||||
"order exiting members by last" in {
|
||||
val members = SortedSet.empty[Member] +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1113"), Up) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1111"), Joining)
|
||||
|
||||
val seq = members.toSeq
|
||||
seq.size must equal(3)
|
||||
seq(0) must equal(Member(AddressFromURIString("akka://sys@darkstar:1111"), Joining))
|
||||
seq(1) must equal(Member(AddressFromURIString("akka://sys@darkstar:1113"), Up))
|
||||
seq(2) must equal(Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting))
|
||||
}
|
||||
|
||||
"order multiple exiting members by last but internally by host:port" in {
|
||||
val members = SortedSet.empty[Member] +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1113"), Leaving) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1111"), Up) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1110"), Exiting)
|
||||
|
||||
val seq = members.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(Member(AddressFromURIString("akka://sys@darkstar:1111"), Up))
|
||||
seq(1) must equal(Member(AddressFromURIString("akka://sys@darkstar:1113"), Leaving))
|
||||
seq(2) must equal(Member(AddressFromURIString("akka://sys@darkstar:1110"), Exiting))
|
||||
seq(3) must equal(Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting))
|
||||
}
|
||||
|
||||
"be sorted by address correctly" in {
|
||||
import Member.ordering
|
||||
// sorting should be done on host and port, only
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
val m3 = Member(Address("cluster", "sys2", "host2", 8000), MemberStatus.Up)
|
||||
val m4 = Member(Address("cluster", "sys2", "host2", 9000), MemberStatus.Up)
|
||||
val m5 = Member(Address("cluster", "sys1", "host2", 10000), MemberStatus.Up)
|
||||
|
||||
val expected = IndexedSeq(m1, m2, m3, m4, m5)
|
||||
val shuffled = Random.shuffle(expected)
|
||||
shuffled.sorted must be(expected)
|
||||
(SortedSet.empty[Member] ++ shuffled).toIndexedSeq must be(expected)
|
||||
}
|
||||
|
||||
"have stable equals and hashCode" in {
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Joining)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m3 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
|
||||
m1 must be(m2)
|
||||
m1.hashCode must be(m2.hashCode)
|
||||
|
||||
m3 must not be (m2)
|
||||
m3 must not be (m1)
|
||||
}
|
||||
}
|
||||
|
||||
"An Ordering[Address]" must {
|
||||
|
||||
"order addresses by port" in {
|
||||
val addresses = SortedSet.empty[Address] +
|
||||
AddressFromURIString("akka://sys@darkstar:1112") +
|
||||
AddressFromURIString("akka://sys@darkstar:1113") +
|
||||
AddressFromURIString("akka://sys@darkstar:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar:1111")
|
||||
|
||||
val seq = addresses.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(AddressFromURIString("akka://sys@darkstar:1110"))
|
||||
seq(1) must equal(AddressFromURIString("akka://sys@darkstar:1111"))
|
||||
seq(2) must equal(AddressFromURIString("akka://sys@darkstar:1112"))
|
||||
seq(3) must equal(AddressFromURIString("akka://sys@darkstar:1113"))
|
||||
}
|
||||
|
||||
"order addresses by hostname" in {
|
||||
val addresses = SortedSet.empty[Address] +
|
||||
AddressFromURIString("akka://sys@darkstar2:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar1:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar3:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar0:1110")
|
||||
|
||||
val seq = addresses.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(AddressFromURIString("akka://sys@darkstar0:1110"))
|
||||
seq(1) must equal(AddressFromURIString("akka://sys@darkstar1:1110"))
|
||||
seq(2) must equal(AddressFromURIString("akka://sys@darkstar2:1110"))
|
||||
seq(3) must equal(AddressFromURIString("akka://sys@darkstar3:1110"))
|
||||
}
|
||||
|
||||
"order addresses by hostname and port" in {
|
||||
val addresses = SortedSet.empty[Address] +
|
||||
AddressFromURIString("akka://sys@darkstar2:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar0:1111") +
|
||||
AddressFromURIString("akka://sys@darkstar2:1111") +
|
||||
AddressFromURIString("akka://sys@darkstar0:1110")
|
||||
|
||||
val seq = addresses.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(AddressFromURIString("akka://sys@darkstar0:1110"))
|
||||
seq(1) must equal(AddressFromURIString("akka://sys@darkstar0:1111"))
|
||||
seq(2) must equal(AddressFromURIString("akka://sys@darkstar2:1110"))
|
||||
seq(3) must equal(AddressFromURIString("akka://sys@darkstar2:1111"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.cluster
|
||||
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.actor.Address
|
||||
import scala.util.Random
|
||||
import scala.collection.immutable.SortedSet
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class MemberSpec extends WordSpec with MustMatchers {
|
||||
|
||||
"Member" must {
|
||||
|
||||
"be sorted by address correctly" in {
|
||||
import Member.ordering
|
||||
// sorting should be done on host and port, only
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
val m3 = Member(Address("cluster", "sys2", "host2", 8000), MemberStatus.Up)
|
||||
val m4 = Member(Address("cluster", "sys2", "host2", 9000), MemberStatus.Up)
|
||||
val m5 = Member(Address("cluster", "sys1", "host2", 10000), MemberStatus.Up)
|
||||
|
||||
val expected = IndexedSeq(m1, m2, m3, m4, m5)
|
||||
val shuffled = Random.shuffle(expected)
|
||||
shuffled.sorted must be(expected)
|
||||
(SortedSet.empty[Member] ++ shuffled).toIndexedSeq must be(expected)
|
||||
}
|
||||
|
||||
"have stable equals and hashCode" in {
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Joining)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m3 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
|
||||
m1 must be(m2)
|
||||
m1.hashCode must be(m2.hashCode)
|
||||
|
||||
m3 must not be (m2)
|
||||
m3 must not be (m1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,7 @@
|
|||
Cluster Specification
|
||||
######################
|
||||
|
||||
.. note:: *This document describes the new clustering coming in Akka Coltrane and
|
||||
is not available in the latest stable release)*
|
||||
.. note:: *This document describes the new clustering coming in Akka Coltrane and is not available in the latest stable release)*
|
||||
|
||||
Intro
|
||||
=====
|
||||
|
|
@ -164,8 +163,8 @@ After gossip convergence a ``leader`` for the cluster can be determined. There i
|
|||
``leader`` election process, the ``leader`` can always be recognised deterministically
|
||||
by any node whenever there is gossip convergence. The ``leader`` is simply the first
|
||||
node in sorted order that is able to take the leadership role, where the only
|
||||
allowed member states for a ``leader`` are ``up`` or ``leaving`` (see below for more
|
||||
information about member states).
|
||||
allowed member states for a ``leader`` are ``up``, ``leaving`` or ``exiting`` (see
|
||||
below for more information about member states).
|
||||
|
||||
The role of the ``leader`` is to shift members in and out of the cluster, changing
|
||||
``joining`` members to the ``up`` state or ``exiting`` members to the
|
||||
|
|
@ -184,14 +183,20 @@ according to the Failure Detector is considered unreachable. This means setting
|
|||
the unreachable node status to ``down`` automatically.
|
||||
|
||||
|
||||
Seed Nodes
|
||||
^^^^^^^^^^
|
||||
|
||||
The seed nodes are configured contact points for inital join of the cluster.
|
||||
When a new node is started started it sends a message to all seed nodes and
|
||||
then sends join command to the one that answers first.
|
||||
|
||||
It is possible to turn off automatic join.
|
||||
|
||||
Deputy Nodes
|
||||
^^^^^^^^^^^^
|
||||
|
||||
After gossip convergence a set of ``deputy`` nodes for the cluster can be
|
||||
determined. As with the ``leader``, there is no ``deputy`` election process,
|
||||
the deputies can always be recognised deterministically by any node whenever there
|
||||
is gossip convergence. The list of ``deputy`` nodes is simply the N - 1 number
|
||||
of nodes (e.g. starting with the first node after the ``leader``) in sorted order.
|
||||
The deputy nodes are the live members of the configured seed nodes.
|
||||
It is preferred to use deputy nodes in different racks/data centers.
|
||||
|
||||
The nodes defined as ``deputy`` nodes are just regular member nodes whose only
|
||||
"special role" is to help breaking logical partitions as seen in the gossip
|
||||
|
|
@ -214,7 +219,7 @@ nodes involved in a gossip exchange.
|
|||
|
||||
Periodically, the default is every 1 second, each node chooses another random
|
||||
node to initiate a round of gossip with. The choice of node is random but can
|
||||
also include extra gossiping for unreachable nodes, ``deputy`` nodes, and nodes with
|
||||
also include extra gossiping for ``deputy`` nodes, and nodes with
|
||||
either newer or older state versions.
|
||||
|
||||
The gossip overview contains the current state version for all nodes and also a
|
||||
|
|
@ -229,14 +234,11 @@ During each round of gossip exchange the following process is used:
|
|||
|
||||
1. Gossip to random live node (if any)
|
||||
|
||||
2. Gossip to random unreachable node with certain probability depending on the
|
||||
number of unreachable and live nodes
|
||||
|
||||
3. If the node gossiped to at (1) was not a ``deputy`` node, or the number of live
|
||||
2. If the node gossiped to at (1) was not a ``deputy`` node, or the number of live
|
||||
nodes is less than number of ``deputy`` nodes, gossip to random ``deputy`` node with
|
||||
certain probability depending on number of unreachable, ``deputy``, and live nodes.
|
||||
|
||||
4. Gossip to random node with newer or older state information, based on the
|
||||
3. Gossip to random node with newer or older state information, based on the
|
||||
current gossip overview, with some probability (?)
|
||||
|
||||
The gossiper only sends the gossip overview to the chosen node. The recipient of
|
||||
|
|
@ -302,10 +304,6 @@ handoff has completed then the node will change to the ``exiting`` state. Once
|
|||
all nodes have seen the exiting state (convergence) the ``leader`` will remove the
|
||||
node from the cluster, marking it as ``removed``.
|
||||
|
||||
A node can also be removed forcefully by moving it directly to the ``removed``
|
||||
state using the ``remove`` action. The cluster will rebalance based on the new
|
||||
cluster membership.
|
||||
|
||||
If a node is unreachable then gossip convergence is not possible and therefore
|
||||
any ``leader`` actions are also not possible (for instance, allowing a node to
|
||||
become a part of the cluster, or changing actor distribution). To be able to
|
||||
|
|
@ -314,11 +312,12 @@ unreachable node is experiencing only transient difficulties then it can be
|
|||
explicitly marked as ``down`` using the ``down`` user action. When this node
|
||||
comes back up and begins gossiping it will automatically go through the joining
|
||||
process again. If the unreachable node will be permanently down then it can be
|
||||
removed from the cluster directly with the ``remove`` user action. The cluster
|
||||
can also *auto-down* a node using the accrual failure detector.
|
||||
removed from the cluster directly by shutting the actor system down or killing it
|
||||
through an external ``SIGKILL`` signal, invocation of ``System.exit(status)`` or
|
||||
similar. The cluster can, through the leader, also *auto-down* a node.
|
||||
|
||||
This means that nodes can join and leave the cluster at any point in time,
|
||||
e.g. provide cluster elasticity.
|
||||
This means that nodes can join and leave the cluster at any point in time, i.e.
|
||||
provide cluster elasticity.
|
||||
|
||||
|
||||
State Diagram for the Member States
|
||||
|
|
@ -339,12 +338,12 @@ Member States
|
|||
- **leaving** / **exiting**
|
||||
states during graceful removal
|
||||
|
||||
- **removed**
|
||||
tombstone state (no longer a member)
|
||||
|
||||
- **down**
|
||||
marked as down/offline/unreachable
|
||||
|
||||
- **removed**
|
||||
tombstone state (no longer a member)
|
||||
|
||||
|
||||
User Actions
|
||||
^^^^^^^^^^^^
|
||||
|
|
@ -359,9 +358,6 @@ User Actions
|
|||
- **down**
|
||||
mark a node as temporarily down
|
||||
|
||||
- **remove**
|
||||
remove a node from the cluster immediately
|
||||
|
||||
|
||||
Leader Actions
|
||||
^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ At-most-once
|
|||
|
||||
Actual transports may provide stronger semantics,
|
||||
but at-most-once is the semantics you should expect.
|
||||
The alternatives would be once-and-only-once, which is extremely costly,
|
||||
The alternatives would be once-and-only-once, which is extremely costly,
|
||||
or at-least-once which essentially requires idempotency of message processing,
|
||||
which is a user-level concern.
|
||||
|
||||
Ordering is preserved on a per-sender basis
|
||||
-------------------------------------------
|
||||
|
||||
Actor ``A1` sends messages ``M1``, ``M2``, ``M3`` to ``A2``
|
||||
Actor ``A1`` sends messages ``M1``, ``M2``, ``M3`` to ``A2``
|
||||
Actor ``A3`` sends messages ``M4``, ``M5``, ``M6`` to ``A2``
|
||||
|
||||
This means that:
|
||||
|
|
@ -66,4 +66,4 @@ This means that:
|
|||
5) ``A2`` can see messages from ``A1`` interleaved with messages from ``A3``
|
||||
6) Since there is no guaranteed delivery, none, some or all of the messages may arrive to ``A2``
|
||||
|
||||
.. _Erlang documentation: http://www.erlang.org/faq/academic.html
|
||||
.. _Erlang documentation: http://www.erlang.org/faq/academic.html
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@ import com.typesafe.config.Config;
|
|||
|
||||
//#imports-prio-mailbox
|
||||
|
||||
//#imports-custom
|
||||
import akka.dispatch.Envelope;
|
||||
import akka.dispatch.MessageQueue;
|
||||
import akka.dispatch.MailboxType;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
//#imports-custom
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
|
@ -136,4 +145,32 @@ public class DispatcherDocTestBase {
|
|||
}
|
||||
}
|
||||
//#prio-mailbox
|
||||
|
||||
//#mailbox-implementation-example
|
||||
class MyUnboundedMailbox implements MailboxType {
|
||||
|
||||
// This constructor signature must exist, it will be called by Akka
|
||||
public MyUnboundedMailbox(ActorSystem.Settings settings, Config config) {
|
||||
// put your initialization code here
|
||||
}
|
||||
|
||||
// The create method is called to create the MessageQueue
|
||||
public MessageQueue create(Option<ActorRef> owner, Option<ActorSystem> system) {
|
||||
return new MessageQueue() {
|
||||
private final Queue<Envelope> queue = new ConcurrentLinkedQueue<Envelope>();
|
||||
|
||||
// these must be implemented; queue used as example
|
||||
public void enqueue(ActorRef receiver, Envelope handle) { queue.offer(handle); }
|
||||
public Envelope dequeue() { return queue.poll(); }
|
||||
public int numberOfMessages() { return queue.size(); }
|
||||
public boolean hasMessages() { return !queue.isEmpty(); }
|
||||
public void cleanUp(ActorRef owner, MessageQueue deadLetters) {
|
||||
for (Envelope handle: queue) {
|
||||
deadLetters.enqueue(owner, handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//#mailbox-implementation-example
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,3 +183,46 @@ And then an example on how you would use it:
|
|||
the configuration which describes the dispatcher using this mailbox type; the
|
||||
mailbox type will be instantiated once for each dispatcher using it.
|
||||
|
||||
Creating your own Mailbox type
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
An example is worth a thousand quacks:
|
||||
|
||||
.. includecode:: code/docs/dispatcher/DispatcherDocTestBase.java#imports-custom
|
||||
|
||||
.. includecode:: code/docs/dispatcher/DispatcherDocTestBase.java#mailbox-implementation-example
|
||||
|
||||
And then you just specify the FQCN of your MailboxType as the value of the "mailbox-type" in the dispatcher configuration.
|
||||
|
||||
.. note::
|
||||
|
||||
Make sure to include a constructor which takes
|
||||
``akka.actor.ActorSystem.Settings`` and ``com.typesafe.config.Config``
|
||||
arguments, as this constructor is invoked reflectively to construct your
|
||||
mailbox type. The config passed in as second argument is that section from
|
||||
the configuration which describes the dispatcher using this mailbox type; the
|
||||
mailbox type will be instantiated once for each dispatcher using it.
|
||||
|
||||
|
||||
Special Semantics of ``system.actorOf``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In order to make ``system.actorOf`` both synchronous and non-blocking while
|
||||
keeping the return type :class:`ActorRef` (and the semantics that the returned
|
||||
ref is fully functional), special handling takes place for this case. Behind
|
||||
the scenes, a hollow kind of actor reference is constructed, which is sent to
|
||||
the system’s guardian actor who actually creates the actor and its context and
|
||||
puts those inside the reference. Until that has happened, messages sent to the
|
||||
:class:`ActorRef` will be queued locally, and only upon swapping the real
|
||||
filling in will they be transferred into the real mailbox. Thus,
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
final Props props = ...
|
||||
// this actor uses MyCustomMailbox, which is assumed to be a singleton
|
||||
system.actorOf(props.withDispatcher("myCustomMailbox").tell("bang");
|
||||
assert(MyCustomMailbox.getInstance().getLastEnqueued().equals("bang"));
|
||||
|
||||
will probably fail; you will have to allow for some time to pass and retry the
|
||||
check à la :meth:`TestKit.awaitCond`.
|
||||
|
||||
|
|
|
|||
|
|
@ -82,13 +82,6 @@ that is used in log messages and for identifying actors. The name must not be em
|
|||
or start with ``$``. If the given name is already in use by another child to the
|
||||
same parent actor an `InvalidActorNameException` is thrown.
|
||||
|
||||
.. warning::
|
||||
|
||||
Creating top-level actors with ``system.actorOf`` is a blocking operation,
|
||||
hence it may dead-lock due to starvation if the default dispatcher is
|
||||
overloaded. To avoid problems, do not call this method from within actors or
|
||||
futures which run on the default dispatcher.
|
||||
|
||||
Actors are automatically started asynchronously when created.
|
||||
When you create the ``UntypedActor`` then it will automatically call the ``preStart``
|
||||
callback method on the ``UntypedActor`` class. This is an excellent place to
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import akka.actor.Props
|
|||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.testkit.AkkaSpec
|
||||
import akka.actor.Actor
|
||||
import akka.actor.{ Actor, ExtendedActorSystem }
|
||||
|
||||
class MyActor extends Actor {
|
||||
def receive = {
|
||||
|
|
@ -56,20 +56,20 @@ import akka.util.duration._
|
|||
class MyMailboxType(systemSettings: ActorSystem.Settings, config: Config)
|
||||
extends MailboxType {
|
||||
|
||||
override def create(owner: Option[ActorContext]): MessageQueue = owner match {
|
||||
case Some(o) ⇒ new MyMessageQueue(o)
|
||||
override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue = owner zip system headOption match {
|
||||
case Some((o, s: ExtendedActorSystem)) ⇒ new MyMessageQueue(o, s)
|
||||
case None ⇒ throw new IllegalArgumentException(
|
||||
"requires an owner (i.e. does not work with BalancingDispatcher)")
|
||||
}
|
||||
}
|
||||
|
||||
class MyMessageQueue(_owner: ActorContext)
|
||||
extends DurableMessageQueue(_owner) with DurableMessageSerialization {
|
||||
class MyMessageQueue(_owner: ActorRef, _system: ExtendedActorSystem)
|
||||
extends DurableMessageQueue(_owner, _system) with DurableMessageSerialization {
|
||||
|
||||
val storage = new QueueStorage
|
||||
// A real-world implmentation would use configuration to set the last
|
||||
// three parameters below
|
||||
val breaker = CircuitBreaker(_owner.system.scheduler, 5, 30.seconds, 1.minute)
|
||||
val breaker = CircuitBreaker(system.scheduler, 5, 30.seconds, 1.minute)
|
||||
|
||||
def enqueue(receiver: ActorRef, envelope: Envelope): Unit = breaker.withSyncCircuitBreaker {
|
||||
val data: Array[Byte] = serialize(envelope)
|
||||
|
|
@ -91,7 +91,7 @@ class MyMessageQueue(_owner: ActorContext)
|
|||
* but the purpose of a durable mailbox is to continue
|
||||
* with the same message queue when the actor is started again.
|
||||
*/
|
||||
def cleanUp(owner: ActorContext, deadLetters: MessageQueue): Unit = ()
|
||||
def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = ()
|
||||
|
||||
}
|
||||
//#custom-mailbox
|
||||
|
|
|
|||
|
|
@ -196,4 +196,4 @@ Licenses for Dependency Libraries
|
|||
---------------------------------
|
||||
|
||||
Each dependency and its license can be seen in the project build file (the comment on the side of each dependency):
|
||||
`<https://github.com/akka/akka/blob/master/project/build/AkkaProject.scala#L127>`_
|
||||
`<https://github.com/akka/akka/blob/master/project/AkkaBuild.scala#L497>`_
|
||||
|
|
|
|||
|
|
@ -76,13 +76,6 @@ that is used in log messages and for identifying actors. The name must not be em
|
|||
or start with ``$``. If the given name is already in use by another child to the
|
||||
same parent actor an `InvalidActorNameException` is thrown.
|
||||
|
||||
.. warning::
|
||||
|
||||
Creating top-level actors with ``system.actorOf`` is a blocking operation,
|
||||
hence it may dead-lock due to starvation if the default dispatcher is
|
||||
overloaded. To avoid problems, do not call this method from within actors or
|
||||
futures which run on the default dispatcher.
|
||||
|
||||
Actors are automatically started asynchronously when created.
|
||||
When you create the ``Actor`` then it will automatically call the ``preStart``
|
||||
callback method on the ``Actor`` trait. This is an excellent place to
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@ object DispatcherDocSpec {
|
|||
}
|
||||
|
||||
//#mailbox-implementation-example
|
||||
case class MyUnboundedMailbox() extends akka.dispatch.MailboxType {
|
||||
import akka.actor.ActorContext
|
||||
class MyUnboundedMailbox extends akka.dispatch.MailboxType {
|
||||
import akka.actor.{ ActorRef, ActorSystem }
|
||||
import com.typesafe.config.Config
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import akka.dispatch.{
|
||||
|
|
@ -149,12 +149,12 @@ object DispatcherDocSpec {
|
|||
def this(settings: ActorSystem.Settings, config: Config) = this()
|
||||
|
||||
// The create method is called to create the MessageQueue
|
||||
final override def create(owner: Option[ActorContext]): MessageQueue =
|
||||
final override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue =
|
||||
new QueueBasedMessageQueue with UnboundedMessageQueueSemantics {
|
||||
final val queue = new ConcurrentLinkedQueue[Envelope]()
|
||||
}
|
||||
//#mailbox-implementation-example
|
||||
}
|
||||
//#mailbox-implementation-example
|
||||
}
|
||||
|
||||
class DispatcherDocSpec extends AkkaSpec(DispatcherDocSpec.config) {
|
||||
|
|
|
|||
|
|
@ -198,3 +198,25 @@ And then you just specify the FQCN of your MailboxType as the value of the "mail
|
|||
the configuration which describes the dispatcher using this mailbox type; the
|
||||
mailbox type will be instantiated once for each dispatcher using it.
|
||||
|
||||
Special Semantics of ``system.actorOf``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In order to make ``system.actorOf`` both synchronous and non-blocking while
|
||||
keeping the return type :class:`ActorRef` (and the semantics that the returned
|
||||
ref is fully functional), special handling takes place for this case. Behind
|
||||
the scenes, a hollow kind of actor reference is constructed, which is sent to
|
||||
the system’s guardian actor who actually creates the actor and its context and
|
||||
puts those inside the reference. Until that has happened, messages sent to the
|
||||
:class:`ActorRef` will be queued locally, and only upon swapping the real
|
||||
filling in will they be transferred into the real mailbox. Thus,
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
val props: Props = ...
|
||||
// this actor uses MyCustomMailbox, which is assumed to be a singleton
|
||||
system.actorOf(props.withDispatcher("myCustomMailbox")) ! "bang"
|
||||
assert(MyCustomMailbox.instance.getLastEnqueuedMessage == "bang")
|
||||
|
||||
will probably fail; you will have to allow for some time to pass and retry the
|
||||
check à la :meth:`TestKit.awaitCond`.
|
||||
|
||||
|
|
|
|||
|
|
@ -13,50 +13,50 @@ akka {
|
|||
file-based {
|
||||
# directory below which this queue resides
|
||||
directory-path = "./_mb"
|
||||
|
||||
|
||||
# attempting to add an item after the queue reaches this size (in items) will fail.
|
||||
max-items = 2147483647
|
||||
|
||||
|
||||
# attempting to add an item after the queue reaches this size (in bytes) will fail.
|
||||
max-size = 2147483647 bytes
|
||||
|
||||
|
||||
# attempting to add an item larger than this size (in bytes) will fail.
|
||||
max-item-size = 2147483647 bytes
|
||||
|
||||
|
||||
# maximum expiration time for this queue (seconds).
|
||||
max-age = 0s
|
||||
|
||||
|
||||
# maximum journal size before the journal should be rotated.
|
||||
max-journal-size = 16 MiB
|
||||
|
||||
|
||||
# maximum size of a queue before it drops into read-behind mode.
|
||||
max-memory-size = 128 MiB
|
||||
|
||||
|
||||
# maximum overflow (multiplier) of a journal file before we re-create it.
|
||||
max-journal-overflow = 10
|
||||
|
||||
|
||||
# absolute maximum size of a journal file until we rebuild it, no matter what.
|
||||
max-journal-size-absolute = 9223372036854775807 bytes
|
||||
|
||||
|
||||
# whether to drop older items (instead of newer) when the queue is full
|
||||
discard-old-when-full = on
|
||||
|
||||
discard-old-when-full = on
|
||||
|
||||
# whether to keep a journal file at all
|
||||
keep-journal = on
|
||||
|
||||
keep-journal = on
|
||||
|
||||
# whether to sync the journal after each transaction
|
||||
sync-journal = off
|
||||
|
||||
# circuit breaker configuration
|
||||
circuit-breaker {
|
||||
# maximum number of failures before opening breaker
|
||||
max-failures = 3
|
||||
# maximum number of failures before opening breaker
|
||||
max-failures = 3
|
||||
|
||||
# duration of time beyond which a call is assumed to be timed out and considered a failure
|
||||
call-timeout = 3 seconds
|
||||
# duration of time beyond which a call is assumed to be timed out and considered a failure
|
||||
call-timeout = 3 seconds
|
||||
|
||||
# duration of time to wait until attempting to reset the breaker during which all calls fail-fast
|
||||
reset-timeout = 30 seconds
|
||||
# duration of time to wait until attempting to reset the breaker during which all calls fail-fast
|
||||
reset-timeout = 30 seconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,26 +13,28 @@ import akka.actor.ActorSystem
|
|||
import akka.dispatch._
|
||||
import akka.util.{ Duration, NonFatal }
|
||||
import akka.pattern.{ CircuitBreakerOpenException, CircuitBreaker }
|
||||
import akka.actor.ExtendedActorSystem
|
||||
|
||||
class FileBasedMailboxType(systemSettings: ActorSystem.Settings, config: Config) extends MailboxType {
|
||||
private val settings = new FileBasedMailboxSettings(systemSettings, config)
|
||||
override def create(owner: Option[ActorContext]): MessageQueue = owner match {
|
||||
case Some(o) ⇒ new FileBasedMessageQueue(o, settings)
|
||||
case None ⇒ throw new ConfigurationException("creating a durable mailbox requires an owner (i.e. does not work with BalancingDispatcher)")
|
||||
override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue = owner zip system headOption match {
|
||||
case Some((o, s: ExtendedActorSystem)) ⇒ new FileBasedMessageQueue(o, s, settings)
|
||||
case None ⇒ throw new ConfigurationException("creating a durable mailbox requires an owner (i.e. does not work with BalancingDispatcher)")
|
||||
}
|
||||
}
|
||||
|
||||
class FileBasedMessageQueue(_owner: ActorContext, val settings: FileBasedMailboxSettings) extends DurableMessageQueue(_owner) with DurableMessageSerialization {
|
||||
class FileBasedMessageQueue(_owner: ActorRef, _system: ExtendedActorSystem, val settings: FileBasedMailboxSettings)
|
||||
extends DurableMessageQueue(_owner, _system) with DurableMessageSerialization {
|
||||
// TODO Is it reasonable for all FileBasedMailboxes to have their own logger?
|
||||
private val log = Logging(system, "FileBasedMessageQueue")
|
||||
|
||||
val breaker = CircuitBreaker(_owner.system.scheduler, settings.CircuitBreakerMaxFailures, settings.CircuitBreakerCallTimeout, settings.CircuitBreakerResetTimeout)
|
||||
val breaker = CircuitBreaker(system.scheduler, settings.CircuitBreakerMaxFailures, settings.CircuitBreakerCallTimeout, settings.CircuitBreakerResetTimeout)
|
||||
|
||||
private val queue = try {
|
||||
(new java.io.File(settings.QueuePath)) match {
|
||||
case dir if dir.exists && !dir.isDirectory ⇒ throw new IllegalStateException("Path already occupied by non-directory " + dir)
|
||||
case dir if !dir.exists ⇒ if (!dir.mkdirs() && !dir.isDirectory) throw new IllegalStateException("Creation of directory failed " + dir)
|
||||
case _ ⇒ //All good
|
||||
case _ ⇒ // All good
|
||||
}
|
||||
val queue = new filequeue.PersistentQueue(settings.QueuePath, name, settings, log)
|
||||
queue.setup // replays journal
|
||||
|
|
@ -79,5 +81,5 @@ class FileBasedMessageQueue(_owner: ActorContext, val settings: FileBasedMailbox
|
|||
case NonFatal(_) ⇒ false
|
||||
}
|
||||
|
||||
def cleanUp(owner: ActorContext, deadLetters: MessageQueue): Unit = ()
|
||||
def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = ()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,20 +16,20 @@ class FileBasedMailboxSettings(val systemSettings: ActorSystem.Settings, val use
|
|||
val config = initialize
|
||||
import config._
|
||||
|
||||
val QueuePath: String = getString("directory-path")
|
||||
val MaxItems: Int = getInt("max-items")
|
||||
val MaxSize: Long = getBytes("max-size")
|
||||
val MaxItemSize: Long = getBytes("max-item-size")
|
||||
val MaxAge: Duration = Duration(getMilliseconds("max-age"), MILLISECONDS)
|
||||
val MaxJournalSize: Long = getBytes("max-journal-size")
|
||||
val MaxMemorySize: Long = getBytes("max-memory-size")
|
||||
val MaxJournalOverflow: Int = getInt("max-journal-overflow")
|
||||
val MaxJournalSizeAbsolute: Long = getBytes("max-journal-size-absolute")
|
||||
val DiscardOldWhenFull: Boolean = getBoolean("discard-old-when-full")
|
||||
val KeepJournal: Boolean = getBoolean("keep-journal")
|
||||
val SyncJournal: Boolean = getBoolean("sync-journal")
|
||||
final val QueuePath: String = getString("directory-path")
|
||||
final val MaxItems: Int = getInt("max-items")
|
||||
final val MaxSize: Long = getBytes("max-size")
|
||||
final val MaxItemSize: Long = getBytes("max-item-size")
|
||||
final val MaxAge: Duration = Duration(getMilliseconds("max-age"), MILLISECONDS)
|
||||
final val MaxJournalSize: Long = getBytes("max-journal-size")
|
||||
final val MaxMemorySize: Long = getBytes("max-memory-size")
|
||||
final val MaxJournalOverflow: Int = getInt("max-journal-overflow")
|
||||
final val MaxJournalSizeAbsolute: Long = getBytes("max-journal-size-absolute")
|
||||
final val DiscardOldWhenFull: Boolean = getBoolean("discard-old-when-full")
|
||||
final val KeepJournal: Boolean = getBoolean("keep-journal")
|
||||
final val SyncJournal: Boolean = getBoolean("sync-journal")
|
||||
|
||||
val CircuitBreakerMaxFailures = getInt("circuit-breaker.max-failures")
|
||||
val CircuitBreakerCallTimeout = Duration.fromNanos(getNanoseconds("circuit-breaker.call-timeout"))
|
||||
val CircuitBreakerResetTimeout = Duration.fromNanos(getNanoseconds("circuit-breaker.reset-timeout"))
|
||||
}
|
||||
final val CircuitBreakerMaxFailures = getInt("circuit-breaker.max-failures")
|
||||
final val CircuitBreakerCallTimeout = Duration.fromNanos(getNanoseconds("circuit-breaker.call-timeout"))
|
||||
final val CircuitBreakerResetTimeout = Duration.fromNanos(getNanoseconds("circuit-breaker.reset-timeout"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,44 +68,44 @@ class PersistentQueue(persistencePath: String, val name: String, val settings: F
|
|||
def overlay[T](base: ⇒ T) = new OverlaySetting(base)
|
||||
|
||||
// attempting to add an item after the queue reaches this size (in items) will fail.
|
||||
val maxItems = overlay(PersistentQueue.maxItems)
|
||||
final val maxItems = overlay(PersistentQueue.maxItems)
|
||||
|
||||
// attempting to add an item after the queue reaches this size (in bytes) will fail.
|
||||
val maxSize = overlay(PersistentQueue.maxSize)
|
||||
final val maxSize = overlay(PersistentQueue.maxSize)
|
||||
|
||||
// attempting to add an item larger than this size (in bytes) will fail.
|
||||
val maxItemSize = overlay(PersistentQueue.maxItemSize)
|
||||
final val maxItemSize = overlay(PersistentQueue.maxItemSize)
|
||||
|
||||
// maximum expiration time for this queue (seconds).
|
||||
val maxAge = overlay(PersistentQueue.maxAge)
|
||||
final val maxAge = overlay(PersistentQueue.maxAge)
|
||||
|
||||
// maximum journal size before the journal should be rotated.
|
||||
val maxJournalSize = overlay(PersistentQueue.maxJournalSize)
|
||||
final val maxJournalSize = overlay(PersistentQueue.maxJournalSize)
|
||||
|
||||
// maximum size of a queue before it drops into read-behind mode.
|
||||
val maxMemorySize = overlay(PersistentQueue.maxMemorySize)
|
||||
final val maxMemorySize = overlay(PersistentQueue.maxMemorySize)
|
||||
|
||||
// maximum overflow (multiplier) of a journal file before we re-create it.
|
||||
val maxJournalOverflow = overlay(PersistentQueue.maxJournalOverflow)
|
||||
final val maxJournalOverflow = overlay(PersistentQueue.maxJournalOverflow)
|
||||
|
||||
// absolute maximum size of a journal file until we rebuild it, no matter what.
|
||||
val maxJournalSizeAbsolute = overlay(PersistentQueue.maxJournalSizeAbsolute)
|
||||
final val maxJournalSizeAbsolute = overlay(PersistentQueue.maxJournalSizeAbsolute)
|
||||
|
||||
// whether to drop older items (instead of newer) when the queue is full
|
||||
val discardOldWhenFull = overlay(PersistentQueue.discardOldWhenFull)
|
||||
final val discardOldWhenFull = overlay(PersistentQueue.discardOldWhenFull)
|
||||
|
||||
// whether to keep a journal file at all
|
||||
val keepJournal = overlay(PersistentQueue.keepJournal)
|
||||
final val keepJournal = overlay(PersistentQueue.keepJournal)
|
||||
|
||||
// whether to sync the journal after each transaction
|
||||
val syncJournal = overlay(PersistentQueue.syncJournal)
|
||||
final val syncJournal = overlay(PersistentQueue.syncJournal)
|
||||
|
||||
// (optional) move expired items over to this queue
|
||||
val expiredQueue = overlay(PersistentQueue.expiredQueue)
|
||||
final val expiredQueue = overlay(PersistentQueue.expiredQueue)
|
||||
|
||||
private var journal = new Journal(new File(persistencePath, name).getCanonicalPath, syncJournal(), log)
|
||||
|
||||
// track tentative removals
|
||||
// track tentative remofinal vals
|
||||
private var xidCounter: Int = 0
|
||||
private val openTransactions = new mutable.HashMap[Int, QItem]
|
||||
def openTransactionCount = openTransactions.size
|
||||
|
|
|
|||
|
|
@ -13,11 +13,10 @@ private[akka] object DurableExecutableMailboxConfig {
|
|||
val Name = "[\\.\\/\\$\\s]".r
|
||||
}
|
||||
|
||||
abstract class DurableMessageQueue(val owner: ActorContext) extends MessageQueue {
|
||||
abstract class DurableMessageQueue(val owner: ActorRef, val system: ExtendedActorSystem) extends MessageQueue {
|
||||
import DurableExecutableMailboxConfig._
|
||||
|
||||
def system: ExtendedActorSystem = owner.system.asInstanceOf[ExtendedActorSystem]
|
||||
def ownerPath: ActorPath = owner.self.path
|
||||
def ownerPath: ActorPath = owner.path
|
||||
val ownerPathString: String = ownerPath.elements.mkString("/")
|
||||
val name: String = "mailbox_" + Name.replaceAllIn(ownerPathString, "_")
|
||||
|
||||
|
|
@ -42,7 +41,7 @@ trait DurableMessageSerialization { this: DurableMessageQueue ⇒
|
|||
val message = MessageSerializer.serialize(system, durableMessage.message.asInstanceOf[AnyRef])
|
||||
val builder = RemoteMessageProtocol.newBuilder
|
||||
.setMessage(message)
|
||||
.setRecipient(serializeActorRef(owner.self))
|
||||
.setRecipient(serializeActorRef(owner))
|
||||
.setSender(serializeActorRef(durableMessage.sender))
|
||||
|
||||
builder.build.toByteArray
|
||||
|
|
@ -60,7 +59,7 @@ trait DurableMessageSerialization { this: DurableMessageQueue ⇒
|
|||
val message = MessageSerializer.deserialize(system, durableMessage.getMessage)
|
||||
val sender = deserializeActorRef(durableMessage.getSender)
|
||||
|
||||
Envelope(message, sender)(system)
|
||||
Envelope(message, sender, system)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -69,11 +68,15 @@ trait DurableMessageSerialization { this: DurableMessageQueue ⇒
|
|||
* Conventional organization of durable mailbox settings:
|
||||
*
|
||||
* {{{
|
||||
* my-durable-dispatcher {
|
||||
* mailbox-type = "my.durable.mailbox"
|
||||
* my-durable-mailbox {
|
||||
* setting1 = 1
|
||||
* setting2 = 2
|
||||
* akka {
|
||||
* actor {
|
||||
* my-durable-dispatcher {
|
||||
* mailbox-type = "my.durable.mailbox"
|
||||
* my-durable-mailbox {
|
||||
* setting1 = 1
|
||||
* setting2 = 2
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
|
|
|
|||
|
|
@ -3,25 +3,21 @@
|
|||
*/
|
||||
package akka.actor.mailbox
|
||||
|
||||
import DurableMailboxSpecActorFactory.AccumulatorActor
|
||||
import DurableMailboxSpecActorFactory.MailboxTestActor
|
||||
import akka.actor.Actor
|
||||
import akka.actor.ActorRef
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor.LocalActorRef
|
||||
import akka.actor.Props
|
||||
import akka.actor.actorRef2Scala
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
import org.scalatest.{ WordSpec, BeforeAndAfterAll }
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
import com.typesafe.config.{ ConfigFactory, Config }
|
||||
|
||||
import DurableMailboxSpecActorFactory.{ MailboxTestActor, AccumulatorActor }
|
||||
import akka.actor.{ RepointableRef, Props, ActorSystem, ActorRefWithCell, ActorRef, ActorCell, Actor }
|
||||
import akka.dispatch.Mailbox
|
||||
import akka.testkit.TestKit
|
||||
import akka.util.duration.intToDurationInt
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeoutException
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import scala.annotation.tailrec
|
||||
|
||||
object DurableMailboxSpecActorFactory {
|
||||
|
||||
|
|
@ -115,9 +111,15 @@ abstract class DurableMailboxSpec(system: ActorSystem, val backendName: String)
|
|||
if (!result.contains(words)) throw new Exception("stream did not contain '" + words + "':\n" + result)
|
||||
}
|
||||
|
||||
def createMailboxTestActor(props: Props = Props[MailboxTestActor], id: String = ""): ActorRef = id match {
|
||||
case null | "" ⇒ system.actorOf(props.withDispatcher(backendName + "-dispatcher"))
|
||||
case some ⇒ system.actorOf(props.withDispatcher(backendName + "-dispatcher"), some)
|
||||
def createMailboxTestActor(props: Props = Props[MailboxTestActor], id: String = ""): ActorRef = {
|
||||
val ref = id match {
|
||||
case null | "" ⇒ system.actorOf(props.withDispatcher(backendName + "-dispatcher"))
|
||||
case some ⇒ system.actorOf(props.withDispatcher(backendName + "-dispatcher"), some)
|
||||
}
|
||||
awaitCond(ref match {
|
||||
case r: RepointableRef ⇒ r.isStarted
|
||||
}, 1 second, 10 millis)
|
||||
ref
|
||||
}
|
||||
|
||||
private def isDurableMailbox(m: Mailbox): Boolean =
|
||||
|
|
@ -127,9 +129,11 @@ abstract class DurableMailboxSpec(system: ActorSystem, val backendName: String)
|
|||
|
||||
"get a new, unique, durable mailbox" in {
|
||||
val a1, a2 = createMailboxTestActor()
|
||||
isDurableMailbox(a1.asInstanceOf[LocalActorRef].underlying.mailbox) must be(true)
|
||||
isDurableMailbox(a2.asInstanceOf[LocalActorRef].underlying.mailbox) must be(true)
|
||||
(a1.asInstanceOf[LocalActorRef].underlying.mailbox ne a2.asInstanceOf[LocalActorRef].underlying.mailbox) must be(true)
|
||||
val mb1 = a1.asInstanceOf[ActorRefWithCell].underlying.asInstanceOf[ActorCell].mailbox
|
||||
val mb2 = a2.asInstanceOf[ActorRefWithCell].underlying.asInstanceOf[ActorCell].mailbox
|
||||
isDurableMailbox(mb1) must be(true)
|
||||
isDurableMailbox(mb2) must be(true)
|
||||
(mb1 ne mb2) must be(true)
|
||||
}
|
||||
|
||||
"deliver messages at most once" in {
|
||||
|
|
@ -148,7 +152,7 @@ abstract class DurableMailboxSpec(system: ActorSystem, val backendName: String)
|
|||
"support having multiple actors at the same time" in {
|
||||
val actors = Vector.fill(3)(createMailboxTestActor(Props[AccumulatorActor]))
|
||||
|
||||
actors foreach { a ⇒ isDurableMailbox(a.asInstanceOf[LocalActorRef].underlying.mailbox) must be(true) }
|
||||
actors foreach { a ⇒ isDurableMailbox(a.asInstanceOf[ActorRefWithCell].underlying.asInstanceOf[ActorCell].mailbox) must be(true) }
|
||||
|
||||
val msgs = 1 to 3
|
||||
|
||||
|
|
|
|||
29
akka-kernel/src/main/dist/bin/akka-cluster
vendored
29
akka-kernel/src/main/dist/bin/akka-cluster
vendored
|
|
@ -63,20 +63,6 @@ case "$2" in
|
|||
$JMX_CLIENT $HOST akka:type=Cluster leave=$ACTOR_SYSTEM_URL
|
||||
;;
|
||||
|
||||
remove)
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Usage: $SELF <node-hostname:jmx-port> remove <actor-system-url-to-join>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensureNodeIsRunningAndAvailable
|
||||
shift
|
||||
|
||||
ACTOR_SYSTEM_URL=$2
|
||||
echo "Scheduling $ACTOR_SYSTEM_URL to REMOVE"
|
||||
$JMX_CLIENT $HOST akka:type=Cluster remove=$ACTOR_SYSTEM_URL
|
||||
;;
|
||||
|
||||
down)
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Usage: $SELF <node-hostname:jmx-port> down <actor-system-url-to-join>"
|
||||
|
|
@ -169,19 +155,32 @@ case "$2" in
|
|||
$JMX_CLIENT $HOST akka:type=Cluster Available
|
||||
;;
|
||||
|
||||
is-running)
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $SELF <node-hostname:jmx-port> is-running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensureNodeIsRunningAndAvailable
|
||||
shift
|
||||
|
||||
echo "Checking if member node on $HOST is AVAILABLE"
|
||||
$JMX_CLIENT $HOST akka:type=Cluster Running
|
||||
;;
|
||||
|
||||
*)
|
||||
printf "Usage: bin/$SELF <node-hostname:jmx-port> <command> ...\n"
|
||||
printf "\n"
|
||||
printf "Supported commands are:\n"
|
||||
printf "%26s - %s\n" "join <actor-system-url>" "Sends request a JOIN node with the specified URL"
|
||||
printf "%26s - %s\n" "leave <actor-system-url>" "Sends a request for node with URL to LEAVE the cluster"
|
||||
printf "%26s - %s\n" "remove <actor-system-url>" "Sends a request for node with URL to be instantly REMOVED from the cluster"
|
||||
printf "%26s - %s\n" "down <actor-system-url>" "Sends a request for marking node with URL as DOWN"
|
||||
printf "%26s - %s\n" member-status "Asks the member node for its current status"
|
||||
printf "%26s - %s\n" cluster-status "Asks the cluster for its current status (member ring, unavailable nodes, meta data etc.)"
|
||||
printf "%26s - %s\n" leader "Asks the cluster who the current leader is"
|
||||
printf "%26s - %s\n" is-singleton "Checks if the cluster is a singleton cluster (single node cluster)"
|
||||
printf "%26s - %s\n" is-available "Checks if the member node is available"
|
||||
printf "%26s - %s\n" is-running "Checks if the member node is running"
|
||||
printf "%26s - %s\n" has-convergence "Checks if there is a cluster convergence"
|
||||
printf "Where the <actor-system-url> should be on the format of 'akka://actor-system-name@hostname:port'\n"
|
||||
printf "\n"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,81 @@ public final class TestConductorProtocol {
|
|||
public static void registerAllExtensions(
|
||||
com.google.protobuf.ExtensionRegistry registry) {
|
||||
}
|
||||
public enum BarrierOp
|
||||
implements com.google.protobuf.ProtocolMessageEnum {
|
||||
Enter(0, 1),
|
||||
Fail(1, 2),
|
||||
Succeeded(2, 3),
|
||||
Failed(3, 4),
|
||||
;
|
||||
|
||||
public static final int Enter_VALUE = 1;
|
||||
public static final int Fail_VALUE = 2;
|
||||
public static final int Succeeded_VALUE = 3;
|
||||
public static final int Failed_VALUE = 4;
|
||||
|
||||
|
||||
public final int getNumber() { return value; }
|
||||
|
||||
public static BarrierOp valueOf(int value) {
|
||||
switch (value) {
|
||||
case 1: return Enter;
|
||||
case 2: return Fail;
|
||||
case 3: return Succeeded;
|
||||
case 4: return Failed;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static com.google.protobuf.Internal.EnumLiteMap<BarrierOp>
|
||||
internalGetValueMap() {
|
||||
return internalValueMap;
|
||||
}
|
||||
private static com.google.protobuf.Internal.EnumLiteMap<BarrierOp>
|
||||
internalValueMap =
|
||||
new com.google.protobuf.Internal.EnumLiteMap<BarrierOp>() {
|
||||
public BarrierOp findValueByNumber(int number) {
|
||||
return BarrierOp.valueOf(number);
|
||||
}
|
||||
};
|
||||
|
||||
public final com.google.protobuf.Descriptors.EnumValueDescriptor
|
||||
getValueDescriptor() {
|
||||
return getDescriptor().getValues().get(index);
|
||||
}
|
||||
public final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptorForType() {
|
||||
return getDescriptor();
|
||||
}
|
||||
public static final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptor() {
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(0);
|
||||
}
|
||||
|
||||
private static final BarrierOp[] VALUES = {
|
||||
Enter, Fail, Succeeded, Failed,
|
||||
};
|
||||
|
||||
public static BarrierOp valueOf(
|
||||
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
|
||||
if (desc.getType() != getDescriptor()) {
|
||||
throw new java.lang.IllegalArgumentException(
|
||||
"EnumValueDescriptor is not for this type.");
|
||||
}
|
||||
return VALUES[desc.getIndex()];
|
||||
}
|
||||
|
||||
private final int index;
|
||||
private final int value;
|
||||
|
||||
private BarrierOp(int index, int value) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(enum_scope:BarrierOp)
|
||||
}
|
||||
|
||||
public enum FailType
|
||||
implements com.google.protobuf.ProtocolMessageEnum {
|
||||
Throttle(0, 1),
|
||||
|
|
@ -56,7 +131,7 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
public static final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptor() {
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(0);
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(1);
|
||||
}
|
||||
|
||||
private static final FailType[] VALUES = {
|
||||
|
|
@ -128,7 +203,7 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
public static final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptor() {
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(1);
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(2);
|
||||
}
|
||||
|
||||
private static final Direction[] VALUES = {
|
||||
|
|
@ -1699,9 +1774,13 @@ public final class TestConductorProtocol {
|
|||
boolean hasName();
|
||||
String getName();
|
||||
|
||||
// optional bool status = 2;
|
||||
boolean hasStatus();
|
||||
boolean getStatus();
|
||||
// required .BarrierOp op = 2;
|
||||
boolean hasOp();
|
||||
akka.remote.testconductor.TestConductorProtocol.BarrierOp getOp();
|
||||
|
||||
// optional int64 timeout = 3;
|
||||
boolean hasTimeout();
|
||||
long getTimeout();
|
||||
}
|
||||
public static final class EnterBarrier extends
|
||||
com.google.protobuf.GeneratedMessage
|
||||
|
|
@ -1764,19 +1843,30 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
// optional bool status = 2;
|
||||
public static final int STATUS_FIELD_NUMBER = 2;
|
||||
private boolean status_;
|
||||
public boolean hasStatus() {
|
||||
// required .BarrierOp op = 2;
|
||||
public static final int OP_FIELD_NUMBER = 2;
|
||||
private akka.remote.testconductor.TestConductorProtocol.BarrierOp op_;
|
||||
public boolean hasOp() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public boolean getStatus() {
|
||||
return status_;
|
||||
public akka.remote.testconductor.TestConductorProtocol.BarrierOp getOp() {
|
||||
return op_;
|
||||
}
|
||||
|
||||
// optional int64 timeout = 3;
|
||||
public static final int TIMEOUT_FIELD_NUMBER = 3;
|
||||
private long timeout_;
|
||||
public boolean hasTimeout() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public long getTimeout() {
|
||||
return timeout_;
|
||||
}
|
||||
|
||||
private void initFields() {
|
||||
name_ = "";
|
||||
status_ = false;
|
||||
op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
timeout_ = 0L;
|
||||
}
|
||||
private byte memoizedIsInitialized = -1;
|
||||
public final boolean isInitialized() {
|
||||
|
|
@ -1787,6 +1877,10 @@ public final class TestConductorProtocol {
|
|||
memoizedIsInitialized = 0;
|
||||
return false;
|
||||
}
|
||||
if (!hasOp()) {
|
||||
memoizedIsInitialized = 0;
|
||||
return false;
|
||||
}
|
||||
memoizedIsInitialized = 1;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1798,7 +1892,10 @@ public final class TestConductorProtocol {
|
|||
output.writeBytes(1, getNameBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
output.writeBool(2, status_);
|
||||
output.writeEnum(2, op_.getNumber());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
output.writeInt64(3, timeout_);
|
||||
}
|
||||
getUnknownFields().writeTo(output);
|
||||
}
|
||||
|
|
@ -1815,7 +1912,11 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBoolSize(2, status_);
|
||||
.computeEnumSize(2, op_.getNumber());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeInt64Size(3, timeout_);
|
||||
}
|
||||
size += getUnknownFields().getSerializedSize();
|
||||
memoizedSerializedSize = size;
|
||||
|
|
@ -1943,8 +2044,10 @@ public final class TestConductorProtocol {
|
|||
super.clear();
|
||||
name_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
status_ = false;
|
||||
op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
timeout_ = 0L;
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -1990,7 +2093,11 @@ public final class TestConductorProtocol {
|
|||
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
to_bitField0_ |= 0x00000002;
|
||||
}
|
||||
result.status_ = status_;
|
||||
result.op_ = op_;
|
||||
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
to_bitField0_ |= 0x00000004;
|
||||
}
|
||||
result.timeout_ = timeout_;
|
||||
result.bitField0_ = to_bitField0_;
|
||||
onBuilt();
|
||||
return result;
|
||||
|
|
@ -2010,8 +2117,11 @@ public final class TestConductorProtocol {
|
|||
if (other.hasName()) {
|
||||
setName(other.getName());
|
||||
}
|
||||
if (other.hasStatus()) {
|
||||
setStatus(other.getStatus());
|
||||
if (other.hasOp()) {
|
||||
setOp(other.getOp());
|
||||
}
|
||||
if (other.hasTimeout()) {
|
||||
setTimeout(other.getTimeout());
|
||||
}
|
||||
this.mergeUnknownFields(other.getUnknownFields());
|
||||
return this;
|
||||
|
|
@ -2022,6 +2132,10 @@ public final class TestConductorProtocol {
|
|||
|
||||
return false;
|
||||
}
|
||||
if (!hasOp()) {
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2054,8 +2168,19 @@ public final class TestConductorProtocol {
|
|||
break;
|
||||
}
|
||||
case 16: {
|
||||
bitField0_ |= 0x00000002;
|
||||
status_ = input.readBool();
|
||||
int rawValue = input.readEnum();
|
||||
akka.remote.testconductor.TestConductorProtocol.BarrierOp value = akka.remote.testconductor.TestConductorProtocol.BarrierOp.valueOf(rawValue);
|
||||
if (value == null) {
|
||||
unknownFields.mergeVarintField(2, rawValue);
|
||||
} else {
|
||||
bitField0_ |= 0x00000002;
|
||||
op_ = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
bitField0_ |= 0x00000004;
|
||||
timeout_ = input.readInt64();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -2100,23 +2225,47 @@ public final class TestConductorProtocol {
|
|||
onChanged();
|
||||
}
|
||||
|
||||
// optional bool status = 2;
|
||||
private boolean status_ ;
|
||||
public boolean hasStatus() {
|
||||
// required .BarrierOp op = 2;
|
||||
private akka.remote.testconductor.TestConductorProtocol.BarrierOp op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
public boolean hasOp() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public boolean getStatus() {
|
||||
return status_;
|
||||
public akka.remote.testconductor.TestConductorProtocol.BarrierOp getOp() {
|
||||
return op_;
|
||||
}
|
||||
public Builder setStatus(boolean value) {
|
||||
public Builder setOp(akka.remote.testconductor.TestConductorProtocol.BarrierOp value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000002;
|
||||
status_ = value;
|
||||
op_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearStatus() {
|
||||
public Builder clearOp() {
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
status_ = false;
|
||||
op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional int64 timeout = 3;
|
||||
private long timeout_ ;
|
||||
public boolean hasTimeout() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public long getTimeout() {
|
||||
return timeout_;
|
||||
}
|
||||
public Builder setTimeout(long value) {
|
||||
bitField0_ |= 0x00000004;
|
||||
timeout_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearTimeout() {
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
timeout_ = 0L;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
|
@ -4056,19 +4205,21 @@ public final class TestConductorProtocol {
|
|||
"\0132\r.EnterBarrier\022\037\n\007failure\030\003 \001(\0132\016.Inje" +
|
||||
"ctFailure\022\014\n\004done\030\004 \001(\t\022\035\n\004addr\030\005 \001(\0132\017." +
|
||||
"AddressRequest\"0\n\005Hello\022\014\n\004name\030\001 \002(\t\022\031\n" +
|
||||
"\007address\030\002 \002(\0132\010.Address\",\n\014EnterBarrier" +
|
||||
"\022\014\n\004name\030\001 \002(\t\022\016\n\006status\030\002 \001(\010\"6\n\016Addres" +
|
||||
"sRequest\022\014\n\004node\030\001 \002(\t\022\026\n\004addr\030\002 \001(\0132\010.A" +
|
||||
"ddress\"G\n\007Address\022\020\n\010protocol\030\001 \002(\t\022\016\n\006s" +
|
||||
"ystem\030\002 \002(\t\022\014\n\004host\030\003 \002(\t\022\014\n\004port\030\004 \002(\005\"",
|
||||
"\212\001\n\rInjectFailure\022\032\n\007failure\030\001 \002(\0162\t.Fai" +
|
||||
"lType\022\035\n\tdirection\030\002 \001(\0162\n.Direction\022\031\n\007" +
|
||||
"address\030\003 \001(\0132\010.Address\022\020\n\010rateMBit\030\006 \001(" +
|
||||
"\002\022\021\n\texitValue\030\007 \001(\005*A\n\010FailType\022\014\n\010Thro" +
|
||||
"ttle\020\001\022\016\n\nDisconnect\020\002\022\t\n\005Abort\020\003\022\014\n\010Shu" +
|
||||
"tdown\020\004*,\n\tDirection\022\010\n\004Send\020\001\022\013\n\007Receiv" +
|
||||
"e\020\002\022\010\n\004Both\020\003B\035\n\031akka.remote.testconduct" +
|
||||
"orH\001"
|
||||
"\007address\030\002 \002(\0132\010.Address\"E\n\014EnterBarrier" +
|
||||
"\022\014\n\004name\030\001 \002(\t\022\026\n\002op\030\002 \002(\0162\n.BarrierOp\022\017" +
|
||||
"\n\007timeout\030\003 \001(\003\"6\n\016AddressRequest\022\014\n\004nod" +
|
||||
"e\030\001 \002(\t\022\026\n\004addr\030\002 \001(\0132\010.Address\"G\n\007Addre" +
|
||||
"ss\022\020\n\010protocol\030\001 \002(\t\022\016\n\006system\030\002 \002(\t\022\014\n\004",
|
||||
"host\030\003 \002(\t\022\014\n\004port\030\004 \002(\005\"\212\001\n\rInjectFailu" +
|
||||
"re\022\032\n\007failure\030\001 \002(\0162\t.FailType\022\035\n\tdirect" +
|
||||
"ion\030\002 \001(\0162\n.Direction\022\031\n\007address\030\003 \001(\0132\010" +
|
||||
".Address\022\020\n\010rateMBit\030\006 \001(\002\022\021\n\texitValue\030" +
|
||||
"\007 \001(\005*;\n\tBarrierOp\022\t\n\005Enter\020\001\022\010\n\004Fail\020\002\022" +
|
||||
"\r\n\tSucceeded\020\003\022\n\n\006Failed\020\004*A\n\010FailType\022\014" +
|
||||
"\n\010Throttle\020\001\022\016\n\nDisconnect\020\002\022\t\n\005Abort\020\003\022" +
|
||||
"\014\n\010Shutdown\020\004*,\n\tDirection\022\010\n\004Send\020\001\022\013\n\007" +
|
||||
"Receive\020\002\022\010\n\004Both\020\003B\035\n\031akka.remote.testc" +
|
||||
"onductorH\001"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
|
|
@ -4096,7 +4247,7 @@ public final class TestConductorProtocol {
|
|||
internal_static_EnterBarrier_fieldAccessorTable = new
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||
internal_static_EnterBarrier_descriptor,
|
||||
new java.lang.String[] { "Name", "Status", },
|
||||
new java.lang.String[] { "Name", "Op", "Timeout", },
|
||||
akka.remote.testconductor.TestConductorProtocol.EnterBarrier.class,
|
||||
akka.remote.testconductor.TestConductorProtocol.EnterBarrier.Builder.class);
|
||||
internal_static_AddressRequest_descriptor =
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ option optimize_for = SPEED;
|
|||
|
||||
/******************************************
|
||||
Compile with:
|
||||
cd ./akka-remote/src/main/protocol
|
||||
cd ./akka-remote-tests/src/main/protocol
|
||||
protoc TestConductorProtocol.proto --java_out ../java
|
||||
cd ../../../..
|
||||
./scripts/fix-protobuf.sh
|
||||
*******************************************/
|
||||
|
||||
message Wrapper {
|
||||
|
|
@ -24,9 +26,17 @@ message Hello {
|
|||
required Address address = 2;
|
||||
}
|
||||
|
||||
enum BarrierOp {
|
||||
Enter = 1;
|
||||
Fail = 2;
|
||||
Succeeded = 3;
|
||||
Failed = 4;
|
||||
}
|
||||
|
||||
message EnterBarrier {
|
||||
required string name = 1;
|
||||
optional bool status = 2;
|
||||
required BarrierOp op = 2;
|
||||
optional int64 timeout = 3;
|
||||
}
|
||||
|
||||
message AddressRequest {
|
||||
|
|
@ -47,11 +57,13 @@ enum FailType {
|
|||
Abort = 3;
|
||||
Shutdown = 4;
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
Send = 1;
|
||||
Receive = 2;
|
||||
Both = 3;
|
||||
}
|
||||
|
||||
message InjectFailure {
|
||||
required FailType failure = 1;
|
||||
optional Direction direction = 2;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import RemoteConnection.getAddrString
|
|||
import TestConductorProtocol._
|
||||
import org.jboss.netty.channel.{ Channel, SimpleChannelUpstreamHandler, ChannelHandlerContext, ChannelStateEvent, MessageEvent }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.util.Timeout
|
||||
import akka.util.Duration
|
||||
import akka.util.duration._
|
||||
import akka.pattern.ask
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
|
|
@ -26,6 +24,7 @@ import akka.actor.OneForOneStrategy
|
|||
import akka.actor.SupervisorStrategy
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import akka.actor.Status
|
||||
import akka.util.{ Deadline, Timeout, Duration }
|
||||
|
||||
sealed trait Direction {
|
||||
def includes(other: Direction): Boolean
|
||||
|
|
@ -283,6 +282,8 @@ private[akka] class ServerFSM(val controller: ActorRef, val channel: Channel) ex
|
|||
import akka.actor.FSM._
|
||||
import Controller._
|
||||
|
||||
var roleName: RoleName = null
|
||||
|
||||
startWith(Initial, None)
|
||||
|
||||
whenUnhandled {
|
||||
|
|
@ -293,12 +294,15 @@ private[akka] class ServerFSM(val controller: ActorRef, val channel: Channel) ex
|
|||
}
|
||||
|
||||
onTermination {
|
||||
case _ ⇒ controller ! ClientDisconnected
|
||||
case _ ⇒
|
||||
controller ! ClientDisconnected(roleName)
|
||||
channel.close()
|
||||
}
|
||||
|
||||
when(Initial, stateTimeout = 10 seconds) {
|
||||
case Event(Hello(name, addr), _) ⇒
|
||||
controller ! NodeInfo(RoleName(name), addr, self)
|
||||
roleName = RoleName(name)
|
||||
controller ! NodeInfo(roleName, addr, self)
|
||||
goto(Ready)
|
||||
case Event(x: NetworkOp, _) ⇒
|
||||
log.warning("client {} sent no Hello in first message (instead {}), disconnecting", getAddrString(channel), x)
|
||||
|
|
@ -335,10 +339,6 @@ private[akka] class ServerFSM(val controller: ActorRef, val channel: Channel) ex
|
|||
}
|
||||
|
||||
initialize
|
||||
|
||||
onTermination {
|
||||
case _ ⇒ channel.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -376,7 +376,8 @@ private[akka] class Controller(private var initialParticipants: Int, controllerP
|
|||
* BarrierTimeouts in the players).
|
||||
*/
|
||||
override def supervisorStrategy = OneForOneStrategy() {
|
||||
case BarrierTimeout(data) ⇒ SupervisorStrategy.Resume
|
||||
case BarrierTimeout(data) ⇒ failBarrier(data)
|
||||
case FailedBarrier(data) ⇒ failBarrier(data)
|
||||
case BarrierEmpty(data, msg) ⇒ SupervisorStrategy.Resume
|
||||
case WrongBarrier(name, client, data) ⇒ client ! ToClient(BarrierResult(name, false)); failBarrier(data)
|
||||
case ClientLost(data, node) ⇒ failBarrier(data)
|
||||
|
|
@ -426,6 +427,7 @@ private[akka] class Controller(private var initialParticipants: Int, controllerP
|
|||
case op: ServerOp ⇒
|
||||
op match {
|
||||
case _: EnterBarrier ⇒ barrier forward op
|
||||
case _: FailBarrier ⇒ barrier forward op
|
||||
case GetAddress(node) ⇒
|
||||
if (nodes contains node) sender ! ToClient(AddressReply(node, nodes(node).addr))
|
||||
else addrInterest += node -> ((addrInterest get node getOrElse Set()) + sender)
|
||||
|
|
@ -463,7 +465,7 @@ private[akka] object BarrierCoordinator {
|
|||
|
||||
case class RemoveClient(name: RoleName)
|
||||
|
||||
case class Data(clients: Set[Controller.NodeInfo], barrier: String, arrived: List[ActorRef])
|
||||
case class Data(clients: Set[Controller.NodeInfo], barrier: String, arrived: List[ActorRef], deadline: Deadline)
|
||||
|
||||
trait Printer { this: Product with Throwable with NoStackTrace ⇒
|
||||
override def toString = productPrefix + productIterator.mkString("(", ", ", ")")
|
||||
|
|
@ -471,6 +473,8 @@ private[akka] object BarrierCoordinator {
|
|||
|
||||
case class BarrierTimeout(data: Data)
|
||||
extends RuntimeException("timeout while waiting for barrier '" + data.barrier + "'") with NoStackTrace with Printer
|
||||
case class FailedBarrier(data: Data)
|
||||
extends RuntimeException("failing barrier '" + data.barrier + "'") with NoStackTrace with Printer
|
||||
case class DuplicateNode(data: Data, node: Controller.NodeInfo)
|
||||
extends RuntimeException(node.toString) with NoStackTrace with Printer
|
||||
case class WrongBarrier(barrier: String, client: ActorRef, data: Data)
|
||||
|
|
@ -497,61 +501,77 @@ private[akka] class BarrierCoordinator extends Actor with LoggingFSM[BarrierCoor
|
|||
import BarrierCoordinator._
|
||||
import akka.actor.FSM._
|
||||
import Controller._
|
||||
import akka.util.{ Timeout ⇒ auTimeout }
|
||||
|
||||
// this shall be set to false if all subsequent barriers shall fail
|
||||
// this shall be set to true if all subsequent barriers shall fail
|
||||
var failed = false
|
||||
|
||||
override def preRestart(reason: Throwable, message: Option[Any]) {}
|
||||
override def postRestart(reason: Throwable) { failed = true }
|
||||
|
||||
// TODO what happens with the other waiting players in case of a test failure?
|
||||
|
||||
startWith(Idle, Data(Set(), "", Nil))
|
||||
startWith(Idle, Data(Set(), "", Nil, null))
|
||||
|
||||
whenUnhandled {
|
||||
case Event(n: NodeInfo, d @ Data(clients, _, _)) ⇒
|
||||
case Event(n: NodeInfo, d @ Data(clients, _, _, _)) ⇒
|
||||
if (clients.find(_.name == n.name).isDefined) throw new DuplicateNode(d, n)
|
||||
stay using d.copy(clients = clients + n)
|
||||
case Event(ClientDisconnected(name), d @ Data(clients, _, arrived)) ⇒
|
||||
if (clients.isEmpty) throw BarrierEmpty(d, "cannot disconnect " + name + ": no client to disconnect")
|
||||
(clients find (_.name == name)) match {
|
||||
case None ⇒ stay
|
||||
case Some(c) ⇒ throw ClientLost(d.copy(clients = clients - c, arrived = arrived filterNot (_ == c.fsm)), name)
|
||||
case Event(ClientDisconnected(name), d @ Data(clients, _, arrived, _)) ⇒
|
||||
if (arrived.isEmpty)
|
||||
stay using d.copy(clients = clients.filterNot(_.name == name))
|
||||
else {
|
||||
(clients find (_.name == name)) match {
|
||||
case None ⇒ stay
|
||||
case Some(c) ⇒ throw ClientLost(d.copy(clients = clients - c, arrived = arrived filterNot (_ == c.fsm)), name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(Idle) {
|
||||
case Event(EnterBarrier(name), d @ Data(clients, _, _)) ⇒
|
||||
case Event(EnterBarrier(name, timeout), d @ Data(clients, _, _, _)) ⇒
|
||||
if (failed)
|
||||
stay replying ToClient(BarrierResult(name, false))
|
||||
else if (clients.map(_.fsm) == Set(sender))
|
||||
stay replying ToClient(BarrierResult(name, true))
|
||||
else if (clients.find(_.fsm == sender).isEmpty)
|
||||
stay replying ToClient(BarrierResult(name, false))
|
||||
else
|
||||
goto(Waiting) using d.copy(barrier = name, arrived = sender :: Nil)
|
||||
case Event(RemoveClient(name), d @ Data(clients, _, _)) ⇒
|
||||
else {
|
||||
goto(Waiting) using d.copy(barrier = name, arrived = sender :: Nil,
|
||||
deadline = getDeadline(timeout))
|
||||
}
|
||||
case Event(RemoveClient(name), d @ Data(clients, _, _, _)) ⇒
|
||||
if (clients.isEmpty) throw BarrierEmpty(d, "cannot remove " + name + ": no client to remove")
|
||||
stay using d.copy(clients = clients filterNot (_.name == name))
|
||||
}
|
||||
|
||||
onTransition {
|
||||
case Idle -> Waiting ⇒ setTimer("Timeout", StateTimeout, TestConductor().Settings.BarrierTimeout.duration, false)
|
||||
case Idle -> Waiting ⇒ setTimer("Timeout", StateTimeout, nextStateData.deadline.timeLeft, false)
|
||||
case Waiting -> Idle ⇒ cancelTimer("Timeout")
|
||||
}
|
||||
|
||||
when(Waiting) {
|
||||
case Event(EnterBarrier(name), d @ Data(clients, barrier, arrived)) ⇒
|
||||
case Event(EnterBarrier(name, timeout), d @ Data(clients, barrier, arrived, deadline)) ⇒
|
||||
if (name != barrier) throw WrongBarrier(name, sender, d)
|
||||
val together = if (clients.exists(_.fsm == sender)) sender :: arrived else arrived
|
||||
handleBarrier(d.copy(arrived = together))
|
||||
case Event(RemoveClient(name), d @ Data(clients, barrier, arrived)) ⇒
|
||||
val enterDeadline = getDeadline(timeout)
|
||||
// we only allow the deadlines to get shorter
|
||||
if (enterDeadline < deadline) {
|
||||
setTimer("Timeout", StateTimeout, enterDeadline.timeLeft, false)
|
||||
handleBarrier(d.copy(arrived = together, deadline = enterDeadline))
|
||||
} else
|
||||
handleBarrier(d.copy(arrived = together))
|
||||
case Event(RemoveClient(name), d @ Data(clients, barrier, arrived, _)) ⇒
|
||||
clients find (_.name == name) match {
|
||||
case None ⇒ stay
|
||||
case Some(client) ⇒
|
||||
handleBarrier(d.copy(clients = clients - client, arrived = arrived filterNot (_ == client.fsm)))
|
||||
}
|
||||
case Event(StateTimeout, data) ⇒
|
||||
throw BarrierTimeout(data)
|
||||
case Event(FailBarrier(name), d @ Data(_, barrier, _, _)) ⇒
|
||||
if (name != barrier) throw WrongBarrier(name, sender, d)
|
||||
throw FailedBarrier(d)
|
||||
case Event(StateTimeout, d) ⇒
|
||||
throw BarrierTimeout(d)
|
||||
}
|
||||
|
||||
initialize
|
||||
|
|
@ -568,5 +588,9 @@ private[akka] class BarrierCoordinator extends Actor with LoggingFSM[BarrierCoor
|
|||
}
|
||||
}
|
||||
|
||||
def getDeadline(timeout: Option[Duration]): Deadline = {
|
||||
Deadline.now + timeout.getOrElse(TestConductor().Settings.BarrierTimeout.duration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import akka.remote.testconductor.{ TestConductorProtocol ⇒ TCP }
|
|||
import com.google.protobuf.Message
|
||||
import akka.actor.Address
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder
|
||||
import akka.util.Duration
|
||||
import akka.remote.testconductor.TestConductorProtocol.BarrierOp
|
||||
|
||||
case class RoleName(name: String)
|
||||
|
||||
|
|
@ -28,7 +30,8 @@ private[akka] sealed trait ConfirmedClientOp extends ClientOp
|
|||
*/
|
||||
private[akka] case class Hello(name: String, addr: Address) extends NetworkOp
|
||||
|
||||
private[akka] case class EnterBarrier(name: String) extends ServerOp with NetworkOp
|
||||
private[akka] case class EnterBarrier(name: String, timeout: Option[Duration]) extends ServerOp with NetworkOp
|
||||
private[akka] case class FailBarrier(name: String) extends ServerOp with NetworkOp
|
||||
private[akka] case class BarrierResult(name: String, success: Boolean) extends UnconfirmedClientOp with NetworkOp
|
||||
|
||||
private[akka] case class Throttle(node: RoleName, target: RoleName, direction: Direction, rateMBit: Float) extends CommandOp
|
||||
|
|
@ -72,10 +75,16 @@ private[akka] class MsgEncoder extends OneToOneEncoder {
|
|||
x match {
|
||||
case Hello(name, addr) ⇒
|
||||
w.setHello(TCP.Hello.newBuilder.setName(name).setAddress(addr))
|
||||
case EnterBarrier(name) ⇒
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name))
|
||||
case EnterBarrier(name, timeout) ⇒
|
||||
val barrier = TCP.EnterBarrier.newBuilder.setName(name)
|
||||
timeout foreach (t ⇒ barrier.setTimeout(t.toNanos))
|
||||
barrier.setOp(BarrierOp.Enter)
|
||||
w.setBarrier(barrier)
|
||||
case BarrierResult(name, success) ⇒
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name).setStatus(success))
|
||||
val res = if (success) BarrierOp.Succeeded else BarrierOp.Failed
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name).setOp(res))
|
||||
case FailBarrier(name) ⇒
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name).setOp(BarrierOp.Fail))
|
||||
case ThrottleMsg(target, dir, rate) ⇒
|
||||
w.setFailure(TCP.InjectFailure.newBuilder.setAddress(target)
|
||||
.setFailure(TCP.FailType.Throttle).setDirection(dir).setRateMBit(rate))
|
||||
|
|
@ -114,8 +123,13 @@ private[akka] class MsgDecoder extends OneToOneDecoder {
|
|||
Hello(h.getName, h.getAddress)
|
||||
} else if (w.hasBarrier) {
|
||||
val barrier = w.getBarrier
|
||||
if (barrier.hasStatus) BarrierResult(barrier.getName, barrier.getStatus)
|
||||
else EnterBarrier(w.getBarrier.getName)
|
||||
barrier.getOp match {
|
||||
case BarrierOp.Succeeded ⇒ BarrierResult(barrier.getName, true)
|
||||
case BarrierOp.Failed ⇒ BarrierResult(barrier.getName, false)
|
||||
case BarrierOp.Fail ⇒ FailBarrier(barrier.getName)
|
||||
case BarrierOp.Enter ⇒ EnterBarrier(barrier.getName,
|
||||
if (barrier.hasTimeout) Option(Duration.fromNanos(barrier.getTimeout)) else None)
|
||||
}
|
||||
} else if (w.hasFailure) {
|
||||
val f = w.getFailure
|
||||
import TCP.{ FailType ⇒ FT }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.typesafe.config.ConfigFactory
|
|||
import akka.util.Timeout
|
||||
import akka.util.Duration
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import akka.pattern.{ ask, pipe, AskTimeoutException }
|
||||
import akka.dispatch.Await
|
||||
import scala.util.control.NoStackTrace
|
||||
import akka.actor.Status
|
||||
|
|
@ -26,6 +26,7 @@ import org.jboss.netty.channel.WriteCompletionEvent
|
|||
import java.net.ConnectException
|
||||
import akka.util.Deadline
|
||||
import akka.actor.Scheduler
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* The Player is the client component of the
|
||||
|
|
@ -76,10 +77,31 @@ trait Player { this: TestConductorExt ⇒
|
|||
* throw an exception in case of timeouts or other errors.
|
||||
*/
|
||||
def enter(name: String*) {
|
||||
enter(Settings.BarrierTimeout, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the named barriers, one after the other, in the order given. Will
|
||||
* throw an exception in case of timeouts or other errors.
|
||||
*/
|
||||
def enter(timeout: Timeout, name: Seq[String]) {
|
||||
system.log.debug("entering barriers " + name.mkString("(", ", ", ")"))
|
||||
val stop = Deadline.now + timeout.duration
|
||||
name foreach { b ⇒
|
||||
import Settings.BarrierTimeout
|
||||
Await.result(client ? ToServer(EnterBarrier(b)), Duration.Inf)
|
||||
val barrierTimeout = stop.timeLeft
|
||||
if (barrierTimeout < Duration.Zero) {
|
||||
client ! ToServer(FailBarrier(b))
|
||||
throw new TimeoutException("Server timed out while waiting for barrier " + b);
|
||||
}
|
||||
try {
|
||||
implicit val timeout = Timeout(barrierTimeout + Settings.QueryTimeout.duration)
|
||||
Await.result(client ? ToServer(EnterBarrier(b, Option(barrierTimeout))), Duration.Inf)
|
||||
} catch {
|
||||
case e: AskTimeoutException ⇒
|
||||
client ! ToServer(FailBarrier(b))
|
||||
// Why don't TimeoutException have a constructor that takes a cause?
|
||||
throw new TimeoutException("Client timed out while waiting for barrier " + b);
|
||||
}
|
||||
system.log.debug("passed barrier {}", b)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +110,7 @@ trait Player { this: TestConductorExt ⇒
|
|||
* Query remote transport address of named node.
|
||||
*/
|
||||
def getAddressFor(name: RoleName): Future[Address] = {
|
||||
import Settings.BarrierTimeout
|
||||
import Settings.QueryTimeout
|
||||
client ? ToServer(GetAddress(name)) mapTo
|
||||
}
|
||||
}
|
||||
|
|
@ -168,8 +190,8 @@ private[akka] class ClientFSM(name: RoleName, controllerAddr: InetSocketAddress)
|
|||
case Event(ToServer(msg), d @ Data(Some(channel), None)) ⇒
|
||||
channel.write(msg)
|
||||
val token = msg match {
|
||||
case EnterBarrier(barrier) ⇒ barrier
|
||||
case GetAddress(node) ⇒ node.name
|
||||
case EnterBarrier(barrier, timeout) ⇒ barrier
|
||||
case GetAddress(node) ⇒ node.name
|
||||
}
|
||||
stay using d.copy(runningOp = Some(token, sender))
|
||||
case Event(ToServer(op), Data(channel, Some((token, _)))) ⇒
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class LookupRemoteActorSpec extends MultiNodeSpec(LookupRemoteActorMultiJvmSpec)
|
|||
val masterAddress = testConductor.getAddressFor(master).await
|
||||
(hello ? "identify").await.asInstanceOf[ActorRef].path.address must equal(masterAddress)
|
||||
}
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class NewRemoteActorSpec extends MultiNodeSpec(NewRemoteActorMultiJvmSpec)
|
|||
system.stop(actor)
|
||||
}
|
||||
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
|
||||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef (with deployOnAll)" taggedAs LongRunningTest in {
|
||||
|
|
@ -74,7 +74,7 @@ class NewRemoteActorSpec extends MultiNodeSpec(NewRemoteActorMultiJvmSpec)
|
|||
system.stop(actor)
|
||||
}
|
||||
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ class RandomRoutedRemoteActorSpec extends MultiNodeSpec(RandomRoutedRemoteActorM
|
|||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first, second, third) {
|
||||
testConductor.enter("start", "broadcast-end", "end", "done")
|
||||
enterBarrier("start", "broadcast-end", "end", "done")
|
||||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("start")
|
||||
enterBarrier("start")
|
||||
val actor = system.actorOf(Props[SomeActor].withRouter(RandomRouter()), "service-hello")
|
||||
actor.isInstanceOf[RoutedActorRef] must be(true)
|
||||
|
||||
|
|
@ -76,17 +76,17 @@ class RandomRoutedRemoteActorSpec extends MultiNodeSpec(RandomRoutedRemoteActorM
|
|||
case (replyMap, address) ⇒ replyMap + (address -> (replyMap(address) + 1))
|
||||
}
|
||||
|
||||
testConductor.enter("broadcast-end")
|
||||
enterBarrier("broadcast-end")
|
||||
actor ! Broadcast(PoisonPill)
|
||||
|
||||
testConductor.enter("end")
|
||||
enterBarrier("end")
|
||||
replies.values foreach { _ must be > (0) }
|
||||
replies.get(node(fourth).address) must be(None)
|
||||
|
||||
// shut down the actor before we let the other node(s) shut down so we don't try to send
|
||||
// "Terminate" to a shut down node
|
||||
system.stop(actor)
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ class RoundRobinRoutedRemoteActorSpec extends MultiNodeSpec(RoundRobinRoutedRemo
|
|||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first, second, third) {
|
||||
testConductor.enter("start", "broadcast-end", "end", "done")
|
||||
enterBarrier("start", "broadcast-end", "end", "done")
|
||||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("start")
|
||||
enterBarrier("start")
|
||||
val actor = system.actorOf(Props[SomeActor].withRouter(RoundRobinRouter()), "service-hello")
|
||||
actor.isInstanceOf[RoutedActorRef] must be(true)
|
||||
|
||||
|
|
@ -76,17 +76,17 @@ class RoundRobinRoutedRemoteActorSpec extends MultiNodeSpec(RoundRobinRoutedRemo
|
|||
case (replyMap, address) ⇒ replyMap + (address -> (replyMap(address) + 1))
|
||||
}
|
||||
|
||||
testConductor.enter("broadcast-end")
|
||||
enterBarrier("broadcast-end")
|
||||
actor ! Broadcast(PoisonPill)
|
||||
|
||||
testConductor.enter("end")
|
||||
enterBarrier("end")
|
||||
replies.values foreach { _ must be(iterationCount) }
|
||||
replies.get(node(fourth).address) must be(None)
|
||||
|
||||
// shut down the actor before we let the other node(s) shut down so we don't try to send
|
||||
// "Terminate" to a shut down node
|
||||
system.stop(actor)
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ class ScatterGatherRoutedRemoteActorSpec extends MultiNodeSpec(ScatterGatherRout
|
|||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first, second, third) {
|
||||
testConductor.enter("start", "broadcast-end", "end", "done")
|
||||
enterBarrier("start", "broadcast-end", "end", "done")
|
||||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("start")
|
||||
enterBarrier("start")
|
||||
val actor = system.actorOf(Props[SomeActor].withRouter(ScatterGatherFirstCompletedRouter(within = 10 seconds)), "service-hello")
|
||||
actor.isInstanceOf[RoutedActorRef] must be(true)
|
||||
|
||||
|
|
@ -76,17 +76,17 @@ class ScatterGatherRoutedRemoteActorSpec extends MultiNodeSpec(ScatterGatherRout
|
|||
case (replyMap, address) ⇒ replyMap + (address -> (replyMap(address) + 1))
|
||||
}
|
||||
|
||||
testConductor.enter("broadcast-end")
|
||||
enterBarrier("broadcast-end")
|
||||
actor ! Broadcast(PoisonPill)
|
||||
|
||||
testConductor.enter("end")
|
||||
enterBarrier("end")
|
||||
replies.values.sum must be === connectionCount * iterationCount
|
||||
replies.get(node(fourth).address) must be(None)
|
||||
|
||||
// shut down the actor before we let the other node(s) shut down so we don't try to send
|
||||
// "Terminate" to a shut down node
|
||||
system.stop(actor)
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
}), "echo")
|
||||
}
|
||||
|
||||
testConductor.enter("name")
|
||||
enterBarrier("name")
|
||||
}
|
||||
|
||||
"support throttling of network connections" taggedAs LongRunningTest in {
|
||||
|
|
@ -62,7 +62,7 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
testConductor.throttle(slave, master, Direction.Send, rateMBit = 0.01).await
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_send")
|
||||
enterBarrier("throttled_send")
|
||||
|
||||
runOn(slave) {
|
||||
for (i ← 0 to 9) echo ! i
|
||||
|
|
@ -73,14 +73,14 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
receiveN(9) must be(1 to 9)
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_send2")
|
||||
enterBarrier("throttled_send2")
|
||||
|
||||
runOn(master) {
|
||||
testConductor.throttle(slave, master, Direction.Send, -1).await
|
||||
testConductor.throttle(slave, master, Direction.Receive, rateMBit = 0.01).await
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_recv")
|
||||
enterBarrier("throttled_recv")
|
||||
|
||||
runOn(slave) {
|
||||
for (i ← 10 to 19) echo ! i
|
||||
|
|
@ -98,7 +98,7 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
receiveN(9) must be(11 to 19)
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_recv2")
|
||||
enterBarrier("throttled_recv2")
|
||||
|
||||
runOn(master) {
|
||||
testConductor.throttle(slave, master, Direction.Receive, -1).await
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.remote.testkit
|
||||
|
||||
import akka.testkit.LongRunningTest
|
||||
|
||||
object MultiNodeSpecMultiJvmSpec extends MultiNodeConfig {
|
||||
commonConfig(debugConfig(on = false))
|
||||
|
||||
val node1 = role("node1")
|
||||
val node2 = role("node2")
|
||||
val node3 = role("node3")
|
||||
val node4 = role("node4")
|
||||
}
|
||||
|
||||
class MultiNodeSpecSpecMultiJvmNode1 extends MultiNodeSpecSpec
|
||||
class MultiNodeSpecSpecMultiJvmNode2 extends MultiNodeSpecSpec
|
||||
class MultiNodeSpecSpecMultiJvmNode3 extends MultiNodeSpecSpec
|
||||
class MultiNodeSpecSpecMultiJvmNode4 extends MultiNodeSpecSpec
|
||||
|
||||
class MultiNodeSpecSpec extends MultiNodeSpec(MultiNodeSpecMultiJvmSpec) {
|
||||
|
||||
import MultiNodeSpecMultiJvmSpec._
|
||||
|
||||
def initialParticipants = 4
|
||||
|
||||
"A MultiNodeSpec" must {
|
||||
|
||||
"wait for all nodes to remove themselves before we shut the conductor down" taggedAs LongRunningTest in {
|
||||
enterBarrier("startup")
|
||||
// this test is empty here since it only exercises the shutdown code in the MultiNodeSpec
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import org.scalatest.BeforeAndAfterEach
|
|||
import java.net.InetSocketAddress
|
||||
import java.net.InetAddress
|
||||
import akka.testkit.TimingTest
|
||||
import akka.util.{ Timeout, Duration }
|
||||
|
||||
object BarrierSpec {
|
||||
case class Failed(ref: ActorRef, thr: Throwable)
|
||||
|
|
@ -28,10 +29,10 @@ object BarrierSpec {
|
|||
akka.remote.netty.port = 0
|
||||
akka.actor.debug.fsm = on
|
||||
akka.actor.debug.lifecycle = on
|
||||
"""
|
||||
"""
|
||||
}
|
||||
|
||||
class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with BeforeAndAfterEach {
|
||||
class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender {
|
||||
|
||||
import BarrierSpec._
|
||||
import Controller._
|
||||
|
|
@ -41,10 +42,6 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val B = RoleName("b")
|
||||
val C = RoleName("c")
|
||||
|
||||
override def afterEach {
|
||||
system.eventStream.setLogLevel(Logging.WarningLevel)
|
||||
}
|
||||
|
||||
"A BarrierCoordinator" must {
|
||||
|
||||
"register clients and remove them" taggedAs TimingTest in {
|
||||
|
|
@ -55,27 +52,22 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
EventFilter[BarrierEmpty](occurrences = 1) intercept {
|
||||
b ! RemoveClient(A)
|
||||
}
|
||||
expectMsg(Failed(b, BarrierEmpty(Data(Set(), "", Nil), "cannot remove RoleName(a): no client to remove")))
|
||||
expectMsg(Failed(b, BarrierEmpty(Data(Set(), "", Nil, null), "cannot remove RoleName(a): no client to remove")))
|
||||
}
|
||||
|
||||
"register clients and disconnect them" taggedAs TimingTest in {
|
||||
val b = getBarrier()
|
||||
b ! NodeInfo(A, AddressFromURIString("akka://sys"), system.deadLetters)
|
||||
b ! ClientDisconnected(B)
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
b ! ClientDisconnected(A)
|
||||
}
|
||||
expectMsg(Failed(b, ClientLost(Data(Set(), "", Nil), A)))
|
||||
EventFilter[BarrierEmpty](occurrences = 1) intercept {
|
||||
b ! ClientDisconnected(A)
|
||||
}
|
||||
expectMsg(Failed(b, BarrierEmpty(Data(Set(), "", Nil), "cannot disconnect RoleName(a): no client to disconnect")))
|
||||
expectNoMsg(1 second)
|
||||
b ! ClientDisconnected(A)
|
||||
expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
"fail entering barrier when nobody registered" taggedAs TimingTest in {
|
||||
val b = getBarrier()
|
||||
b ! EnterBarrier("b")
|
||||
expectMsg(ToClient(BarrierResult("b", false)))
|
||||
b ! EnterBarrier("bar1", None)
|
||||
expectMsg(ToClient(BarrierResult("bar1", false)))
|
||||
}
|
||||
|
||||
"enter barrier" taggedAs TimingTest in {
|
||||
|
|
@ -83,12 +75,12 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val a, b = TestProbe()
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar2", None))
|
||||
noMsg(a, b)
|
||||
within(2 second) {
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
b.send(barrier, EnterBarrier("bar2", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar2", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar2", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,15 +89,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val a, b, c = TestProbe()
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar3", None))
|
||||
barrier ! NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar3", None))
|
||||
noMsg(a, b, c)
|
||||
within(2 second) {
|
||||
c.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
c.send(barrier, EnterBarrier("bar3", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar3", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar3", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar3", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,14 +107,14 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar4", None))
|
||||
b.send(barrier, EnterBarrier("bar4", None))
|
||||
barrier ! RemoveClient(A)
|
||||
barrier ! ClientDisconnected(A)
|
||||
noMsg(a, b, c)
|
||||
b.within(2 second) {
|
||||
b.within(2 seconds) {
|
||||
barrier ! RemoveClient(C)
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar4", true)))
|
||||
}
|
||||
barrier ! ClientDisconnected(C)
|
||||
expectNoMsg(1 second)
|
||||
|
|
@ -133,9 +125,9 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val a, b = TestProbe()
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar5", None))
|
||||
barrier ! RemoveClient(A)
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
b.expectMsg(ToClient(BarrierResult("foo", true)))
|
||||
}
|
||||
|
||||
|
|
@ -145,11 +137,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar6", None))
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
expectMsg(Failed(barrier, ClientLost(Data(Set(nodeA), "bar", a.ref :: Nil), B)))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: ClientLost) if (thr == ClientLost(Data(Set(nodeA), "bar6", a.ref :: Nil, thr.data.deadline), B)) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, ClientLost(Data(Set(nodeA), "bar6", a.ref :: Nil, null), B)) + " but got " + x)
|
||||
}
|
||||
}
|
||||
|
||||
"fail barrier with disconnecing node who already arrived" taggedAs TimingTest in {
|
||||
|
|
@ -160,12 +156,16 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeA
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeC
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar7", None))
|
||||
b.send(barrier, EnterBarrier("bar7", None))
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
expectMsg(Failed(barrier, ClientLost(Data(Set(nodeA, nodeC), "bar", a.ref :: Nil), B)))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: ClientLost) if (thr == ClientLost(Data(Set(nodeA, nodeC), "bar7", a.ref :: Nil, thr.data.deadline), B)) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, ClientLost(Data(Set(nodeA, nodeC), "bar7", a.ref :: Nil, null), B)) + " but got " + x)
|
||||
}
|
||||
}
|
||||
|
||||
"fail when entering wrong barrier" taggedAs TimingTest in {
|
||||
|
|
@ -175,11 +175,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeA
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeB
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar8", None))
|
||||
EventFilter[WrongBarrier](occurrences = 1) intercept {
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
}
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: WrongBarrier) if (thr == WrongBarrier("foo", b.ref, Data(Set(nodeA, nodeB), "bar8", a.ref :: Nil, thr.data.deadline))) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, WrongBarrier("foo", b.ref, Data(Set(nodeA, nodeB), "bar8", a.ref :: Nil, null))) + " but got " + x)
|
||||
}
|
||||
expectMsg(Failed(barrier, WrongBarrier("foo", b.ref, Data(Set(nodeA, nodeB), "bar", a.ref :: Nil))))
|
||||
}
|
||||
|
||||
"fail barrier after first failure" taggedAs TimingTest in {
|
||||
|
|
@ -188,10 +192,14 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
EventFilter[BarrierEmpty](occurrences = 1) intercept {
|
||||
barrier ! RemoveClient(A)
|
||||
}
|
||||
expectMsg(Failed(barrier, BarrierEmpty(Data(Set(), "", Nil), "cannot remove RoleName(a): no client to remove")))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: BarrierEmpty) if (thr == BarrierEmpty(Data(Set(), "", Nil, thr.data.deadline), "cannot remove RoleName(a): no client to remove")) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, BarrierEmpty(Data(Set(), "", Nil, null), "cannot remove RoleName(a): no client to remove")) + " but got " + x)
|
||||
}
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
a.send(barrier, EnterBarrier("right"))
|
||||
a.expectMsg(ToClient(BarrierResult("right", false)))
|
||||
a.send(barrier, EnterBarrier("bar9", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar9", false)))
|
||||
}
|
||||
|
||||
"fail after barrier timeout" taggedAs TimingTest in {
|
||||
|
|
@ -201,9 +209,13 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
a.send(barrier, EnterBarrier("right"))
|
||||
a.send(barrier, EnterBarrier("bar10", None))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
expectMsg(7 seconds, Failed(barrier, BarrierTimeout(Data(Set(nodeA, nodeB), "right", a.ref :: Nil))))
|
||||
val msg = expectMsgType[Failed](7 seconds)
|
||||
msg match {
|
||||
case Failed(barrier, thr: BarrierTimeout) if (thr == BarrierTimeout(Data(Set(nodeA, nodeB), "bar10", a.ref :: Nil, thr.data.deadline))) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, BarrierTimeout(Data(Set(nodeA, nodeB), "bar10", a.ref :: Nil, null))) + " but got " + x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +228,11 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
EventFilter[DuplicateNode](occurrences = 1) intercept {
|
||||
barrier ! nodeB
|
||||
}
|
||||
expectMsg(Failed(barrier, DuplicateNode(Data(Set(nodeA), "", Nil), nodeB)))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: DuplicateNode) if (thr == DuplicateNode(Data(Set(nodeA), "", Nil, thr.data.deadline), nodeB)) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, DuplicateNode(Data(Set(nodeA), "", Nil, null), nodeB)) + " but got " + x)
|
||||
}
|
||||
}
|
||||
|
||||
"finally have no failure messages left" taggedAs TimingTest in {
|
||||
|
|
@ -243,17 +259,14 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
b ! NodeInfo(A, AddressFromURIString("akka://sys"), testActor)
|
||||
expectMsg(ToClient(Done))
|
||||
b ! ClientDisconnected(B)
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
b ! ClientDisconnected(A)
|
||||
}
|
||||
EventFilter[BarrierEmpty](occurrences = 1) intercept {
|
||||
b ! ClientDisconnected(A)
|
||||
}
|
||||
expectNoMsg(1 second)
|
||||
b ! ClientDisconnected(A)
|
||||
expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
"fail entering barrier when nobody registered" taggedAs TimingTest in {
|
||||
val b = getController(0)
|
||||
b ! EnterBarrier("b")
|
||||
b ! EnterBarrier("b", None)
|
||||
expectMsg(ToClient(BarrierResult("b", false)))
|
||||
}
|
||||
|
||||
|
|
@ -264,12 +277,12 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar11", None))
|
||||
noMsg(a, b)
|
||||
within(2 second) {
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
b.send(barrier, EnterBarrier("bar11", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar11", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar11", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,16 +293,16 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar12", None))
|
||||
barrier ! NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
c.expectMsg(ToClient(Done))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar12", None))
|
||||
noMsg(a, b, c)
|
||||
within(2 second) {
|
||||
c.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
c.send(barrier, EnterBarrier("bar12", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar12", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar12", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar12", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,14 +315,14 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar13", None))
|
||||
b.send(barrier, EnterBarrier("bar13", None))
|
||||
barrier ! Remove(A)
|
||||
barrier ! ClientDisconnected(A)
|
||||
noMsg(a, b, c)
|
||||
b.within(2 second) {
|
||||
b.within(2 seconds) {
|
||||
barrier ! Remove(C)
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar13", true)))
|
||||
}
|
||||
barrier ! ClientDisconnected(C)
|
||||
expectNoMsg(1 second)
|
||||
|
|
@ -322,9 +335,9 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar14", None))
|
||||
barrier ! Remove(A)
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
b.expectMsg(ToClient(BarrierResult("foo", true)))
|
||||
}
|
||||
|
||||
|
|
@ -336,13 +349,13 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar15", None))
|
||||
barrier ! ClientDisconnected(RoleName("unknown"))
|
||||
noMsg(a)
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
a.expectMsg(ToClient(BarrierResult("bar", false)))
|
||||
a.expectMsg(ToClient(BarrierResult("bar15", false)))
|
||||
}
|
||||
|
||||
"fail barrier with disconnecing node who already arrived" taggedAs TimingTest in {
|
||||
|
|
@ -356,12 +369,12 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar16", None))
|
||||
b.send(barrier, EnterBarrier("bar16", None))
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
a.expectMsg(ToClient(BarrierResult("bar", false)))
|
||||
a.expectMsg(ToClient(BarrierResult("bar16", false)))
|
||||
}
|
||||
|
||||
"fail when entering wrong barrier" taggedAs TimingTest in {
|
||||
|
|
@ -373,15 +386,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeB
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar17", None))
|
||||
EventFilter[WrongBarrier](occurrences = 1) intercept {
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
}
|
||||
a.expectMsg(ToClient(BarrierResult("bar", false)))
|
||||
a.expectMsg(ToClient(BarrierResult("bar17", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("foo", false)))
|
||||
}
|
||||
|
||||
"not really fail after barrier timeout" taggedAs TimingTest in {
|
||||
"fail after barrier timeout" taggedAs TimingTest in {
|
||||
val barrier = getController(2)
|
||||
val a, b = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
|
|
@ -390,13 +403,13 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeB
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("right"))
|
||||
a.send(barrier, EnterBarrier("bar18", Option(2 seconds)))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
Thread.sleep(5000)
|
||||
Thread.sleep(4000)
|
||||
}
|
||||
b.send(barrier, EnterBarrier("right"))
|
||||
a.expectMsg(ToClient(BarrierResult("right", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("right", true)))
|
||||
b.send(barrier, EnterBarrier("bar18", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar18", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar18", false)))
|
||||
}
|
||||
|
||||
"fail if a node registers twice" taggedAs TimingTest in {
|
||||
|
|
@ -423,8 +436,75 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
controller ! nodeB
|
||||
b.expectMsg(ToClient(BarrierResult("initial startup", false)))
|
||||
}
|
||||
a.send(controller, EnterBarrier("x"))
|
||||
a.expectMsg(ToClient(BarrierResult("x", false)))
|
||||
a.send(controller, EnterBarrier("bar19", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar19", false)))
|
||||
}
|
||||
|
||||
"fail subsequent barriers after foreced failure" taggedAs TimingTest in {
|
||||
val barrier = getController(2)
|
||||
val a, b = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar20", Option(2 seconds)))
|
||||
EventFilter[FailedBarrier](occurrences = 1) intercept {
|
||||
b.send(barrier, FailBarrier("bar20"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar20", false)))
|
||||
b.expectNoMsg(1 second)
|
||||
}
|
||||
a.send(barrier, EnterBarrier("bar21", None))
|
||||
b.send(barrier, EnterBarrier("bar21", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar21", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar21", false)))
|
||||
}
|
||||
|
||||
"timeout within the shortest timeout if the new timeout is shorter" taggedAs TimingTest in {
|
||||
val barrier = getController(3)
|
||||
val a, b, c = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
val nodeC = NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
barrier ! nodeC
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar22", Option(10 seconds)))
|
||||
b.send(barrier, EnterBarrier("bar22", Option(2 seconds)))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
Thread.sleep(4000)
|
||||
}
|
||||
c.send(barrier, EnterBarrier("bar22", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar22", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar22", false)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar22", false)))
|
||||
}
|
||||
|
||||
"timeout within the shortest timeout if the new timeout is longer" taggedAs TimingTest in {
|
||||
val barrier = getController(3)
|
||||
val a, b, c = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
val nodeC = NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
barrier ! nodeC
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar23", Option(2 seconds)))
|
||||
b.send(barrier, EnterBarrier("bar23", Option(10 seconds)))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
Thread.sleep(4000)
|
||||
}
|
||||
c.send(barrier, EnterBarrier("bar23", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar23", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar23", false)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar23", false)))
|
||||
}
|
||||
|
||||
"finally have no failure messages left" taggedAs TimingTest in {
|
||||
|
|
@ -469,4 +549,7 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
probes foreach (_.msgAvailable must be(false))
|
||||
}
|
||||
|
||||
private def data(clients: Set[Controller.NodeInfo], barrier: String, arrived: List[ActorRef], previous: Data): Data = {
|
||||
Data(clients, barrier, arrived, previous.deadline)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,13 @@ import java.net.InetSocketAddress
|
|||
|
||||
import com.typesafe.config.{ ConfigObject, ConfigFactory, Config }
|
||||
|
||||
import akka.actor.{ RootActorPath, Deploy, ActorPath, ActorSystem, ExtendedActorSystem }
|
||||
import akka.actor.{ RootActorPath, ActorPath, ActorSystem, ExtendedActorSystem }
|
||||
import akka.dispatch.Await
|
||||
import akka.dispatch.Await.Awaitable
|
||||
import akka.remote.testconductor.{ TestConductorExt, TestConductor, RoleName }
|
||||
import akka.testkit.AkkaSpec
|
||||
import akka.util.{ NonFatal, Duration }
|
||||
import akka.util.{ Timeout, NonFatal }
|
||||
import akka.util.duration._
|
||||
|
||||
/**
|
||||
* Configure the role names and participants of the test, including configuration settings.
|
||||
|
|
@ -182,6 +183,14 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles:
|
|||
if (nodes exists (_ == myself)) yes else no
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the named barriers in the order given. Use the remaining duration from
|
||||
* the innermost enclosing `within` block or the default `BarrierTimeout`
|
||||
*/
|
||||
def enterBarrier(name: String*) {
|
||||
testConductor.enter(Timeout.durationToTimeout(remainingOr(testConductor.Settings.BarrierTimeout.duration)), name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the controller for the transport address of the given node (by role name) and
|
||||
* return that as an ActorPath for easy composition:
|
||||
|
|
@ -193,11 +202,12 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles:
|
|||
def node(role: RoleName): ActorPath = RootActorPath(testConductor.getAddressFor(role).await)
|
||||
|
||||
/**
|
||||
* Enrich `.await()` onto all Awaitables, using BarrierTimeout.
|
||||
* Enrich `.await()` onto all Awaitables, using remaining duration from the innermost
|
||||
* enclosing `within` block or QueryTimeout.
|
||||
*/
|
||||
implicit def awaitHelper[T](w: Awaitable[T]) = new AwaitHelper(w)
|
||||
class AwaitHelper[T](w: Awaitable[T]) {
|
||||
def await: T = Await.result(w, testConductor.Settings.BarrierTimeout.duration)
|
||||
def await: T = Await.result(w, remainingOr(testConductor.Settings.QueryTimeout.duration))
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -206,9 +216,11 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles:
|
|||
|
||||
private val controllerAddr = new InetSocketAddress(nodeNames(0), 4711)
|
||||
if (selfIndex == 0) {
|
||||
testConductor.startController(initialParticipants, myself, controllerAddr).await
|
||||
Await.result(testConductor.startController(initialParticipants, myself, controllerAddr),
|
||||
testConductor.Settings.BarrierTimeout.duration)
|
||||
} else {
|
||||
testConductor.startClient(myself, controllerAddr).await
|
||||
Await.result(testConductor.startClient(myself, controllerAddr),
|
||||
testConductor.Settings.BarrierTimeout.duration)
|
||||
}
|
||||
|
||||
// now add deployments, if so desired
|
||||
|
|
@ -250,4 +262,16 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles:
|
|||
// useful to see which jvm is running which role
|
||||
log.info("Role [{}] started", myself.name)
|
||||
|
||||
// wait for all nodes to remove themselves before we shut the conductor down
|
||||
final override def beforeShutdown() = {
|
||||
if (selfIndex == 0) {
|
||||
testConductor.removeNode(myself)
|
||||
within(testConductor.Settings.BarrierTimeout.duration) {
|
||||
awaitCond {
|
||||
testConductor.getNodes.await.filterNot(_ == myself).isEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ option optimize_for = SPEED;
|
|||
Compile with:
|
||||
cd ./akka-remote/src/main/protocol
|
||||
protoc RemoteProtocol.proto --java_out ../java
|
||||
cd ../../../..
|
||||
./scripts/fix-protobuf.sh
|
||||
*******************************************/
|
||||
|
||||
message AkkaRemoteProtocol {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,8 @@ akka {
|
|||
# (I) Length in akka.time-unit how long core threads will be kept alive if idling
|
||||
execution-pool-keepalive = 60s
|
||||
|
||||
# (I) Size of the core pool of the remote execution unit
|
||||
# (I) Size in number of threads of the core pool of the remote execution unit.
|
||||
# A value of 0 will turn this off, which is can lead to deadlocks under some configurations!
|
||||
execution-pool-size = 4
|
||||
|
||||
# (I) Maximum channel size, 0 for off
|
||||
|
|
@ -193,7 +194,7 @@ akka {
|
|||
# Examples: [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ]
|
||||
# You need to install the JCE Unlimited Strength Jurisdiction Policy Files to use AES 256
|
||||
# More info here: http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJCEProvider
|
||||
supported-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA"]
|
||||
enabled-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA"]
|
||||
|
||||
# Using /dev/./urandom is only necessary when using SHA1PRNG on Linux to prevent blocking
|
||||
# It is NOT as secure because it reuses the seed
|
||||
|
|
@ -204,11 +205,11 @@ akka {
|
|||
# There are three options, in increasing order of security:
|
||||
# "" or SecureRandom => (default)
|
||||
# "SHA1PRNG" => Can be slow because of blocking issues on Linux
|
||||
# "AES128CounterRNGFast" => fastest startup and based on AES encryption algorithm
|
||||
# "AES128CounterSecureRNG" => fastest startup and based on AES encryption algorithm
|
||||
# The following use one of 3 possible seed sources, depending on availability: /dev/random, random.org and SecureRandom (provided by Java)
|
||||
# "AES128CounterRNGSecure"
|
||||
# "AES256CounterRNGSecure" (Install JCE Unlimited Strength Jurisdiction Policy Files first)
|
||||
# Setting a value here may require you to supply the appropriate cipher suite (see supported-algorithms section above)
|
||||
# "AES128CounterInetRNG"
|
||||
# "AES256CounterInetRNG" (Install JCE Unlimited Strength Jurisdiction Policy Files first)
|
||||
# Setting a value here may require you to supply the appropriate cipher suite (see enabled-algorithms section above)
|
||||
random-number-generator = ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue