akka.pattern.GracefulStopSupport.scala Maven / Gradle / Ivy
/**
* Copyright (C) 2009-2014 Typesafe Inc.
*/
package akka.pattern
import akka.actor._
import akka.util.{ Timeout }
import akka.dispatch.sysmsg.{ Unwatch, Watch }
import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.util.Success
import scala.concurrent.duration.FiniteDuration
trait GracefulStopSupport {
/**
* Returns a [[scala.concurrent.Future]] that will be completed with success (value `true`) when
* existing messages of the target actor has been processed and the actor has been
* terminated.
*
* Useful when you need to wait for termination or compose ordered termination of several actors,
* which should only be done outside of the ActorSystem as blocking inside Actors is discouraged.
*
* IMPORTANT NOTICE: the actor being terminated and its supervisor
* being informed of the availability of the deceased actor’s name are two
* distinct operations, which do not obey any reliable ordering. Especially
* the following will NOT work:
*
* {{{
* def receive = {
* case msg =>
* Await.result(gracefulStop(someChild, timeout), timeout)
* context.actorOf(Props(...), "someChild") // assuming that that was someChild’s name, this will NOT work
* }
* }}}
*
* If the target actor isn't terminated within the timeout the [[scala.concurrent.Future]]
* is completed with failure [[akka.pattern.AskTimeoutException]].
*
* If you want to invoke specialized stopping logic on your target actor instead of PoisonPill, you can pass your
* stop command as a parameter:
* {{{
* gracefulStop(someChild, timeout, MyStopGracefullyMessage).onComplete {
* // Do something after someChild being stopped
* }
* }}}
*/
def gracefulStop(target: ActorRef, timeout: FiniteDuration, stopMessage: Any = PoisonPill): Future[Boolean] = {
if (target.isTerminated) Future successful true
else {
val internalTarget = target.asInstanceOf[InternalActorRef]
val ref = PromiseActorRef(internalTarget.provider, Timeout(timeout), targetName = target.toString)
internalTarget.sendSystemMessage(Watch(internalTarget, ref))
target.tell(stopMessage, Actor.noSender)
ref.result.future.transform(
{
case Terminated(t) if t.path == target.path ⇒ true
case _ ⇒ { internalTarget.sendSystemMessage(Unwatch(target, ref)); false }
},
t ⇒ { internalTarget.sendSystemMessage(Unwatch(target, ref)); t })(ref.internalCallingThreadExecutionContext)
}
}
}