Search code examples
scalaakka-supervision

Akka: How to combine OneForOneStrategy and AllForOneStrategy


If I define the supervisor strategy for an actor in Scala, how can I use both the OneForOneStrategy and the AllForOneStrategy? Is there a simple way to combine them or do I have to define a custom SupervisorStrategy? Here is an example:

class MyActor extends Actor {
  override val supervisorStrategy = OneForOneStrategy() {
    case _: NullPointerException  => Restart
    case _: FileNotFoundException => Restart // here I want to restart all children
    case _: Exception             => Escalate
  }
}

If I have to write my own supervisor strategy, how can I do this? I found no example for that.


Solution

  • You would have to define a custom supervisor strategy. For your specific use case, the following custom strategy would work:

    package akka.actor
    
    import java.io.FileNotFoundException
    import scala.concurrent.duration._
    
    case class CustomStrategy(
      maxNrOfRetries:              Int      = -1,
      withinTimeRange:             Duration = Duration.Inf,
      override val loggingEnabled: Boolean  = true)(val decider: SupervisorStrategy.Decider)
      extends SupervisorStrategy {
    
      import SupervisorStrategy._
    
      private val retriesWindow = (maxNrOfRetriesOption(maxNrOfRetries), 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 (cause.isInstanceOf[FileNotFoundException]) {
          // behave like AllForOneStrategy
          if (children.nonEmpty) {
            if (restart && children.forall(_.requestRestartPermission(retriesWindow)))
              children foreach (crs ⇒ restartChild(crs.child, cause, suspendFirst = (crs.child != child)))
            else
              for (c ← children) context.stop(c.child)
          }
        } else {
          // behave like OneForOneStrategy
          if (restart && stats.requestRestartPermission(retriesWindow))
            restartChild(child, cause, suspendFirst = false)
          else
            context.stop(child)
        }
      }
    }
    

    Here is a gist that tests the above strategy. The spec creates a supervisor that uses CustomStrategy and it creates two children. When one child throws a NullPointerException, only that child is restarted; when a child throws a FileNotFoundException, both children are restarted.

    A few notes about the custom strategy:

    • It extends SupervisorStrategy and is modeled after the existing strategies defined here.
    • It is defined inside the akka.actor package to have access to the package-private members there.
    • processFailure, one of the methods that must be overridden in classes that extend SupervisorStrategy, is called on Restart and Stop, so for your scenario, we define the custom behavior there.