I'm curious if it's possible to safely implement a self-cancelling poller without using a var to keep the instance of akka.actor.Cancellable
So far, I came up with something like what you see in the example below. However, I'm curious if it's safe to assume that the "tick" message will never be dispatched before the hotswap happens, i.e the line that schedules the poller:
tick(1, 5, context.system.scheduler.schedule(Duration.Zero, 3 seconds, self, "tick"))
is basically the same as:
val poll = context.system.scheduler.schedule(Duration.Zero, 3 seconds, self, "tick")
tick(1, 5, poll)
So, I would think that in some cases the first tick would be received before the hotswap has a chance to happen... Thoughts?
import akka.actor.{Cancellable, ActorSystem}
import akka.actor.ActorDSL._
import concurrent.duration._
object PollerDemo {
def run() {
implicit val system = ActorSystem("DemoPoller")
import system.dispatcher
actor(new Act{
become {
case "tick" => println("UH-OH!")
case "start" =>
become {
tick(1, 5, context.system.scheduler.schedule(Duration.Zero, 3 seconds, self, "tick"))
}
}
def tick(curr:Long, max:Long, poll:Cancellable):Receive = {
case "tick" => {
println(s"poll $curr/$max")
if(curr > max)
cancel(poll)
else
become{ tick(curr + 1, max, poll) }
}
}
def cancel(poll:Cancellable) {
println("cancelling")
poll.cancel()
println(s"cancelled successfully? ${poll.isCancelled}")
println("shutting down")
context.system.shutdown()
}
}) ! "start"
system.awaitTermination(1 minute)
}
}
My guess is that your code will be okay. Remember, actors only process their mailbox one at a time. When you receive the start
message, you setup a timer that will deliver another message to the mailbox and then you swap the receive implementation. Because you do the receive swap while you are still processing the start
message, then you will have already changed the actors's receive behavior before it processes the next message in the mailbox. So when it moves on to process the tick
message you can be sure that it will be using the new receive behavior.
You could verify this by sending an additional tick
message inside the first become
like so:
become {
self ! "tick"
tick(1, 5, context.system.scheduler.schedule(Duration.Zero, 3 seconds, self, "tick"))
}
Here we are really eliminating the timer from the equation when asking if a message sent during the become block will be processed by the old receive or the new receive. I did not run this, but from my understanding or akka, both of these ticks should be handled by the new receive.