3

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.

Jojo
  • 357
  • 2
  • 10

1 Answers1

0

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.
Jeffrey Chung
  • 19,319
  • 8
  • 34
  • 54
  • I am suprised, that it has to be defined inside the `akk.actor`. To make it more general one could also override the OneForOneStartegy and add a `RestartAll` and a `StopAll` decider directive, right? – Jojo Jul 13 '17 at 08:33
  • Overriding the `OneForOneStrategy` should be avoided, because it is a case class. Extending the `Decider` type is also not possible, because it is sealed and the specs (`Resume`, `Restart`, ...) are case objects (so I can also not extend `Restart` for creating `RestartAll`). To make the example more general it is necessary to write a complete new SupervisorStrategy class. – Jojo Jul 14 '17 at 06:45