获取或创建儿童Akka演员并确保活力

时间:2014-05-13 10:49:13

标签: scala akka actor race-condition

我正在尝试使用Akka actor的层次结构来处理每个用户状态。有一个拥有所有孩子的父actor,并以正确的方式处理get-or-create(参见a1a2):

class UserActorRegistry extends Actor {
  override def Receive = {
    case msg@ DoPerUserWork(userId, _) =>
      val perUserActor = getOrCreateUserActor(userId)
      // perUserActor is live now, but will it receive "msg"?
      perUserActor.forward(msg)
  }

  def getOrCreateUserActor(userId: UserId): ActorRef = {
    val childName = userId.toActorName
    context.child(childName) match {
      case Some(child) => child
      case None => context.actorOf(Props(classOf[UserActor], userId), childName)
  }
}

为了回收记忆,UserActors在闲置一段时间后过期(即计时器触发儿童演员呼叫context.stop(self))。

我的问题是我认为“getOrCreateUserActor”与接收转发邮件的子actor之间存在竞争条件 - 如果子进程在该窗口中到期,则转发的邮件将丢失。

有什么方法可以检测到这个边缘情况,或者重构UserActorRegistry以排除它?

1 个答案:

答案 0 :(得分:9)

我可以看到你当前设计的两个问题,让你自己接受你提到的竞争条件:

1)终止条件(计时器发送毒丸)直接进入儿童演员。通过采用这种方法,子进程当然可以在一个单独的线程上(在调度程序内)终止,同时,已经设置了一条消息在UserActorRegistry actor中转发给它(在一个不同的线程内)调度员)。

2)使用PoisonPill终止孩子。 PoisonPill用于正常停止,允许首先处理邮箱中的其他邮件。在您的情况下,由于不活动而终止,这似乎表明邮箱中已有其他邮件。我在此处看到PoisonPill错误,因为在您的情况下,可能会在PosionPill之后发送另一条消息,并且在处理PoisonPill后该消息肯定会丢失。

所以我建议你将非活动儿童的终止委托给UserActorRegistry,而不是在孩子们自己做。当您检测到不活动状态时,向UserActorRegistry实例发送一条消息,指示需要终止某个特定子项。收到该邮件后,请通过stop终止该子邮件,而不是发送PoisonPill。通过使用以串行方式处理的UserActorRegistry的单个邮箱,您可以帮助确保在您即将向其发送消息时不会并行终止子项。

现在,这里有一个复杂的问题,你必须处理。停止一个actor是异步的。因此,如果您对某个孩子调用stop,则在处理DoPerUserWork消息时可能不会完全停止该消息,从而可能会向其发送一条消息,因为它正处于停止状态。您可以通过保留一些表示正在停止的子项的内部状态(List)来解决此问题。当您停止孩子时,请将其名称添加到该列表中,然后在其上设置DeathWatch(通过context watch child)。当您收到该孩子的Terminated事件时,请从被终止的孩子列表中删除该名称。如果您的名字在该列表中,则为其接收工作,请将其重新排列以进行重新处理,最多可能达到最大次数,以便不会尝试永久重新处理。

这不是一个完美的解决方案;它只是对您的方法的一些问题的识别,并推动正确的方向来解决其中的一些问题。如果你想看到这个的代码,请告诉我,我会一起鞭打。

修改

回应你的第二条评论。我认为你不能看到一个孩子ActorRef并且看到它正在关闭,因此需要关闭正在关闭的孩子列表。您可以增强DoPerUserWork消息以包含numberOfAttempts:Int字段并将其递增并发送回self以进行重新处理(如果您看到目标子节点当前正在关闭)。然后,您可以使用numberOfAttempts来防止永久重新排队,在某些最大尝试次数停止。如果依靠DeathWatch感觉不太舒服,可以将生存时间组件添加到关闭子项列表中的项目。然后你可以在你遇到它们时修剪它们,如果它们在列表中但已经存在太久了。