4

我遇到了一个 Akka 主管演员的问题。当子角色在未来结果的 onFailure 方法中抛出异常时,主管不会处理该错误(我想在发生 ConnectException 的情况下重新启动子角色)。

我正在使用 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(e) 通知发件人,但没有奏效,主管未处理异常。

作为一种解决方法,我找到了这种方法来让它工作:

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)
  }

}

这是 Akka 错误还是我做错了什么?

谢谢!

4

1 回答 1

4

I think that the problem is that the exception thrown inside the Future doesn't belong to the same thread (potentially) as the one the Actor is running (someone more experienced can elaborate on this). So, the problem is that the exception thrown inside the Future body is "swallowed" and not propagated to the Actor. Since this is the case, the Actor doesn't fail and so there's no need to apply the supervision strategy. So, the first solution that comes to my mind is to wrap the exception inside the Future in some message, send it to yourself, and then throw it from inside the Actor context itself. This time, the Exception will be caught and the supervision strategy will be applied. Note, however, that unless you send the Send(noti) message again, you will not see the Exception happening since the Actor was restarted. All in all, the code would be like this:

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)
  }

}

Hope it helped.

于 2014-11-22T17:51:21.637 回答