![JAR search and dependency download from the Maven repository](/logo.png)
akka.actor.FaultHandling.scala Maven / Gradle / Ivy
/**
* Copyright (C) 2009-2012 Typesafe Inc.
*/
package akka.actor
import java.util.concurrent.TimeUnit
import scala.collection.mutable.ArrayBuffer
import scala.collection.JavaConversions._
import java.lang.{ Iterable ⇒ JIterable }
import akka.util.Duration
case class ChildRestartStats(val child: ActorRef, var maxNrOfRetriesCount: Int = 0, var restartTimeWindowStartNanos: Long = 0L) {
def requestRestartPermission(retriesWindow: (Option[Int], Option[Int])): Boolean =
retriesWindow match {
case (Some(retries), _) if retries < 1 ⇒ false
case (Some(retries), None) ⇒ maxNrOfRetriesCount += 1; maxNrOfRetriesCount <= retries
case (x, Some(window)) ⇒ retriesInWindowOkay(if (x.isDefined) x.get else 1, window)
case (None, _) ⇒ true
}
private def retriesInWindowOkay(retries: Int, window: Int): Boolean = {
/*
* Simple window algorithm: window is kept open for a certain time
* after a restart and if enough restarts happen during this time, it
* denies. Otherwise window closes and the scheme starts over.
*/
val retriesDone = maxNrOfRetriesCount + 1
val now = System.nanoTime
val windowStart =
if (restartTimeWindowStartNanos == 0) {
restartTimeWindowStartNanos = now
now
} else restartTimeWindowStartNanos
val insideWindow = (now - windowStart) <= TimeUnit.MILLISECONDS.toNanos(window)
if (insideWindow) {
maxNrOfRetriesCount = retriesDone
retriesDone <= retries
} else {
maxNrOfRetriesCount = 1
restartTimeWindowStartNanos = now
true
}
}
}
trait SupervisorStrategyLowPriorityImplicits { this: SupervisorStrategy.type ⇒
/**
* Implicit conversion from `Seq` of Cause-Directive pairs to a `Decider`. See makeDecider(causeDirective).
*/
implicit def seqCauseDirective2Decider(trapExit: Iterable[CauseDirective]): Decider = makeDecider(trapExit)
// the above would clash with seqThrowable2Decider for empty lists
}
object SupervisorStrategy extends SupervisorStrategyLowPriorityImplicits {
sealed trait Directive
/**
* Resumes message processing for the failed Actor
*/
case object Resume extends Directive
/**
* Discards the old Actor instance and replaces it with a new,
* then resumes message processing.
*/
case object Restart extends Directive
/**
* Stops the Actor
*/
case object Stop extends Directive
/**
* Escalates the failure to the supervisor of the supervisor,
* by rethrowing the cause of the failure.
*/
case object Escalate extends Directive
/**
* Resumes message processing for the failed Actor
* Java API
*/
def resume = Resume
/**
* Discards the old Actor instance and replaces it with a new,
* then resumes message processing.
* Java API
*/
def restart = Restart
/**
* Stops the Actor
* Java API
*/
def stop = Stop
/**
* Escalates the failure to the supervisor of the supervisor,
* by rethrowing the cause of the failure.
* Java API
*/
def escalate = Escalate
/**
* When supervisorStrategy is not specified for an actor this
* is used by default. The child will be stopped when
* [[akka.ActorInitializationException]] or [[akka.ActorKilledException]]
* is thrown. It will be restarted for other `Exception` types.
* The error is escalated if it's a `Throwable`, i.e. `Error`.
*/
final val defaultStrategy: SupervisorStrategy = {
def defaultDecider: Decider = {
case _: ActorInitializationException ⇒ Stop
case _: ActorKilledException ⇒ Stop
case _: Exception ⇒ Restart
case _ ⇒ Escalate
}
OneForOneStrategy()(defaultDecider)
}
/**
* Implicit conversion from `Seq` of Throwables to a `Decider`.
* This maps the given Throwables to restarts, otherwise escalates.
*/
implicit def seqThrowable2Decider(trapExit: Seq[Class[_ <: Throwable]]): Decider = makeDecider(trapExit)
type Decider = PartialFunction[Throwable, Directive]
type JDecider = akka.japi.Function[Throwable, Directive]
type CauseDirective = (Class[_ <: Throwable], Directive)
/**
* Decider builder which just checks whether one of
* the given Throwables matches the cause and restarts, otherwise escalates.
*/
def makeDecider(trapExit: Array[Class[_]]): Decider =
{ case x ⇒ if (trapExit exists (_ isInstance x)) Restart else Escalate }
/**
* Decider builder which just checks whether one of
* the given Throwables matches the cause and restarts, otherwise escalates.
*/
def makeDecider(trapExit: Seq[Class[_ <: Throwable]]): Decider =
{ case x ⇒ if (trapExit exists (_ isInstance x)) Restart else Escalate }
/**
* Decider builder which just checks whether one of
* the given Throwables matches the cause and restarts, otherwise escalates.
*/
def makeDecider(trapExit: JIterable[Class[_ <: Throwable]]): Decider = makeDecider(trapExit.toSeq)
/**
* Decider builder for Iterables of cause-directive pairs, e.g. a map obtained
* from configuration; will sort the pairs so that the most specific type is
* checked before all its subtypes, allowing carving out subtrees of the
* Throwable hierarchy.
*/
def makeDecider(flat: Iterable[CauseDirective]): Decider = {
val directives = sort(flat)
{
case x ⇒ directives find (_._1 isInstance x) map (_._2) getOrElse Escalate
}
}
def makeDecider(func: JDecider): Decider = {
case x ⇒ func(x)
}
/**
* Sort so that subtypes always precede their supertypes, but without
* obeying any order between unrelated subtypes (insert sort).
*/
@deprecated("Will become private[akka] in 2.1, this is not user-api", "2.0.2")
def sort(in: Iterable[CauseDirective]): Seq[CauseDirective] =
(new ArrayBuffer[CauseDirective](in.size) /: in) { (buf, ca) ⇒
buf.indexWhere(_._1 isAssignableFrom ca._1) match {
case -1 ⇒ buf append ca
case x ⇒ buf insert (x, ca)
}
buf
}
private[akka] def withinTimeRangeOption(withinTimeRange: Duration): Option[Duration] =
if (withinTimeRange.isFinite && withinTimeRange >= Duration.Zero) Some(withinTimeRange) else None
private[akka] def maxNrOfRetriesOption(maxNrOfRetries: Int): Option[Int] =
if (maxNrOfRetries < 0) None else Some(maxNrOfRetries)
}
abstract class SupervisorStrategy {
import SupervisorStrategy._
def decider: Decider
/**
* This method is called after the child has been removed from the set of children.
*/
def handleChildTerminated(context: ActorContext, child: ActorRef, children: Iterable[ActorRef]): Unit
/**
* This method is called to act on the failure of a child: restart if the flag is true, stop otherwise.
*/
def processFailure(context: ActorContext, restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit
def handleSupervisorFailing(supervisor: ActorRef, children: Iterable[ActorRef]): Unit = {
if (children.nonEmpty)
children.foreach(_.asInstanceOf[InternalActorRef].suspend())
}
def handleSupervisorRestarted(cause: Throwable, supervisor: ActorRef, children: Iterable[ActorRef]): Unit = {
if (children.nonEmpty)
children.foreach(_.asInstanceOf[InternalActorRef].restart(cause))
}
/**
* Returns whether it processed the failure or not
*/
def handleFailure(context: ActorContext, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Boolean = {
val directive = if (decider.isDefinedAt(cause)) decider(cause) else Escalate
directive match {
case Resume ⇒ child.asInstanceOf[InternalActorRef].resume(); true
case Restart ⇒ processFailure(context, true, child, cause, stats, children); true
case Stop ⇒ processFailure(context, false, child, cause, stats, children); true
case Escalate ⇒ false
}
}
}
/**
* Applies the fault handling `Directive` (Resume, Restart, Stop) specified in the `Decider`
* to all children when one fails, as opposed to [[akka.actor.OneForOneStrategy]] that applies
* it only to the child actor that failed.
*
* @param maxNrOfRetries the number of times an actor is allowed to be restarted, negative value means no limit
* @param withinTimeRange duration of the time window for maxNrOfRetries, Duration.Inf means no window
* @param decider mapping from Throwable to [[akka.actor.SupervisorStrategy.Directive]], you can also use a
* `Seq` of Throwables which maps the given Throwables to restarts, otherwise escalates.
*/
case class AllForOneStrategy(maxNrOfRetries: Int = -1, withinTimeRange: Duration = Duration.Inf)(val decider: SupervisorStrategy.Decider)
extends SupervisorStrategy {
def this(maxNrOfRetries: Int, withinTimeRange: Duration, decider: SupervisorStrategy.JDecider) =
this(maxNrOfRetries, withinTimeRange)(SupervisorStrategy.makeDecider(decider))
def this(maxNrOfRetries: Int, withinTimeRange: Duration, trapExit: JIterable[Class[_ <: Throwable]]) =
this(maxNrOfRetries, withinTimeRange)(SupervisorStrategy.makeDecider(trapExit))
def this(maxNrOfRetries: Int, withinTimeRange: Duration, trapExit: Array[Class[_]]) =
this(maxNrOfRetries, withinTimeRange)(SupervisorStrategy.makeDecider(trapExit))
/*
* this is a performance optimization to avoid re-allocating the pairs upon
* every call to requestRestartPermission, assuming that strategies are shared
* across actors and thus this field does not take up much space
*/
private val retriesWindow = (
SupervisorStrategy.maxNrOfRetriesOption(maxNrOfRetries),
SupervisorStrategy.withinTimeRangeOption(withinTimeRange).map(_.toMillis.toInt))
def handleChildTerminated(context: ActorContext, child: ActorRef, children: Iterable[ActorRef]): Unit = {}
def processFailure(context: ActorContext, restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = {
if (children.nonEmpty) {
if (restart && children.forall(_.requestRestartPermission(retriesWindow)))
children.foreach(_.child.asInstanceOf[InternalActorRef].restart(cause))
else
for (c ← children) context.stop(c.child)
}
}
}
/**
* Applies the fault handling `Directive` (Resume, Restart, Stop) specified in the `Decider`
* to the child actor that failed, as opposed to [[akka.actor.AllForOneStrategy]] that applies
* it to all children.
*
* @param maxNrOfRetries the number of times an actor is allowed to be restarted, negative value means no limit
* @param withinTimeRange duration of the time window for maxNrOfRetries, Duration.Inf means no window
* @param decider mapping from Throwable to [[akka.actor.SupervisorStrategy.Directive]], you can also use a
* `Seq` of Throwables which maps the given Throwables to restarts, otherwise escalates.
*/
case class OneForOneStrategy(maxNrOfRetries: Int = -1, withinTimeRange: Duration = Duration.Inf)(val decider: SupervisorStrategy.Decider)
extends SupervisorStrategy {
def this(maxNrOfRetries: Int, withinTimeRange: Duration, decider: SupervisorStrategy.JDecider) =
this(maxNrOfRetries, withinTimeRange)(SupervisorStrategy.makeDecider(decider))
def this(maxNrOfRetries: Int, withinTimeRange: Duration, trapExit: JIterable[Class[_ <: Throwable]]) =
this(maxNrOfRetries, withinTimeRange)(SupervisorStrategy.makeDecider(trapExit))
def this(maxNrOfRetries: Int, withinTimeRange: Duration, trapExit: Array[Class[_]]) =
this(maxNrOfRetries, withinTimeRange)(SupervisorStrategy.makeDecider(trapExit))
/*
* this is a performance optimization to avoid re-allocating the pairs upon
* every call to requestRestartPermission, assuming that strategies are shared
* across actors and thus this field does not take up much space
*/
private val retriesWindow = (
SupervisorStrategy.maxNrOfRetriesOption(maxNrOfRetries),
SupervisorStrategy.withinTimeRangeOption(withinTimeRange).map(_.toMillis.toInt))
def handleChildTerminated(context: ActorContext, child: ActorRef, children: Iterable[ActorRef]): Unit = {}
def processFailure(context: ActorContext, restart: Boolean, child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = {
if (restart && stats.requestRestartPermission(retriesWindow))
child.asInstanceOf[InternalActorRef].restart(cause)
else
context.stop(child) //TODO optimization to drop child here already?
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy