AKKA关机模式不起作用

时间:2015-08-22 10:38:11

标签: akka

在我阅读了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>

TestWorkerSupervisorStrategy是两回事。 context.watch()只对所有儿童演员产生影响。

SupervisorStrategy内发生异常时,override val supervisorStrategy = SupervisorStrategy.stoppingStrategy中的TestReaper不会使TestWorker停止。相反,我们需要更改TestWorker的父{1}}。由于上面代码中的所有参与者都是由SupervisorStrategy创建的,因此他们是Guardian Actor /user的子项,因此实际上我们需要通过添加{{1}来更改TestWorker参与者的监督策略在system.actorOf()

然而,最好使用另一个演员作为监督演员,就像@mattinbits在他的代码中所做的那样。

1 个答案:

答案 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) {...}