Изящно завершить работу разных актеров супервизора без дублирования кода - PullRequest
0 голосов
/ 06 декабря 2018

У меня есть API, который создает актер A (во время выполнения).Затем A создает Actor B (также во время выполнения).

У меня есть другой API, который создает Actor C (отличается от Actor, код команды между ними отсутствует), а C создает Actor D.

Я хочу аккуратно отключить A и C, как только B и D закончили обработку своих сообщений (A и C не обязательно работают вместе, они не связаны).

Отправка таблеток с отравлением в A / C не достаточно хороша, потому чтодети (B / D) все равно получат остановку контекста и не смогут завершить свои задачи.

Я понимаю, что мне нужно реализовать новый тип сообщения.Я не понимал, как создать инфраструктуру, чтобы и A, и C знали, как реагировать на это сообщение, не имея одинакового метода получения дубликатов в обоих.

Решение, которое я нашел, заключалось в создании новой черты, которая расширяетАктер и переопределить необработанный метод.Код выглядит следующим образом:

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
  }
}

Но это звучит немного странно ... Есть ли лучшее (не хакерское) решение?

Ответы [ 2 ]

0 голосов
/ 23 декабря 2018

Так что это заняло у меня немного времени, но я нашел свой ответ.Я реализовал Шаблон Жнеца

SuicideActor создает выделенного актера Жнеца, когда он заканчивает свой блок.Жнец watch всех детей-самоубийц и после того, как все они прервали его, он отправляет PoisonPill в SuicideActor и себе

Код 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
  }
}
0 голосов
/ 06 декабря 2018

Я думаю, что разработка собственной процедуры завершения не является необходимой и потенциально может вызвать головную боль для будущих разработчиков вашего кода, поскольку это нестандартное поведение akka, которое необходимо еще раз понять.

Если A и C не связаны и могут завершаться независимо, возможны следующие варианты.Чтобы избежать путаницы, я буду использовать только актеров A и B в своих объяснениях.

Опция 1.

Актер A использует context.watch для вновь созданного актера B и реагирует на сообщение Terminated в своем методе receive.Актер B вызывает context.stop(context.self), когда это будет сделано с его задачей, и это сгенерирует событие Terminated, которое будет обработано актором A, который может очистить свое состояние в случае необходимости и завершиться тоже.

Проверьте эти документы для получения более подробной информации.

Вариант 2.

Actor B вызывает context.stop(context.parent) для непосредственного завершения родителя, когда это делается с егособственная задача.Эта опция не позволяет родителю реагировать и выполнять дополнительные задачи по очистке, если это необходимо.

Наконец, разделение этой логики между актерами A и C может быть выполнено с такой же чертой, как вы, но логика очень малаи дублирование кода не всегда плохо.

...