在学习akka的监督策略时,我想出了以下例子:
我希望父级演员(有一个自定义监督策略)向ask
其子演员处理一些状态,并将结果返回给sender
。对演员的调用也应该是ask
,而不是tell
(仅与Future
一起使用)。监督策略通过将状态存储在儿童演员中并在杀死其中一个后查询儿童来进行测试。
我想出了下面的测试和实现。我想使用pipeTo
模式将孩子的future
打包到一个future
中,该sender
将返回到父ask
。
然而,这种方法不能按预期工作。我已经确定父母在孩子身上执行的.withDispatcher(CallerThreadDispatcher.Id)
没有返回预期的状态。
我也试过了:
Await.result(future, timeout)
来仅使用单个调度程序
package org.skramer.learn.supervisorStrategies
import akka.actor.SupervisorStrategy.Restart
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, AllForOneStrategy, DeadLetter, OneForOneStrategy, Props, SupervisorStrategy}
import akka.pattern.ask
import akka.testkit.{CallingThreadDispatcher, ImplicitSender, TestKit, TestProbe}
import akka.util.Timeout
import org.scalatest.{Matchers, WordSpecLike}
import org.skramer.learn.AkkaSystemClosing
import org.skramer.learn.supervisorStrategies.StateHoldingActor.{ActorThrowCommand, AddStateCommand, GetStateCommand}
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, Future}
class SupervisorStrategiesTest extends TestKit(ActorSystem("testSystem")) with WordSpecLike with Matchers with ImplicitSender with AkkaSystemClosing {
import StateHoldingActor._
"actor with custom supervision strategy" should {
"apply the strategy to a single child" in {
implicit val timeout: Timeout = 3 seconds
val parentActor = system.actorOf(Props(new OneForOneParentActor(testActor)))
val initialStateFuture = parentActor ? "state"
val initialState = Await.result(initialStateFuture, timeout.duration)
initialState shouldBe List(Vector(), Vector())
parentActor ! ("first", AddStateCommand(1))
parentActor ! ("second", AddStateCommand(2))
val currentStateFuture = parentActor ? "state"
val currentState = Await.result(currentStateFuture, timeout.duration)
currentState shouldBe List(Vector(1), Vector(2))
parentActor ! "throwFirst"
val stateAfterRestartFuture = parentActor ? "state"
val stateAfterRestart = Await.result(stateAfterRestartFuture, timeout.duration)
stateAfterRestart shouldBe List(Vector(), Vector(2))
}
"apply the strategy to all children" in {
implicit val timeout: Timeout = 3 seconds
val parentActor = system.actorOf(Props(new OneForOneParentActor(testActor)))
val initialStateFuture = parentActor ? "state"
val initialState = Await.result(initialStateFuture, timeout.duration)
initialState shouldBe List(Vector(), Vector())
parentActor ! ("first", AddStateCommand(1))
parentActor ! ("second", AddStateCommand(2))
val currentStateFuture = parentActor ? "state"
val currentState = Await.result(currentStateFuture, timeout.duration)
currentState shouldBe List(Vector(1), Vector(2))
parentActor ! "throwFirst"
val stateAfterRestartFuture = parentActor ? "state"
val stateAfterRestart = Await.result(stateAfterRestartFuture, timeout.duration)
stateAfterRestart shouldBe List(Vector(), Vector())
}
}
}
但这些方法都没有帮助。如何使我的代码按预期工作?是否还有其他可以改进的领域(比如在儿童演员中设置人工状态只是为了知道他们已经重新启动了?)
SupervisorStrategiesTest:
object StateHoldingActor {
case class ActorThrowCommand()
case class AddStateCommand(stateElement: Int)
case class GetStateCommand()
case class GetStateCommandWithResponse()
def props(receiver: ActorRef): Props = Props(new StateHoldingActor())
}
class StateHoldingActor() extends Actor with ActorLogging {
log.info("about to create state")
private var state = Vector[Int]()
log.info(s"state created: $state")
import StateHoldingActor._
override def receive: Receive = {
case AddStateCommand(i) =>
log.info(s"extending state: $state")
state = i +: state
log.info(s"extended state: $state")
case GetStateCommand() =>
log.info(s"returning state: $state")
sender ! state
case GetStateCommandWithResponse() =>
log.info(s"returning state in response: $state")
sender ! state
case _: ActorThrowCommand =>
log.info(s"throwing exception with state: $state")
throw new IllegalStateException("Should crash actor instance and restart state")
}
}
StateHoldingActor:
abstract class ParentActor(recipient: ActorRef) extends Actor with ActorLogging {
log.info("creating children")
private val stateHoldingActor1 = context
.actorOf(Props(new StateHoldingActor()).withDispatcher(CallingThreadDispatcher.Id))
private val stateHoldingActor2 = context
.actorOf(Props(new StateHoldingActor()).withDispatcher(CallingThreadDispatcher.Id))
log.info("children created")
implicit val timeout: Timeout = 3 seconds
import scala.concurrent.ExecutionContext.Implicits.global
override def receive: Receive = {
case "throwFirst" =>
log.info("stateHoldingActor1 ! ActorThrowCommand")
stateHoldingActor1 ! ActorThrowCommand
case "throwSecond" =>
log.info("stateHoldingActor1 ! ActorThrowCommand")
stateHoldingActor2 ! ActorThrowCommand
case "state" =>
log.info("gathering states")
val futureResults: Future[List[Any]] = Future
.sequence(List(stateHoldingActor1 ? GetStateCommand, stateHoldingActor2 ? GetStateCommand))
import akka.pattern.pipe
futureResults pipeTo sender()
case ("first", msg@AddStateCommand(_)) => stateHoldingActor1 forward msg
case ("second", msg@AddStateCommand(_)) => stateHoldingActor2 forward msg
}
}
ParentActor:
class OneForOneParentActor(recipient: ActorRef) extends ParentActor(recipient) {
override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() {
case _ => Restart
}
}
OneForOneParentActor:
class AllForOneParentActor(recipient: ActorRef) extends ParentActor(recipient) {
override def supervisorStrategy: SupervisorStrategy = AllForOneStrategy() {
case _ => Restart
}
}
allForOneParentActor:
SQL> select a.book_id, count(a.book_id)
from
a
group by a.book_id ;
BOOK_ID COUNT(A.BOOK_ID)
--------- ----------------
1 2
2 2
3 2
4 2
5 2
6 3
答案 0 :(得分:3)
您将无参数消息声明为案例类(带括号),但您的ParentActor
实现不带括号发送,因此只发送类型,而不是实际实例。这意味着在StateHoldingActor
赢得的匹配中接收方法(查找实例),ask
永远不会返回。
e.g。 stateHoldingActor1 ? GetStateCommand(), stateHoldingActor2 ? GetStateCommand()
代替stateHoldingActor1 ? GetStateCommand, stateHoldingActor2 ? GetStateCommand
修好后,您的第一次测试应该完成。为您的消息使用案例对象可能是个好主意,这些消息不需要参数。然后这不会再发生了。
第二次测试仍然失败。其中一个原因可能是您仍然在第二个测试中使用OneForOneParentActor
,您可能希望测试AllForOneParentActor
。我正在研究另一个原因;)发布这个答案,以便您也可以查看其他问题。
修改
第二次测试仅仅因为竞争条件而失败。当最后一次请求状态(stateAfterRestartFuture
)时,第一个actor已经失败,因为异常,但是第二个actor还没有重启(在&#34之后添加Thread.sleep; throwFirst"来测试)。
EDIT2
我使用我用来测试/修复的代码创建了一个github存储库:https://github.com/thwiegan/so_ActorSupervisionTest
EDIT3
回应您的评论,当我从GitHub代码运行第二个测试时,会发生以下情况:
[INFO] [06/19/2017 10:32:07.734] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b] creating children
[INFO] [06/19/2017 10:32:07.735] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b] children created
[INFO] [06/19/2017 10:32:07.735] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b] gathering states
[INFO] [06/19/2017 10:32:07.736] [testSystem-akka.actor.default-dispatcher-6] [akka://testSystem/user/$b/$a] returning state: Vector()
[INFO] [06/19/2017 10:32:07.736] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/$b/$b] returning state: Vector()
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/$b] gathering states
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-6] [akka://testSystem/user/$b/$a] extended state: Vector(3)
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b/$b] extended state: Vector(4)
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-6] [akka://testSystem/user/$b/$a] returning state: Vector(3)
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b/$b] returning state: Vector(4)
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/$b] stateHoldingActor1 ! ActorThrowCommand
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/$b] gathering states
[INFO] [06/19/2017 10:32:07.737] [testSystem-akka.actor.default-dispatcher-5] [akka://testSystem/user/$b/$a] throwing exception with state: Vector(3)
[INFO] [06/19/2017 10:32:07.738] [testSystem-akka.actor.default-dispatcher-6] [akka://testSystem/user/$b/$b] returning state: Vector(4)
[INFO] [06/19/2017 10:32:07.741] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/$b] Children crashed
[ERROR] [06/19/2017 10:32:07.741] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/$b/$a] Should crash actor instance and restart state
java.lang.IllegalStateException: Should crash actor instance and restart state
[INFO] [06/19/2017 10:32:07.752] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b/$a] About to restart actor with state: Vector(3)
[INFO] [06/19/2017 10:32:07.753] [testSystem-akka.actor.default-dispatcher-6] [akka://testSystem/user/$b/$b] About to restart actor with state: Vector(4)
[INFO] [06/19/2017 10:32:07.753] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b/$a] returning state: Vector()
[INFO] [06/19/2017 10:32:07.753] [testSystem-akka.actor.default-dispatcher-6] [akka://testSystem/user/$b] gathering states
[INFO] [06/19/2017 10:32:07.754] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/$b/$a] returning state: Vector()
[INFO] [06/19/2017 10:32:07.754] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/$b/$b] returning state: Vector()
正如你所看到的,当ParentActor
试图在throwFirst
命令之后立即收集状态时,第二个有状态的actor(Vector(4))在第一个有状态的actor之前返回它的状态( Vector(3))将其崩溃传播给ParentActor(它只需要时间)。这就是为什么这是崩溃传播到ParentActor
- 因此对所有有状态的演员 - 以及聚集状态命令之间的竞争条件。
由于我的测试没有通过你的情况,我认为,一些参数(机器时间或任何延迟)是不同的。
编辑
作为对您评论的回复: 在重新启动期间,ParentActor已完成服务处理状态查询。由于你只询问两个StatefulActors然后将期货传递给pipeTo模式,所以ParentActor不再需要触及这个未来,所以它可以继续处理任何进来。在这种情况下,这是一个崩溃报告它的孩子们。因此,当第一个StatefulActor崩溃然后在重新启动后排队等待处理的状态查询时,第二个StatefulActor接收状态查询,因此在接收restart命令之前服务它。因此,它在某种意义上被并发处理,即ParentActor处理崩溃,而在不同的未来执行的pipeTo模式继续运行状态查询。在这种情况下,缓解这种情况的一个选择是停止子actor而不是重新启动它们。这将使管道未来的时间超时,因为第一个演员不会响应,因此没有,可能不一致的状态将被泄露。