diff --git a/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala index 5b6d70738e..e951760cd9 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/HotSwapSpec.scala @@ -7,144 +7,109 @@ package akka.actor import akka.testkit._ @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) -class HotSwapSpec extends AkkaSpec { +class HotSwapSpec extends AkkaSpec with ImplicitSender { "An Actor" must { "be able to hotswap its behavior with HotSwap(..)" in { - val barrier = TestBarrier(2) - @volatile - var _log = "" val a = system.actorOf(new Actor { - def receive = { case _ ⇒ _log += "default" } - }) - a ! HotSwap(context ⇒ { - case _ ⇒ - _log += "swapped" - barrier.await + def receive = { case _ ⇒ sender ! "default" } }) + a ! HotSwap(context ⇒ { case _ ⇒ context.sender ! "swapped" }) a ! "swapped" - barrier.await - _log must be("swapped") + expectMsg("swapped") } "be able to hotswap its behavior with become(..)" in { - val barrier = TestBarrier(2) - @volatile - var _log = "" val a = system.actorOf(new Actor { def receive = { - case "init" ⇒ - _log += "init" - barrier.await - case "swap" ⇒ context.become({ - case _ ⇒ - _log += "swapped" - barrier.await - }) + case "init" ⇒ sender ! "init" + case "swap" ⇒ context.become({ case x: String ⇒ context.sender ! x }) } }) a ! "init" - barrier.await - _log must be("init") - - barrier.reset - _log = "" + expectMsg("init") a ! "swap" a ! "swapped" - barrier.await - _log must be("swapped") + expectMsg("swapped") } "be able to revert hotswap its behavior with RevertHotSwap(..)" in { - val barrier = TestBarrier(2) - @volatile - var _log = "" val a = system.actorOf(new Actor { def receive = { - case "init" ⇒ - _log += "init" - barrier.await + case "init" ⇒ sender ! "init" } }) a ! "init" - barrier.await - _log must be("init") - - barrier.reset - _log = "" - a ! HotSwap(context ⇒ { - case "swapped" ⇒ - _log += "swapped" - barrier.await - }) + expectMsg("init") + a ! HotSwap(context ⇒ { case "swapped" ⇒ context.sender ! "swapped" }) a ! "swapped" - barrier.await - _log must be("swapped") + expectMsg("swapped") - barrier.reset - _log = "" a ! RevertHotSwap a ! "init" - barrier.await - _log must be("init") + expectMsg("init") // try to revert hotswap below the bottom of the stack - barrier.reset - _log = "" a ! RevertHotSwap a ! "init" - barrier.await - _log must be("init") + expectMsg("init") } "be able to revert hotswap its behavior with unbecome" in { - val barrier = TestBarrier(2) - @volatile - var _log = "" val a = system.actorOf(new Actor { def receive = { - case "init" ⇒ - _log += "init" - barrier.await + case "init" ⇒ sender ! "init" case "swap" ⇒ context.become({ case "swapped" ⇒ - _log += "swapped" - barrier.await + sender ! "swapped" case "revert" ⇒ context.unbecome() }) - barrier.await } }) a ! "init" - barrier.await - _log must be("init") - - barrier.reset - _log = "" + expectMsg("init") a ! "swap" - barrier.await - barrier.reset - _log = "" a ! "swapped" - barrier.await - _log must be("swapped") + expectMsg("swapped") - barrier.reset - _log = "" a ! "revert" a ! "init" - barrier.await - _log must be("init") + expectMsg("init") + } + + "revert to initial state on restart" in { + + val a = system.actorOf(new Actor { + def receive = { + case "state" ⇒ sender ! "0" + case "swap" ⇒ + context.become({ + case "state" ⇒ sender ! "1" + case "swapped" ⇒ sender ! "swapped" + case "crash" ⇒ throw new Exception("Crash (expected)!") + }) + sender ! "swapped" + } + }) + a ! "state" + expectMsg("0") + a ! "swap" + expectMsg("swapped") + a ! "state" + expectMsg("1") + EventFilter[Exception](message = "Crash (expected)!", occurrences = 1) intercept { a ! "crash" } + a ! "state" + expectMsg("0") } } } diff --git a/akka-actor/src/main/scala/akka/actor/ActorCell.scala b/akka-actor/src/main/scala/akka/actor/ActorCell.scala index 494f048ccb..cef9fd60f6 100644 --- a/akka-actor/src/main/scala/akka/actor/ActorCell.scala +++ b/akka-actor/src/main/scala/akka/actor/ActorCell.scala @@ -364,6 +364,7 @@ private[akka] final class ActorCell( } } actor = freshActor // assign it here so if preStart fails, we can null out the sef-refs next call + hotswap = Props.noHotSwap // Reset the behavior freshActor.postRestart(cause) if (system.settings.DebugLifecycle) system.eventStream.publish(Debug(self.path.toString, "restarted")) @@ -534,6 +535,7 @@ private[akka] final class ActorCell( } finally { currentMessage = null clearActorFields() + hotswap = Props.noHotSwap } } } diff --git a/akka-docs/scala/actors.rst b/akka-docs/scala/actors.rst index 02bbc34012..4e54c58ecb 100644 --- a/akka-docs/scala/actors.rst +++ b/akka-docs/scala/actors.rst @@ -487,6 +487,10 @@ Both of these takes a ``ActorRef => PartialFunction[Any, Unit]`` that implements the new message handler. The hotswapped code is kept in a Stack which can be pushed and popped. +.. warning:: + + Please note that the actor will revert to its original behavior when restarted by its Supervisor. + To hotswap the Actor body using the ``HotSwap`` message: .. code-block:: scala