import scala.concurrent.duration._
import akka.actor._
import benefits.configuration.Settings
/* Message used by `Child` to signal `Parent` it should
* be passivated by way of PoisonPill after a configurable
* idle time.
* */
case object Passivate
trait Parent extends Actor with Stash with ActorLogging {
import Parent._
import context._
private val settings =
Settings(system).Support.Passivation
def withPassivation(receive: Receive): Receive =
receive orElse passivation
private[support] def passivation: Receive = {
case Passivate ⇒ passivate(sender())
case Terminated(ref) ⇒ logUnexpected(ref)
case WaitTick(ref) ⇒ // no-op
}
private def passivate(entry: ActorRef) = {
log.debug(s"Passivation start: $entry")
watch(entry)
entry ! PoisonPill
val tick = scheduleTick(entry)
val await = awaiting(entry, tick)
become(await, discardOld = false)
}
private def awaiting(ref: ActorRef, tick: Cancellable): Receive = {
case Terminated(`ref`) ⇒ terminated(ref, tick)
case WaitTick(`ref`) ⇒ waitTick(ref)
case _ ⇒ stash()
}
private def terminated(ref: ActorRef, tick: Cancellable) = {
tick.cancel()
previousBehavior(ref)
log.debug(s"Passivation end: $ref")
}
private def waitTick(ref: ActorRef) = {
previousBehavior(ref)
log.warning(s"Giving up waiting for Terminated($ref)")
}
private def previousBehavior(ref: ActorRef) = {
unwatch(ref)
unbecome()
unstashAll()
}
private def logUnexpected(ref: ActorRef) =
log.warning(s"Unexpected terminated for $ref")
private def scheduleTick(entry: ActorRef) = {
system.scheduler.scheduleOnce(
waitTime, self, WaitTick(entry)
)
}
def waitTime: FiniteDuration =
settings.parentWaitTime
}
object Parent {
private[support] case class WaitTick(
entry: ActorRef
)
}
trait Child extends Actor with ActorLogging {
import Child._
import context._
private val settings =
Settings(system).Support.Passivation
override def preStart() = {
super.preStart()
self ! StartTimingOut
}
def passivator: ActorRef = parent
def withPassivation(receive: Receive): Receive =
receive orElse passivation
private[support] def passivation: Receive = {
case ReceiveTimeout ⇒ issuePassivate()
case StartTimingOut ⇒ startTimeout()
}
private def startTimeout() = {
log.debug(s"Setting receive timeout to $idleTime")
setReceiveTimeout(idleTime)
}
private def issuePassivate() = {
log.debug(s"Sending Passivate to $passivator")
setReceiveTimeout(Duration.Inf)
passivator ! Passivate
}
def idleTime: FiniteDuration =
settings.childIdleTime
}
object Child {
private[support] case object StartTimingOut
}