Skip to content

Instantly share code, notes, and snippets.

@richardimaoka
Last active June 18, 2017 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save richardimaoka/28d8e754bdba21f6d485b163c6767e7c to your computer and use it in GitHub Desktop.
Save richardimaoka/28d8e754bdba21f6d485b163c6767e7c to your computer and use it in GitHub Desktop.
Akka Actor's restart and stop followed by actor re-creation
lazy val root = (project in file(".")).
settings(
name := "root",
version := "1.0",
scalaVersion := "2.12.2",
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.5.0"
)
)
//Place it under src/main/scala/my/actor
package my.actor
import akka.actor.SupervisorStrategy.{Restart, Stop}
import akka.actor.{Actor, ActorInitializationException, ActorKilledException, ActorRef, ActorSystem, DeathPactException, OneForOneStrategy, PoisonPill, Props, SupervisorStrategy}
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.Await
import scala.concurrent.duration._
class Parent extends Actor {
override def receive = {
case name: String =>
val ref = context.actorOf(Props[Child], name)
sender() ! ref
}
/**
* User overridable definition the strategy to use for supervising
* child actors.
*
* SupervisorStrategy.defaultStrategy
*/
override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy(){
case m: ActorInitializationException ⇒ {
println(s"Parent: supervision strategy received: ${m}, and `Stop`-ing the child")
Stop
}
case m: ActorKilledException ⇒ {
println(s"Parent: supervision strategy received: ${m}, and `Stop`-ing the child")
Stop
}
case m: DeathPactException ⇒ {
println(s"Parent: supervision strategy received: ${m}, and `Stop`-ing the child")
Stop
}
case m: Exception => {
println(s"Parent: supervision strategy received: ${m}, and `Restart`-ing the child")
Restart
}
}
}
class Child extends Actor {
println("Child : constructed")
override def receive = {
case "normal" =>
println("Child : Received 'normal' message, so just printing it out .")
case "kaboom" =>
println("Child : Received kaboom")
throw new Exception("kaboom!!")
case "stop" =>
println("Child : Received 'stop', so calling context.stop()")
context.stop(self)
case m: Any =>
println(s"Child : Received: ${m}")
}
override def preStart() = {
println(s"Child : preStart() called, with hashCode =${hashCode} (supposedly hashCode = object's memory address)")
}
override def postStop(): Unit = {
println("Child : postStop() called")
}
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
println(s"Child : preRestart() called for ${reason} and ${message}")
super.preRestart(reason, message)
}
override def postRestart(reason: Throwable): Unit = {
println(s"Child : postRestart() called for ${reason}")
super.postRestart(reason)
}
}
object MyActorIncarnation {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("MyActorIncarnation")
implicit val timeout = Timeout(1 seconds)
try{
println(
"""
|*********************************************
|
| Let's see how restart works
|
|*********************************************
|
""".stripMargin
)
val parent = system.actorOf(Props[Parent], "parent")
val fut = parent ? "child"
Thread.sleep(500) //wait until actors are fully created calling the constructor and preStart()
val child = Await.result(fut, 1 second).asInstanceOf[ActorRef]
println(s"main : child = ${child} (path#-incarnation uid)")
child ! "kaboom"
Thread.sleep(500) // wait until restart is fully done
child ! "normal"
Thread.sleep(100) // wait until the "normal" message is processed
println(
"""
|You must have seen:
| * Child actor's hashCode changed after restart
| * Child actor's constructor and preStart() are both called on restart
| * The Child actor handled the "normal" message after restart
""".stripMargin
)
println(
"""
|*********************************************
|
| Let's see how stop works
|
|*********************************************
|
""".stripMargin
)
child ! "stop"
Thread.sleep(500) // wait until stop is fully done
val fut2 = parent ? "child"
val child2 = Await.result(fut2, 1 second).asInstanceOf[ActorRef]
println(s"main : child2 = ${child2} (path#-incarnation uid)")
Thread.sleep(500) // wait until the new actor incarnation is fully ready
child ! "normal" // sending to an old, invalid ActorRef
Thread.sleep(100) // wait "normal" reaches at deadLetters
println(
"""
|You must have seen:
| * Child actor's hashCode changed in the new incarnation (after complete "stop")
| * Child actor's constructor and preStart() are both called in the new incarnation
| * AcreRef for the Child actor had a new uid (i.e.) confirming the new incarnation
| * Sending "normal" to the old ActorRef ended up in deadLetters
""".stripMargin
)
println(
"""
|*********************************************
|
| Receiving PoisonPill does the same thing as
| calling context.stop() for the child
|
|*********************************************
|
""".stripMargin
)
child ! PoisonPill
Thread.sleep(1000)
} finally {
println("main: terminating the system")
system.terminate()
}
}
}
*********************************************
Let's see how restart works
*********************************************
Child : constructed
Child : preStart() called, with hashCode =237942453 (supposedly hashCode = object's memory address)
main : child = Actor[akka://MyActorIncarnation/user/parent/child#583399484] (path#-incarnation uid)
Child : Received kaboom
Parent: supervision strategy received: java.lang.Exception: kaboom!!, and `Restart`-ing the child
Child : preRestart() called for java.lang.Exception: kaboom!! and Some(kaboom)
Child : postStop() called
[ERROR] [06/18/2017 23:04:36.383] [MyActorIncarnation-akka.actor.default-dispatcher-4] [akka://MyActorIncarnation/user/parent/child] kaboom!!
java.lang.Exception: kaboom!!
at my.actor.Child$$anonfun$receive$2.applyOrElse(MyActorIncarnation.scala:55)
at akka.actor.Actor.aroundReceive(Actor.scala:513)
at akka.actor.Actor.aroundReceive$(Actor.scala:511)
at my.actor.Child.aroundReceive(MyActorIncarnation.scala:46)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:519)
at akka.actor.ActorCell.invoke(ActorCell.scala:488)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Child : constructed
Child : postRestart() called for java.lang.Exception: kaboom!!
Child : preStart() called, with hashCode =1850612155 (supposedly hashCode = object's memory address)
Child : Received 'normal' message, so just printing it out .
You must have seen:
* Child actor's hashCode changed after restart
* Child actor's constructor and preStart() are both called on restart
* The Child actor handled the "normal" message after restart
*********************************************
Let's see how stop works
*********************************************
Child : Received 'stop', so calling context.stop()
Child : postStop() called
Child : constructed
main : child2 = Actor[akka://MyActorIncarnation/user/parent/child#1508670955] (path#-incarnation uid)
Child : preStart() called, with hashCode =443716504 (supposedly hashCode = object's memory address)
[INFO] [06/18/2017 23:04:37.983] [MyActorIncarnation-akka.actor.default-dispatcher-2] [akka://MyActorIncarnation/user/parent/child] Message [java.lang.String] from Actor[akka://MyActorIncarnation/deadLetters] to Actor[akka://MyActorIncarnation/user/parent/child#583399484] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
You must have seen:
* Child actor's hashCode changed in the new incarnation (after complete "stop")
* Child actor's constructor and preStart() are both called in the new incarnation
* AcreRef for the Child actor had a new uid (i.e.) confirming the new incarnation
* Sending "normal" to the old ActorRef will end up in deadLetters
*********************************************
Receiving PoisonPill does the same thing as
calling context.stop() for the child
*********************************************
main: terminating the system
Child : postStop() called
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment