Scala 获取或创建子Akka actor并确保生动性

Scala 获取或创建子Akka actor并确保生动性,scala,akka,actor,race-condition,Scala,Akka,Actor,Race Condition,我试图使用Akka参与者的层次结构来处理每个用户的状态。有一个父角色拥有所有子角色,并以正确的方式处理get或create(请参见,): 为了回收内存,UserActors在空闲一段时间后过期(即计时器触发子actor调用context.stop(self)) 我的问题是,我认为“getOrCreateUserActor”和接收转发消息的子参与者之间存在竞争条件——如果子参与者在该窗口中过期,则转发消息将丢失 有没有什么方法可以检测到这种边缘情况,或者重构UserActorRegistry以排除

我试图使用Akka参与者的层次结构来处理每个用户的状态。有一个父角色拥有所有子角色,并以正确的方式处理get或create(请参见,):

为了回收内存,
UserActors
在空闲一段时间后过期(即计时器触发子actor调用
context.stop(self)

我的问题是,我认为“getOrCreateUserActor”和接收转发消息的子参与者之间存在竞争条件——如果子参与者在该窗口中过期,则转发消息将丢失


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

我可以看到您当前的设计存在两个问题,这两个问题使您面临您提到的竞争条件:

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

2) 使用
毒药
终止孩子的生命。
毒丸
用于优雅的停止,允许首先处理邮箱中的其他邮件。在您的情况下,由于不活动而终止,这似乎表明邮箱中没有其他邮件。我认为
毒丸
在这里是错误的,因为在您的情况下,在
PosionPill
之后可能会发送另一条消息,并且在处理
毒丸
后,该消息肯定会丢失

因此,我建议您将非活动子项的终止委托给
UserActorRegistry
,而不是委托给子项本身。当检测到不活动状态时,向
UserActorRegistry
的实例发送一条消息,指示需要终止特定的子级。当您收到该消息时,通过
stop
终止该儿童,而不是发送
毒药。通过使用以串行方式处理的
UserActorRegistry
的单个邮箱,您可以帮助确保在您将要向其发送消息时,不会并行终止子项


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

这不是一个完美的解决方案;这只是确定了你的方法中的一些问题,并推动了解决其中一些问题的正确方向。如果您想查看此代码,请告诉我,我会一起做一些事情

编辑


回应你的第二个评论。我不认为你能看到一个child
ActorRef
并且看到它当前正在关闭,因此需要一个正在关闭的child列表。您可以增强
DoPerUserWork
消息,使其包含numberOfAttempts:Int字段,并将其递增,如果您看到目标子级当前正在关闭,则发送回self进行重新处理。然后,您可以使用numberOfAttempts来防止永远重新排队,在某个最大尝试次数处停止。如果你觉得依赖死亡之路不太舒服,你可以在关闭儿童的列表中添加一个生存时间组件。如果项目在列表中但在列表中的时间过长,则可以在遇到项目时对其进行修剪。

谢谢;非常有用。我实际上没有使用
毒药
,但儿童演员正在调用
上下文。超时后停止(self)
:我已经更新了问题。很抱歉给你带来了困惑。你剩下的解释仍然很有道理,“停止一个参与者是异步的”——我认为这是这里的关键问题。在我从父级调用了
context.stop(child)
之后,子级仍然通过
context.child(name)
显示,它将处于什么状态?不确定状态?在此期间,如何处理传入的
DoPerUserWork
消息?我想我得让他们排队。在这段不确定的时间内,我将不被允许创建一个同名的新子对象,对吗?“孩子们被终止”的列表是检测这种僵尸状态的唯一方法吗?该列表是否也需要暂停,或者我可以依赖死亡之翼吗?@Rich,我在回答中添加了更多内容以解决您的第二个问题。如果您在地图中明确维护子角色列表,则会变得更简单。然后,您可以通过向父级发送到期消息让子级参与者发出超时信号,这会导致父级停止子级并清理映射。地图是同步维护的(从父参与者的角度来看),因此竞赛条件消失了。这听起来很合理。我还必须使用地图来查找儿童演员,而不是按姓名查找,否则可能会有冲突,因为儿童计划死亡,但仍然存在于
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)
  }
}