在我阅读了AKKA团队撰写的这篇优秀博客Shutdown Patterns in AKKA 2之后,我运行了代码,确实有效。
但是当我做一个稍微改变的另一个实验,在工人中抛出异常时,那么这个模式将不起作用。由于工人在工作期间可能会抛出任何异常是合理的,对吗?
以下是我的代码,两个文件:
Reaper.scala,复制自上述文章:
import akka.actor.{Actor, ActorRef, Terminated}
import scala.collection.mutable.ArrayBuffer
object Reaper {
// Used by others to register an Actor for watching
case class WatchMe(ref: ActorRef)
}
abstract class Reaper extends Actor {
import Reaper._
// Keep track of what we're watching
val watched = ArrayBuffer.empty[ActorRef]
// Derivations need to implement this method. It's the
// hook that's called when everything's dead
def allSoulsReaped(): Unit
// Watch and check for termination
final def receive = {
case WatchMe(ref) =>
context.watch(ref)
watched += ref
case Terminated(ref) =>
watched -= ref
if (watched.isEmpty) allSoulsReaped()
}
}
TestWorker.scala
import akka.actor.{SupervisorStrategy, Props, ActorSystem, Actor}
import Reaper._
class TestReaper extends Reaper {
def allSoulsReaped(): Unit = context.system.shutdown()
override val supervisorStrategy = SupervisorStrategy.stoppingStrategy
}
// The reaper sends this message to all workers to notify them to start work
case object StartWork
class TestWorker extends Actor {
def receive = {
case StartWork =>
// do real work ...
throw new IllegalStateException("Simulate uncaught exceptions during work")
}
}
object TestWorker {
def main(args: Array[String]) : Unit = {
val system = ActorSystem("system")
val reaper = system.actorOf(Props[TestReaper])
val worker1 = system.actorOf(Props[TestWorker])
val worker2 = system.actorOf(Props[TestWorker])
reaper ! WatchMe(worker1)
reaper ! WatchMe(worker2)
Thread.sleep(3000) // make sure WatchMe will be delivered before StartWork
worker1 ! StartWork
worker2 ! StartWork
system.awaitTermination()
}
}
这个程序将永远挂起。
如果工人抛出未捕获的异常,收割者似乎无法收到Terminated
消息
有人可以告诉我为什么吗?非常感谢提前!
@mattinbits正确答案:
此程序永远挂起的原因是我的代码TestWorker
不是TestReaper
甚至TestReaper
来电context.watch(ref)
的孩子。
context.watch()并不意味着成为一名儿童。 context.watch(ref)
只是意味着当TestReaper
演员去世时{}}会收到通知。< / p>
TestWorker
和SupervisorStrategy
是两回事。 context.watch()
只对所有儿童演员产生影响。
在SupervisorStrategy
内发生异常时,override val supervisorStrategy = SupervisorStrategy.stoppingStrategy
中的TestReaper
不会使TestWorker
停止。相反,我们需要更改TestWorker
的父{1}}。由于上面代码中的所有参与者都是由SupervisorStrategy
创建的,因此他们是Guardian Actor /user
的子项,因此实际上我们需要通过添加{{1}来更改TestWorker
参与者的监督策略在system.actorOf()
然而,最好使用另一个演员作为监督演员,就像@mattinbits在他的代码中所做的那样。
答案 0 :(得分:4)
观看演员是不够的,你还必须确保演员停止(这是Terminated
被发送的条件。)
默认情况下,当actor抛出异常时,策略是重新启动它。您需要为演员提供一个将应用Stop
指令的主管。
看看以下内容,两个测试都通过(Reaper与上面的版本相同):
import java.util.concurrent.TimeoutException
import Reaper.WatchMe
import akka.actor.SupervisorStrategy.Stop
import akka.actor._
import akka.testkit.{TestProbe, TestKit}
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike, WordSpec}
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
case object StartWork
class TestReaper extends Reaper {
def allSoulsReaped(): Unit = context.system.shutdown()
}
class TestWorker extends Actor {
def receive = {
case StartWork =>
// do real work ...
throw new IllegalStateException("Simulate uncaught exceptions during work")
}
}
class TestParent(reaper: ActorRef, probe: ActorRef) extends Actor {
def receive = {
case "Start" =>
val worker1 = context.actorOf(Props[TestWorker])
val worker2 = context.actorOf(Props[TestWorker])
reaper ! WatchMe(worker1)
reaper ! WatchMe(worker2)
worker1 ! StartWork
worker2 ! StartWork
}
override def supervisorStrategy = OneForOneStrategy() {
case ex: IllegalStateException =>
probe ! "Stopped a worker"
Stop
}
}
class TestSupervision extends TestKit(ActorSystem("Test"))
with WordSpecLike
with Matchers
with BeforeAndAfterAll{
"Supervision" should {
"Stop the actor system when the parent stops the workers" in {
val reaper = system.actorOf(Props[TestReaper])
val probe = TestProbe()
val parent = system.actorOf(Props(new TestParent(reaper, probe.ref)))
parent ! "Start"
probe.expectMsg("Stopped a worker")
probe.expectMsg("Stopped a worker")
import system.dispatcher
val terminatedF = Future {
system.awaitTermination()
}
Await.ready(terminatedF, 2 seconds)
}
}
override def afterAll(){
system.shutdown()
}
}
class TestLackSupervision extends TestKit(ActorSystem("Test2"))
with WordSpecLike
with Matchers
with BeforeAndAfterAll{
"Lack of Supervision" should {
"Not stop the actor system when the workers don't have an appropriate parent" in {
val reaper = system.actorOf(Props[TestReaper])
val worker1 = system.actorOf(Props[TestWorker])
val worker2 = system.actorOf(Props[TestWorker])
reaper ! WatchMe(worker1)
reaper ! WatchMe(worker2)
import system.dispatcher
val terminatedF = Future { system.awaitTermination()}
a [TimeoutException] should be thrownBy Await.ready(terminatedF, 2 seconds)
}
}
override def afterAll(){
system.shutdown()
}
}
默认情况下,当actor抛出异常时会重新启动它们。由于监督策略从父级应用于子级,因此存在TestParent以对子级执行Stop
指令。由于这个原因,您的原始代码无效。
如果您希望顶级actor(使用system.actorOf
启动的那些)停止异常,您可以设置配置属性akka.actor.guardian-supervisor-strategy = "akka.actor.StoppingSupervisorStrategy"
,但在我的示例中,我更喜欢使用父actor,因为actor层次结构是在阿卡组织监督的正常方式。
要以应用程序身份运行,请执行以下类似操作:
object Main extends App {
val system = ActorSystem("Example")
val reaper = system.actorOf(Props[TestReaper])
val dummyProbe = system.actorOf(Props(new Actor{
def receive = {
case "Stopped a worker" => println("Stopped a worker")
}
}))
val parent = system.actorOf(Props(new TestParent(reaper, dummyProbe)))
parent ! "Start"
system.awaitTermination()
}
要阻止在命令行上打印异常并混淆输出,请按以下步骤更改监督策略:
override def supervisorStrategy = OneForOneStrategy(loggingEnabled = false) {...}