正常关闭不同的主管角色,而无需复制代码

时间:2018-12-06 08:03:06

标签: scala akka

我有一个API(在运行时)创建actorA。然后,A(也在运行时)创建Actor B。

我还有另一个API创建Actor C(与Actor A不同,它们之间没有命令代码),而C创建了ActorD。

我想在B和D完成处理它们的消息后就正常关闭A和C(A和C不一定一起运行,它们是无关的)。

将毒丸发送到A / C还不够好,因为孩子(B / D)仍然会停止上下文,并且无法完成任务。

我知道我需要实施一种新型的消息。 我不了解如何创建基础结构,因此A和C都将知道如何响应此消息而又没有相同的重复接收方法。

我发现的解决方案是创建一个扩展Actor并覆盖未处理方法的新特征。 代码如下:

object SuicideActor {
  case class PleaseKillYourself()
  case class IKilledMyself()
}

trait SuicideActor extends Actor {
 override def unhandled(message: Any): Unit = message match {
    case PleaseKillYourself =>
      Logger.debug(s"Actor ${self.path} received PleaseKillYourself - stopping children and aborting...")
      val livingChildren = context.children.size
      if (livingChildren == 0) {
        endLife()
      } else {
        context.children.foreach(_ ! PleaseKillYourself)
        context become waitForChildren(livingChildren)
      }
    case _ => super.unhandled(message)
  }

  protected[crystalball] def waitForChildren(livingChildren: Int): Receive = {
    case IKilledMyself =>
      val remaining = livingChildren - 1
      if (remaining == 0) { endLife() }
      else { context become waitForChildren(remaining) }
  }

  private def endLife(): Unit = {
    context.parent ! IKilledMyself
    context stop self
  }
}

但是,这听起来有点头。...是否有更好的(非non头)解决方案?

2 个答案:

答案 0 :(得分:2)

我认为没有必要设计自己的终止程序,并且可能会导致将来的代码维护人员头痛,因为这是非标准的akka​​行为,需要再次理解。

如果A和C不相关并且可以独立终止,则可以使用以下选项。为避免混淆,在解释中我只使用演员A和B。

选项1。

演员A在新创建的演员B上使用context.watch,并以其Terminated方法对receive消息做出反应。角色B完成其任务后将调用context.stop(context.self),这将生成Terminated事件,该事件将由角色A处理,该事件可以在需要时清除其状态并终止。

检查这些docs以获得更多详细信息。

选项2。

演员B完成自己的任务后,会调用context.stop(context.parent)直接终止其父。如果需要,此选项不允许家长做出反应并执行其他清理任务。

最后,在角色A和C之间共享此逻辑可以使用您的特质来完成,但是逻辑很小,并且重复代码并不是一件坏事。

答案 1 :(得分:0)

所以花了我一些时间,但我找到了答案。 我实施了The Reaper Pattern

SuicideActor在完成阻止后会创建一个专用的Reaper actor。收割者watch的所有SuicideActor孩子,一旦他们全部终止,就会向SuicideActor及其本身发送PoisonPill

SuicideActor代码是:

trait SuicideActor extends  Actor  {

  def killSwitch(block: => Unit): Unit = {
    block
    Logger.info(s"Actor ${self.path.name} is commencing suicide sequence...")
    context become PartialFunction.empty
    val children = context.children
    val reaper = context.system.actorOf(ReaperActor.props(self), s"ReaperFor${self.path.name}")
    reaper ! Reap(children.toSeq)
  }

  override def postStop(): Unit = Logger.debug(s"Actor ${self.path.name} is dead.")

}

收割者是: 对象ReaperActor {

  case class Reap(underWatch: Seq[ActorRef])

  def props(supervisor: ActorRef): Props = {
    Props(new ReaperActor(supervisor))
  }
}

class ReaperActor(supervisor: ActorRef) extends Actor {

  override def preStart(): Unit = Logger.info(s"Reaper for ${supervisor.path.name} started")
  override def postStop(): Unit = Logger.info(s"Reaper for ${supervisor.path.name} ended")

  override def receive: Receive = {
    case Reap(underWatch) =>
      if (underWatch.isEmpty) {
        killLeftOvers
      } else {
        underWatch.foreach(context.watch)
        context become reapRemaining(underWatch.size)
        underWatch.foreach(_ ! PoisonPill)
      }
  }

  def reapRemaining(livingActorsNumber: Int): Receive = {
    case Terminated(_) =>
      val remainingActorsNumber = livingActorsNumber - 1
      if (remainingActorsNumber == 0) {
        killLeftOvers
      } else {
        context become reapRemaining(remainingActorsNumber)
      }
  }

  private def killLeftOvers = {
    Logger.debug(s"All children of ${supervisor.path.name} are dead killing supervisor")
    supervisor ! PoisonPill
    self ! PoisonPill
  }
}