softshipper
softshipper

Reputation: 34061

How to restart a killed actor?

I have a supervisor and a child actor that looks as the following:

SupervisorActor.scala

import akka.actor.{Actor, ActorLogging, Props, Terminated}

object SupervisorActor {

  def props: Props = Props(new SupervisorActor)

  object KillFoo
  object TerminateFoo
  object RestartFoo

}

final class SupervisorActor extends Actor with ActorLogging {

  import com.sweetsoft.SupervisorActor._

  log.info(s"Start the actor ${self}")

  private val foo = context.actorOf(FooActor.props, "FOOCHILD")

  context.watch(foo)

  override def receive: Receive = {
    case KillFoo =>
      context.stop(foo)
    case Terminated(actor) =>
      log.info(s"Kill actor $actor")
    case TerminateFoo =>
      log.info("Terminate FooActor")
      foo ! new IllegalArgumentException
    case RestartFoo =>
  }
}

FooActor.scala

import akka.actor.SupervisorStrategy.{Escalate, Restart, Resume, Stop}
import akka.actor.{Actor, ActorLogging, OneForOneStrategy, Props}
import scala.concurrent.duration._

object FooActor {
  def props: Props = Props(new FooActor)
}

final class FooActor extends Actor with ActorLogging {

  log.info(s"Start the actor ${ self }")

  override val supervisorStrategy: OneForOneStrategy =
    OneForOneStrategy(maxNrOfRetries = 1, withinTimeRange = 1.minute) {
      case _: ArithmeticException => Resume
      case _: NullPointerException => Restart
      case _: IllegalArgumentException =>
        println("STOP exception raised.")
        Stop
      case _: Exception => Escalate
    }

  override def receive: Receive = {
    case _ => log.info("I got killed.")
  }
}

and Main.scala

import akka.actor.ActorSystem
import com.sweetsoft.SupervisorActor.{TerminateFoo}

import scala.concurrent.Future
import scala.io.StdIn

object Main extends App {

  val system = ActorSystem("monarch")
  implicit val dispatcher = system.dispatcher

  try {
    // Create top level supervisor
    val supervisor = system.actorOf(SupervisorActor.props, "mupervisor")
    // Exit the system after ENTER is pressed
    Future {
     Thread.sleep(2000)
      supervisor ! TerminateFoo
    }

    StdIn.readLine()
  } finally {
    system.terminate()
  }

} 

After the FooActor got killed, I would like to restart it manually again, like I did:

private val foo = context.actorOf(FooActor.props, "FOOCHILD") 

How to do it?

I am thinking about to create a function, that is going to create the FooActor and after it got killed, just call the function to start a new FooActor.

Upvotes: 0

Views: 386

Answers (1)

Denis Makarenko
Denis Makarenko

Reputation: 2928

There are a few problems with the code. supervisorStrategy should be in the SupervisorActor as it is responsible for supervision, not the child actor itself.

foo ! new IllegalArgumentException won't cause the child actor to terminate as actors can accept any object as a message and Exception derived objects are not treated specially. It'll just print "I got killed." but otherwise ignore the message and keep running. In particular, it won't invoke supervisorStrategy handler.

You can either:

  • send the pre-defined system PoisonPill message to the child actor to gracefully stop it (i.e. after processing all pending messages)

  • send the pre-defined system Kill message to the child actor to stop it immediately after processing the current message and ignore the other queued messages if any.

  • forward TerminateFoo message to it and throw an exception in its handler. In this case the child object's destiny is decided by supervisorStrategy handler for IllegalArgumentException exception type (i.e. Stop in your case).

.

override def receive: Receive = {
    case TerminateFoo =>
      log.info("Stopping FooActor by throwing an unhandled exception for supervisorStrategy to process it")
      throw new IllegalArgumentException
    case m => log.info(s"I received a message $m")
  }

See https://doc.akka.io/docs/akka/current/actors.html#stopping-actors for details

Upvotes: 4

Related Questions