3

New to Akka. Creating a new Scala class that extends SupervisorStrategy gives me the following template to work with:

class MySupervisorStrategy extends SupervisorStrategy {
  override def decider: Decider = ???

  override def handleChildTerminated(context: ActorContext, child: ActorRef,
    children: Iterable[ActorRef]): Unit = ???

  override def processFailure(context: ActorContext, restart: Boolean,
    child: ActorRef, cause: Throwable, stats: ChildRestartStats, children: Iterable[ChildRestartStats]): Unit = ???
}

I'm looking for a way to access:

  1. The Throwable/Exception that was thrown from the child actor
  2. The child actor ActorRef that threw the exception
  3. The message that was passed to the child actor that prompted the exception to be thrown

I think the Decider (which is actually a PartialFunction[Throwable,Directive]) gets passed the Throwable whenever the child throws the exception, but I'm not seeing where I could get access to #2 and #3 from my list above. Any ideas?


Update

From the posted fiddle, it looks like a valid Decider is:

{
    case ActorException(ref,t,"stop")      =>
      println(s"Received 'stop' from ${ref}")
      Stop
    case ActorException(ref,t,"restart")      =>
      println(s"Received 'restart' from ${ref}")
      Restart
    case ActorException(ref,t,"resume")      =>
      println(s"Received 'resume' from ${ref}")
      Resume
}

Above, I see all three:

  1. The exception that was thrown by the child
  2. The child (ref) that threw the exception
  3. The message that was sent to the child originally (that caused the exception to be thrown)

It looks like there's nothing in that Decider that needs to be defined inside that Supervisor class. I'd like to pull the Decider logic out into, say, MyDecider.scala and find a way to refactor the Supervisor so that its supervisorStrategy uses an instance of MyDecider, so maybe something similar to:

class Supervisor extends Actor {
  import akka.actor.OneForOneStrategy
  import akka.actor.SupervisorStrategy._
  import scala.concurrent.duration._

  var child: ActorRef = _

  override val supervisorStrategy =
    OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute, decider = myDecider)

  ...
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
hotmeatballsoup
  • 385
  • 6
  • 58
  • 136

1 Answers1

3

For #2 you could access the sender "if the strategy is declared inside the supervising actor"

If the strategy is declared inside the supervising actor (as opposed to within a companion object) its decider has access to all internal state of the actor in a thread-safe fashion, including obtaining a reference to the currently failed child (available as the sender of the failure message).

The message is not made available so the only option is to catch your exception and throw a custom one with the message that was received.

Here is a quick fiddle

class ActorSO extends Actor {

  def _receive: Receive = {
    case e =>
      println(e)
      throw new RuntimeException(e.toString)
  }

  final def receive = {
    case any => try {
      _receive(any)
    }
    catch {
      case t:Throwable => throw new ActorException(self,t,any)
    }

  }

}

Update

A Decider is just a PartialFunction so you can pass it in the constructor.

object SupervisorActor {
  def props(decider: Decider) = Props(new SupervisorActor(decider))
}

class SupervisorActor(decider: Decider) extends Actor {

  override val supervisorStrategy = OneForOneStrategy()(decider)

  override def receive: Receive = ???
}

class MyDecider extends Decider {
  override def isDefinedAt(x: Throwable): Boolean = true

  override def apply(v1: Throwable): SupervisorStrategy.Directive = {
    case t:ActorException => Restart
    case notmatched => SupervisorStrategy.defaultDecider.apply(notmatched)
  }
}

object Test {
  val myDecider: Decider = {
    case t:ActorException => Restart
    case notmatched => SupervisorStrategy.defaultDecider.apply(notmatched)
  }
  val myDecider2 = new MyDecider()
  val system = ActorSystem("stackoverflow")
  val supervisor = system.actorOf(SupervisorActor.props(myDecider))
  val supervisor2 = system.actorOf(SupervisorActor.props(myDecider2))
}

By doing so, you won't be able to access supervisor state like the ActorRef of the child that throw the exception via sender() (although we are including this in the ActorException)

Regarding your original question of accessing from the supervisor the child message that cause the exception, you can see here (from akka 2.5.3) that the akka developers choose to not make it available for decision.

  final protected def handleFailure(f: Failed): Unit = {
    // ¡¡¡ currentMessage.message is the one that cause the exception !!!
    currentMessage = Envelope(f, f.child, system)
    getChildByRef(f.child) match {
      /*
       * only act upon the failure, if it comes from a currently known child;
       * the UID protects against reception of a Failed from a child which was
       * killed in preRestart and re-created in postRestart
       */
      case Some(stats) if stats.uid == f.uid ⇒
        // ¡¡¡ currentMessage.message is not passed to the handleFailure !!!
        if (!actor.supervisorStrategy.handleFailure(this, f.child, f.cause, stats, getAllChildStats)) throw f.cause
      case Some(stats) ⇒
        publish(Debug(self.path.toString, clazz(actor),
          "dropping Failed(" + f.cause + ") from old child " + f.child + " (uid=" + stats.uid + " != " + f.uid + ")"))
      case None ⇒
        publish(Debug(self.path.toString, clazz(actor), "dropping Failed(" + f.cause + ") from unknown child " + f.child))
    }
  }
gabrielgiussi
  • 9,245
  • 7
  • 41
  • 71
  • Awesome answer, thanks @gabrielgiussi (+1) -- so my motivation for putting the `SupervisorStrategy` into its own Scala class is to make it reusable across many actors (parents). Looking at your fiddle, couldn't I extend `OneForOneStrategy` in my own Scala class and use the same code you put in it, and get my desired effect? Then from inside of each parent (where I want to use this strategy) I could inject the class with an instance of `MyOneForOneStrategy` and then just set `override val supervisorStrategy = myOneForOneeStrategy`, wouldn't this work? Thanks again! – hotmeatballsoup May 17 '18 at 18:04
  • There should be a really good reason to extend SupervisorStrategy instead of [using the provided ones](https://github.com/akka/akka/blob/fe3935a649294cdbd19fb0773f270d4dd412cf55/akka-actor/src/main/scala/akka/actor/FaultHandling.scala#L252). You should be ok defining only the Decider. What you are trying to do? Why you need access to the message, child ref and exception? – gabrielgiussi May 17 '18 at 18:37
  • I want to build in robust fault tolerance logic. I'm fine only defining the Decider if its possible to be reused across multiple actors and has access to all three items. LONG story short: I need the thrown Exception and the child that threw the exception to figure out whether the exception is recoverable or not (the exception by itself is not enough information, seeing that multiple children can throw the same exception under different circumstances). Then if it is recoverable, I need to take some custom action to possibly recover from it (run some code, wait a certain amount of time, etc.).. – hotmeatballsoup May 17 '18 at 18:47
  • ...then (again only if its recoverable) I need to replay the same message back to the child actor again, now that the problem has been fixed. Thats why I need all 3. – hotmeatballsoup May 17 '18 at 18:48
  • So @gabrielgiussi if you could show me a code example of creating a custom `Decider` and wiring that in to a dummy Actor's `SupervisorStrategy` I will accept your answer and give you the bounty! – hotmeatballsoup May 17 '18 at 20:02
  • Since the `Supervisor` actor in the fiddle already contains a custom `Decider` I need you to be more precise in what you want so I can help you. BTW the `Decider` is the `PartialFunction[Throwable, Directive]` that is passed to the `OneForOneStrategy` constructor – gabrielgiussi May 18 '18 at 02:05
  • Sure thing @gabrielgiussi! Please see my update above. Basically I'm wondering how I could take your `Decider` and externalize it into its own `MyDecider` class, and then inject the `Supervisor` class with it (or any other actor) and wire it into the `Supervisor#supervisorStrategy` field. – hotmeatballsoup May 18 '18 at 08:07
  • I'm checking something before answer you. – gabrielgiussi May 18 '18 at 12:49
  • I think is safe to access actor internal state from decider [but I don't have a confirmation from akka developers yet](https://discuss.lightbend.com/t/accessing-sender-from-decider-in-thread-safe-fashion/1081). The child failure is handled as a message sent to the supervisor so I think is totally safe. – gabrielgiussi May 23 '18 at 19:39