메시지들은 Scheduler
를 직접적으로 사용함으로써 미래에 보내지도록 설정할 수 있다. 하지만, 스케줄링 기간이나 액터에서의 개별 메시지들은 named timer
들을 사용하는 것이 더 편리하고 안전하다.
스케줄링된 메시지들의 라이프사이클은 액터가 재시작되고 타이머로부터 제어가 된다면 관리하기 어려워진다.
import scala.concurrent.duration._
import akka.actor.Actor
import akka.actor.Timers
object MyActor {
private case object TickKey
private case object FirstTick
private case object Tick
}
class MyActor extends Actor with Timers {
import MyActor._
timers.startSingleTimer(TickKey, FirstTick, 500.millis)
def receive = {
case FirstTick =>
timers.startTimerWithFixedDelay(TickKey, Tick, 1.second)
case Tick =>
// ...
}
}
Scheduler documentation에 fixed-delay
와 fixed-rate
스케줄링의 차이점이 상세히 적혀있다. 하지만, 2개의 차이점을 구분하기 힘들고 어떤 것을 선택해야할 지 모른다면 위의 예시 코드처럼 startTimerWithFixedDelay
를 사용하면 된다.
(위의 스케줄러 문서는 나중에 자세히 정리할 예정이다.)
위의 타이머들은 그 타이머를 가지고 있는 actor의 라이프사이클 내에서 사용된다. 그리고 이 타이머들은 액터들이 재시작되거나 정지되면 취소된다. 이때, TimerScheduler
는 thread-safe하지 않고, 타이머를 가지고 있는 액터와 반드시 같이 사용해야 한다.
액터들은 ActorRefFactory에 있는 메서드인 stop
을 통해 액터들을 멈출 수 있다. context.stop(ActorRef)
형태로 사용한다. 이러한 stop 메서드는 액터 그 자신이나 자손 액터들 그리고 시스템을 위한 top-level 액터들을 멈출 때 사용한다.
액터의 실질적인 종료는 비동기적으로 실행된다.
class MyActor extends Actor {
val child: ActorRef = ???
def receive = {
case "interrupt-child" =>
context.stop(child)
case "done" =>
context.stop(self)
}
}
위는 stop
을 사용한 코드의 예제이다. 현재의 메시지를 처리하는 과정에서 액터는 멈춘다. 그러나, 메일박스의 추가적인 메시지는 처리되지 않는다. 기본적으로, 이 메시지들은 ActorSystem의 deadLetters
에 이 메시지들이 보내진다.
그리고, PoisonPill
이라는 메시지를 액터에게 보냄으로써 액터를 멈출 수 있다.
class PingActor extends Actor{
private val log = Logging(context.system, this)
private var countDown:Int = 100
override def receive: Receive = {
case Pong =>
log.info(s"${self.path} received pong, count down $countDown")
if (countDown > 0) {
countDown -= 1
sender() ! Ping
} else {
sender() ! PoisonPill
self ! PoisonPill
}
}
}
이전 포스트들에서 사용했던 클래스이다. 이때, PoisonPill
이라는 메시지를 보내는 것이다.
PoisonPill
이 보내지면 mailbox에 enqueue된다. mailbox에서는 이전에 enqueue되었던 메시지들을 모두 처리하고 PoisonPill을 처리한다. 그리고 액터를 멈춘다.
kill
역시 액터를 멈추게 하는 메시지이다. 하지만, PoisonPill과는 다르게 액터가 ActorKilledException
을 throw하도록 하며 failure를 발생시킨다. 액터는 연산을 멈추고 어떻게 이 failure를 다룰 것인지 물어본다. 즉, 액터를 재개할 것인지 뭇는것이다. 재시작하거나 완전히 종료시킨다.
context.watch(victim) // watch the Actor to receive Terminated message once it dies
victim ! Kill
expectMsgPF(hint = "expecting victim to terminate") {
case Terminated(v) if v == victim => v // the Actor has indeed terminated
}
gracefulStop
은 종료를 기다려야할 때나 몇몇 액터들의 종료 순서를 정해야할 때 사용하기 좋다.
import akka.pattern.gracefulStop
import scala.concurrent.Await
try {
val stopped: Future[Boolean] = gracefulStop(actorRef, 5 seconds, Manager.Shutdown)
Await.result(stopped, 6 seconds)
// the actor has been stopped
} catch {
// the actor wasn't stopped within 5 seconds
case e: akka.pattern.AskTimeoutException =>
}
gracefulStop
은 성공적으로 리턴되면, 액터의 postStop
이 실행된다.
Akka는 runtime에 메시지루프를 hotswapping하는 것을 도와준다.
hot swapping
hot swap이란 컴포넌트들을 전원이 들어와 있는 상태에서 지우거나 컴퓨터 시스템에 플러깅하는 행위를 뜻한다. 즉, 컴퓨터의 일부분이 컴퓨터나 서버의 재부팅이나 shut down 없이도 변경되는 것을 뜻한다.
become
을 사용하여 Hotswap을 하려면 다음과 같다.
class HotSwapActor extends Actor {
import context._
def angry: Receive = {
case "foo" => sender() ! "I am already angry?"
case "bar" => become(happy)
}
def happy: Receive = {
case "bar" => sender() ! "I am already happy :-)"
case "foo" => become(angry)
}
def receive = {
case "foo" => become(angry)
case "bar" => become(happy)
}
}
become
메서드 안에 들어가는 인자는 많은 곳에서 유용하다. 이것은 현재 행위를 대체하는데 사용된다. 즉, unbecome
을 사용하지 않는다면 다음 행위는 명시적으로 설치된다.