这是一个简单的函数,我写了一个定时重试做Akka“问”。有一个明显的竞争条件,我不知道如何解决。
def askWithRetry(actor: ActorRef, message: Any, timeout: Timeout): Future[Any] =
(actor ? message)(timeout) recoverWith { case e: AskTimeoutException =>
// do a retry. currently there is no retry limit for simplicity.
askWithRetry(actor, message, timeout)
}
通常,这有效。 “ask”或?
为每个调用创建一个临时中间actor。如果目标发送响应消息,则临时“ask actor”将结果作为成功完成将结果放入Future中。如果目标没有及时响应,则将来会以超时异常完成,并且recoverWith进程会重试。
然而,存在竞争条件。如果目标将响应消息发送给临时“ask actor”,但在响应消息之前处理超时,则响应消息将丢失。重试过程使用新的临时actor重新发送新请求。由于响应消息被发送到之前临时的“询问参与者”,现在已经不存在,因此不会被处理并丢失。
我该如何解决这个问题?
我可以使用内置的重试逻辑编写一个自定义版本的Ask模式来修复此竞争条件...如果有更多标准选项,我讨厌使用不必要的自定义代码。
更新:以下是我最终使用的自定义版本:
object AskWithRetry {
def askWithRetry(context: ActorContext, actor: ActorRef, message: Any, retryInterval: Duration, maxRetries: Option[Int]): Future[Any] = {
val p = Promise[Any]
val intermediate = context.actorOf(props(p, actor, message, retryInterval, maxRetries))
p.future
}
def props(promise: Promise[Any], target: ActorRef, message: Any, retryInterval: Duration, maxRetries: Option[Int]): Props =
Props(new AskWithRetryIntermediateActor(promise, target, message, retryInterval, maxRetries))
}
class AskWithRetryIntermediateActor(promise: Promise[Any], target: ActorRef, message: Any, retryInterval: Duration, var maxRetries: Option[Int]) extends Actor {
def doSend(): Unit = target ! message
def receive: Receive = {
case ReceiveTimeout =>
maxRetries match {
case None =>
//println(s"Retrying. Infinite tries left. ${message}")
doSend()
case Some(retryCount) =>
if (retryCount > 0) {
//println(s"Retrying. ${retryCount-1} tries left. ${message}")
maxRetries = Some(retryCount - 1)
doSend()
} else {
//println(s"Exceeded timeout limit. Failing. ${message}")
if (!promise.isCompleted) {
promise.failure(new AskTimeoutException("retry limit reached"))
}
context.stop(self)
}
}
case otherMessage: Any =>
if (!promise.isCompleted) {
//println(s"AskWithRetry: completing ${otherMessage}")
promise.success(otherMessage)
}
context.stop(self)
}
context.setReceiveTimeout(retryInterval)
doSend()
}
答案 0 :(得分:3)
我认为你的直觉很好。如果你想要自定义actor-logic,你应该写它。
自定义询问等待的玩家应该将消息发送到actor
并将scheduleOnce
消息发送到本身以重试。这样,响应和超时都通过receive
方法到达,并且您没有任何比赛。