2014-11-22 55 views
3

我面臨Akka監督演員的問題。當子actor在未來結果的onFailure方法中拋出異常時,主管不處理該錯誤(我想在出現ConnectException時重新啓動子進程)。Akka監督演員不處理異常當小孩演員在未來的onFailure內拋出異常

我使用的是Akka 2.3.7。

這是主管演員:

class MobileUsersActor extends Actor with ActorLogging { 

    import Model.Implicits._ 
    import Model.MobileNotifications 

    override val supervisorStrategy = 
    OneForOneStrategy(maxNrOfRetries = 3, withinTimeRange = 1 minute) { 
     case _: java.net.ConnectException => { 
     Logger.error("API connection error. Check your proxy configuration.") 
     Restart 
     } 
    } 

    def receive = { 
    case Start => findMobileUsers 
    } 

    private def findMobileUsers = { 
    val notis = MobileNotificationsRepository().find() 
    notis.map(invokePushSender) 
    } 

    private def invokePushSender(notis: List[MobileNotifications]) = { 
    notis.foreach { n => 
     val pushSender = context.actorOf(PushSenderActor.props) 
     pushSender ! Send(n) 
    } 
    } 

} 

這是兒童演員:

class PushSenderActor extends Actor with ActorLogging { 

    def receive = { 
    case Send(noti) => { 
     val response = sendPushNotification(noti) onFailure { 
     case e: ConnectException => throw e 
     } 
    } 
    } 

    private def sendPushNotification(noti: MobileNotifications): Future[WSResponse] = { 
    val message = "Push notification message example" 
    Logger.info(s"Push Notification >> $message to users " + noti.users) 
    PushClient.sendNotification(message, noti.users) 
    } 

} 

我試圖用一個akka.actor.Status.Failure(五)以通知發件人的是建議here,但沒有工作,例外保持未處理的主管。

作爲一種變通方法,我發現這種方式來得到它的工作:

class PushSenderActor extends Actor with ActorLogging { 

    def receive = { 
    case Send(noti) => { 
     val response = sendPushNotification(noti) onFailure { 
     case e: ConnectException => self ! APIConnectionError 
     } 
    } 
    case APIConnectionError => throw new ConnectException 
    } 

    private def sendPushNotification(noti: MobileNotifications): Future[WSResponse] = { 
    val message = "Push notification message example" 
    Logger.info(s"Push Notification >> $message to users " + noti.users) 
    PushClient.sendNotification(message, noti.users) 
    } 

} 

這是一個錯誤阿卡還是我做錯了什麼?

謝謝!

回答

2

我認爲問題在於未來內部引發的異常不屬於同一個線程(可能)與Actor正在運行的線程(更有經驗的人可以詳細說明這一點)。所以,問題在於Future框體內拋出的異常是「吞噬」而不是傳播給Actor。既然如此,演員不會失敗,所以不需要應用監督策略。所以,我想到的第一個解決方案是在Future中將異常封裝在某個消息中,將它發送給自己,然後將其從Actor上下文本身中拋出。這一次,例外將被捕獲並且監督策略將被應用。但請注意,除非您再次發送Send(noti)消息,否則您將不會看到自從Actor重新啓動以來發生的異常。總而言之,代碼將是這樣的:

class PushSenderActor extends Actor with ActorLogging { 

    case class SmthFailed(e: Exception) 

    def receive = { 
    case Send(noti) => { 
     val response = sendPushNotification(noti) onFailure { 
     case e: ConnectException => self ! SmthFailed(e) // send the exception to yourself 
     } 
    } 

    case SmthFailed(e) => 
     throw e // this one will be caught by the supervisor 
    } 

    private def sendPushNotification(noti: MobileNotifications): Future[WSResponse] = { 
    val message = "Push notification message example" 
    Logger.info(s"Push Notification >> $message to users " + noti.users) 
    PushClient.sendNotification(message, noti.users) 
    } 

} 

希望它有幫助。

+0

謝謝,您的解決方案與我找到的解決方法類似,這是我目前使用的解決方法(請參閱我的文章的最後部分),但我認爲Akka必須提供某些內容來處理而不是發送消息給自我然後拋出異常。 – 2014-11-22 19:48:12

+0

@GabrielVolpe不客氣。我認爲,只要你在未來之內,你就不能將這種異常傳播給調用線程。它的設計是爲了不以這種方式破壞它;未來的身體會自己捕獲異常,而你不能將它發出。即使使用akka.actor.Status.Failure(e),您也必須捕獲它並自行包裝。但我希望有一個聰明的方法))) – ale64bit 2014-11-22 19:52:32