Last active
June 18, 2017 14:06
-
-
Save richardimaoka/28d8e754bdba21f6d485b163c6767e7c to your computer and use it in GitHub Desktop.
Akka Actor's restart and stop followed by actor re-creation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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() | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
********************************************* | |
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