2014-10-17 97 views
5

在我的情況我有2個演員:阿卡:測試監控死看

  1. watchee(我用TestProbe
  2. watcherWatcher包裹成TestActorRef暴露了一些內部state我跟蹤我的測試)

watchee去世時,看守應採取一些措施。

下面是完整的測試情況下,我到目前爲止已經寫的:

class TempTest(_system: ActorSystem) extends TestKit(_system) with ImplicitSender with FunSuiteLike with Matchers with BeforeAndAfterAll { 

    def this() = this(ActorSystem("TempTest")) 

    override def afterAll { 
    TestKit.shutdownActorSystem(system) 
    } 

    class WatcherActor(watchee: ActorRef) extends Actor { 

    var state = "initial" 
    context.watch(watchee) 

    override def receive: Receive = { 
     case "start" => 
     state = "start" 
     case _: Terminated => 
     state = "terminated" 
    } 

    } 

    test("example") { 
    val watchee = TestProbe() 
    val watcher = TestActorRef[WatcherActor](Props(new WatcherActor(watchee.ref))) 

    assert(watcher.underlyingActor.state === "initial") 

    watcher ! "start" // "start" will be sent and handled by watcher synchronously 
    assert(watcher.underlyingActor.state === "start") 

    system.stop(watchee.ref) // will cause Terminated to be sent and handled asynchronously by watcher 
    Thread.sleep(100) // what is the best way to avoid blocking here? 
    assert(watcher.underlyingActor.state === "terminated") 
    } 

} 

現在,因爲所有相關參與者使用CallingThreadDispatcher(所有阿卡的測試傭工被使用的道具與.withDispatcher(CallingThreadDispatcher.Id)構成),我可以有把握地認爲,當這個語句返回:

watcher ! "start" 

...「開始」的消息已經被WatchingActor處理,從而使我能夠根據在watcher.underlyingActor.state

斷言

但是,根據我的觀察,當我停止使用watcheesystem.stop發送Kill以它爲watchee死亡的副作用被異步執行,另一個線程產生的Terminated消息。

不是一個解決方案是停止watchee,阻塞線程一段時間,然後驗證Watcher狀態,但我想知道如何以正確的方式做到這一點(即如何確保在殺死演員是觀察者收到並處理了Terminated消息表明它是死亡)?

回答

3

編輯: 經過討論和OP測試後,我們發現發送PoisonPill作爲終止受監視的actor的手段達到了預期的行爲,因爲從PPill中終止處理是同步處理的,而從停止或終止處理中處理異步。

雖然我們不確定原因,但我們最好的選擇是這是由於殺死演員引發異常而PPilling不引發異常。

顯然,這與使用gracefulStop模式無關,原因是我的原始建議,我將在下面報告。

總之,OP的問題的解決方案只是終止觀看的演員發送PPill,而不是發送Kill消息或執行system.stop。


老答案就從這裏開始:

我可能會建議的東西有點風馬牛不相及,但我覺得它可能適用。

如果我正確地理解你想要做的事情,基本上是同步地終止一個演員,即做一些只有一次演員正式死亡並且其死亡已被記錄(在你的情況下由觀察者)纔會返回的東西。

一般來說,死亡通知以及其他大部分的akka​​是異步的。儘管如此,使用gracefulStop模式(akka.pattern.gracefulStop)可以獲得同步死亡確認。

要做到這一點,則代碼應該類似於:

val timeout = 5.seconds 
val killResultFuture = gracefulStop(victimRef, timeout, PoisonPill) 
Await.result(killResultFuture, timeout) 

這樣做是發送PoisonPill到受害者(注意:您可以使用自定義消息),誰將會與未來的回覆一旦受害者死亡就完成了。使用Await.result你保證是同步的。

不幸的是,這僅僅是可用的,如果)你是積極殺害受害人,你不想要,而不是對死亡B的外部原因並採取措施)可以使用超時,並在你的代碼阻塞。但是,也許你可以根據你的情況調整這種模式。

+0

試過你的代碼片段,它工作。有趣的是,它受到幾乎同樣的問題,因爲初始@ cmbaxter的回答(等候的'gracefulStop'保證目標的演員的「posStop」後返回的結果,畢竟不是'Terminated',通過目標的演員去世消耗生產) 。我試圖理解爲什麼你的剪裁工作,事實證明,它不是'gracefulStop',它使它的工作,但事實上,你發送'PoisonPill'終止演員。 – 2014-10-24 07:35:06

+0

出於某種原因'Terminated'的,由演員死亡'PoisonPill'生產得到同步處理,而那些被死亡'Kill'​​或'system.stop'生產 - 異步。我還沒有想通了,爲什麼,還沒有,但最終這意味着,人們可以簡單地發送'PoisonPill'到'watchee'實現我的目標(知道什麼時候死的演員和所有'watcher's已確認),並且'gracefulStop'與解決這個問題無關(實際上,使用'gracefulStop'和'Kill'​​作爲消息介紹了我在上面評論中談到的競爭條件)。 – 2014-10-24 07:44:48

+0

我也不確定。難道那是因爲Kill拋出一個異常,而PPill不會呢? – 2014-10-24 19:40:14

5

解決此問題的一種方法是在您的測試中引入另一個觀察者,該觀察者也可以觀察watchee。這個其他的觀察者是一個TestProbe這將允許我們執行一個斷言,它將擺脫你所看到的時間問題。首先,修改後的測試代碼:

val watchee = TestProbe() 
val watcher = TestActorRef[WatcherActor](Props(new WatcherActor(watchee.ref))) 
val probeWatcher = TestProbe() 
probeWatcher watch watchee.ref 

assert(watcher.underlyingActor.state === "initial") 

watcher ! "start" // "start" will be sent and handled by watcher synchronously 
assert(watcher.underlyingActor.state === "start") 

system.stop(watchee.ref) // will cause Terminated to be sent and handled asynchronously by watcher 
probeWatcher.expectTerminated(watchee.ref) 
assert(watcher.underlyingActor.state === "terminated") 

所以你可以看到,我已經介紹了額外的守望與線:

val probeWatcher = TestProbe() 
probeWatcher watch watchee.ref 

然後,在後面的代碼,發生故障的最終說法前你我用另一種說法,讓我知道,Terminated消息停止演員已經正確分配:

probeWatcher.expectTerminated(watchee.ref)

當代碼移過這條線時,我可以肯定的是,被測試的watcher也收到了它的終止消息,並且隨後的斷言會通過。

EDIT

正如OP所指出的,有非確定性的與此代碼的水平。另一種可能的解決辦法是改變測試代碼,停止演員行:

watcher.underlyingActor.context.stop(watchee.ref) 

通過使用TestActorRefcontext我相信Terminated將全部通過CallingThreadDispatcher交付,因此是完全同步的。我在一個循環中測試了它,它爲我工作了超過1000次迭代。

現在我想,也許是因爲我使用的是期待,或許有一個優化交付Terminated以自我爲scanario的Terminated相同的演員進行stop,所以我也有完全不同的Actor測試這如下:

class FooActor extends Actor{ 
    def receive = { 
    case _ => 
    } 

然後在測試代碼:

val foo = TestActorRef(new FooActor) 

而就停止:

foo.underlyingActor.context.stop(watchee.ref) 

這也按預期工作。

+0

恐怕這不是一個解決方案。以您建議的方式重建測試引入競賽情節(確切地說,確保發送給'probeWatcher'的Terminated'將在Terminate發送給'watcher'後處理?)。通過運行您提供的代碼循環查看它。 – 2014-10-20 18:22:41

+0

@EugenyLoy,我已經更新了我的答案。 – cmbaxter 2014-10-20 18:51:33