我有一个场景,我必须从父actor中重新启动一个子Actor。重启应遵循以下规则:
我现在有以下情况:
在我的父Actor中,我有一个Monix Observable,它推送事件如下:
class ParentActor extends Actor {
...
override def preStart(): Unit = {
super.preStart()
// Observable to stream events regarding PowerPlant's
val powerPlantEventObservable =
// For every config.database.refreshInterval in seconds
Observable.interval(config.database.refreshInterval)
// We ask the actor for the latest messages
.map(_ => (dbServiceActor ? DBServiceActor.PowerPlantEvents).mapTo[PowerPlantEventsSeq])
.concatMap(Observable.fromFuture(_))
.concatMap(Observable.fromIterable(_))
// Subscriber that pipes the messages to this Actor
cancelable := powerPlantEventObservable.subscribe { update =>
(self ? update).map(_ => Continue)
}
}
}
所以上面发生的是,我要求另一个名为DBServiceActor的Actor获取事件列表,当这些事件可用时,我将它传递给ParentActor(自我更新)。 ParentActor的receive方法如下所示,这是我想重新启动子actor的地方。异步:
override def receive: Receive = {
case PowerPlantUpdateEvent(id, powerPlantCfg) =>
log.info(s"Re-starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
// I want to stop the actor first by finding it from the actor system
// If it exists, do a context.stop on the Actor instance
// Once it is stopped, I want to start it again by creating a new instance of this Actor
// Once this new Actor instance is created, I want to signal my Monix Observer to send me the next event
}
有什么建议吗?
编辑:所以这是根据以下帖子的建议的新接收方法:
def receive: Receive = {
// TODO: When I restart, I need the powerPlantCfg!!! How to get it?
case Terminated(actorRef) =>
context.unwatch(actorRef)
case PowerPlantCreateEvent(id, powerPlantCfg) =>
log.info(s"Starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
// Start the PowerPlant, and pipe the message to self
startPowerPlant(id, powerPlantCfg).pipeTo(self)
case PowerPlantUpdateEvent(id, powerPlantCfg) =>
log.info(s"Re-starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
actorRef ! PoisonPill
case None =>
log.warning(s"No running actor instance found for id $id :: Creating a new instance")
self ! PowerPlantCreateEvent(id, powerPlantCfg)
}
case PowerPlantDeleteEvent(id, powerPlantCfg) => // TODO
log.info(s"Stopping PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
actorRef ! PoisonPill
case None =>
log.warning(s"No running actor instance found for id $id")
}
}
答案 0 :(得分:1)
我建议如下:
// I want to stop the actor first by finding it from the actor system
要查找孩子,请使用val child = context.child(name)
或浏览context.children
// If it exists, do a context.stop on the Actor instance
上面找到的孩子,你必须设置死亡监视(对下一步有用)并杀死它:
context.watch(child) // you could also consider using context.watchWith()
child ! PoisonPill
// Once it is stopped, I want to start it again by creating a new instance of this Actor
由于上面的deatch-watch设置,您的ParentActor
将被通知孩子停止,只需添加一个消息处理程序:
override def receive: Receive = {
case Terminated(child) => // you might want to check which specific child was terminated if you care
context.unwatch(child)
context.actorOf(...) // recreate the child actor
// Once this new Actor instance is created, I want to signal my Monix Observer to send me the next event
你可以在调用context.actorOf
之后立即发出信号,如果你想确定儿童演员设法启动(即,因为某些东西可能在初始化/开始时失败),你应该拥有你的{ {1}}期待来自新创建的孩子的信号。如果启动更复杂,您可以在ParentActor
或其中一个消息处理程序中将该信号从子节点发送到父节点。
答案 1 :(得分:0)
基于Frederic A的回答,我在这里发布我的解决方案,展示了如何处理一个Actor的重启:
def waitForRestart(source: ActorRef, newMessage: Option[PowerPlantCreateEvent[PowerPlantConfig]]): Receive = {
case Terminated(actor) =>
context.unwatch(actor)
newMessage match {
case Some(powerPlantCreateEvent) =>
self ! powerPlantCreateEvent
case _ =>
// just ignore
}
// Now unstash all of the messages
unstashAll()
case someDamnThing =>
log.error(s"Unexpected message $someDamnThing :: " +
s"received while waiting for an actor to be stopped")
stash()
}
这是接收方法:
def receive: Receive = {
case Terminated(actorRef) =>
context.unwatch(actorRef)
case PowerPlantCreateEvent(id, powerPlantCfg) =>
log.info(s"Starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
// Start the PowerPlant, and pipe the message to self
startPowerPlant(id, powerPlantCfg).pipeTo(self)
case PowerPlantUpdateEvent(id, powerPlantCfg) =>
log.info(s"Re-starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
// We first kill
actorRef ! PoisonPill
// We wait asynchronously until this Actor is re-started
context.become(
waitForRestart(
actorRef,
Some(PowerPlantCreateEvent(id, powerPlantCfg))
)
)
case None =>
log.warning(s"No running actor instance found for id $id :: Creating a new instance")
self ! PowerPlantCreateEvent(id, powerPlantCfg)
}
case PowerPlantDeleteEvent(id, powerPlantCfg) =>
log.info(s"Stopping PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
actorRef ! Kill
case None =>
log.warning(s"No running actor instance found for id $id")
}
}