8

When using the methods on the default Akka system scheduler (context().system().scheduler().schedule() from inside an actor), and one of the overloads accepting a destination actor, do I need to explicitly cancel using the returned Cancellable to free up resources when the destination actor stops?

I imagine the scheduler may be watch()ing the destination actor and automatically perform the cleanup but cannot find it explicitly state anywhere in the documentation.

Daenyth
  • 35,856
  • 13
  • 85
  • 124
SoftMemes
  • 5,602
  • 4
  • 32
  • 61

4 Answers4

11

The variants of Scheduler.schedule which take an ActorRef will not watch that actor (which would have a rather high overhead relative to what a timer task is), hence you should always clean up the recurring timer from the actor’s postStop hook.

In the local case we check target.isTerminated, but that method always returns false for actor references which are not of the bog-standard local kind, hence you can rely on this feature only in specific cases and your code would then stop working correctly when you scale out your application. Another consideration is that the aforementioned check runs when trying to send the message, which can be a “soft leak” (i.e. deferred cleanup) in case of long schedules (where depending on the use-case 100ms can already be long).

Roland Kuhn
  • 15,412
  • 2
  • 36
  • 45
  • 1
    This is the answer that you should mark as correct, SoftMemes. I'll add that I created a BaseActor class that offers common functionality to all of my actors. Among other things it maintains a list of Cancellables (a method `def cancelOnExit[ T <: Cancellable ]( c:T ):T = { cancellables ::= c; c }` throws a Cancellable on the list and returns it), and postStop does `cancellables foreach (_.cancel)`. – AmigoNico Jul 12 '13 at 03:41
  • Does this (cleaning up the `Cancellable` in `postStop`) hold true for events which are not recurring, i.e. created by `scheduleOnce`? – 2rs2ts Feb 26 '15 at 02:21
  • Just consider outstanding scheduler requests to have a cost: if you submit one for “next week” but the actor typically goes away quickly and millions come and go during that week, then yes, clean up. If the task will expire in a millisecond anyway, then there might not be much need to accelerate the cleanup. – Roland Kuhn Feb 26 '15 at 08:16
3

It looks like the task still runs, and the message more than likely ends up going to dead letter. You can see that behavior with the following code sample:

import akka.actor._
import scala.concurrent.duration._

object SchedTest {
  def main(args: Array[String]) {
    val sys = ActorSystem("test")
    val ref = sys.actorOf(Props[MyActor])

    val can = sys.scheduler.schedule(1 second, 1 second){
      println("executing")
      ref ! "foo"
    }(sys.dispatcher)

    Thread.sleep(10000)
    sys.stop(ref)
    Thread.sleep(5000)
    can.cancel
  }
}


class MyActor extends Actor{
  def receive = {
    case _ => 
      println("received message...")
  }
}

After 10 seconds, you will stop seeing the "received message..." string get printed, but you will continue to see the "executing" string get printed. Then after I manually kill the task, you stop seeing "executed" getting printed.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • Thanx I wanted to know that if a actor dies and before it died, it started a Schedule Task. Will scheduled Task run or not ? and I believe as per your reply the answer is YES. – Abdeali Chandanwala May 10 '17 at 11:16
2

I haven't tested this, but this is copied and pasted directly from the documentation (2.2-M3 though).

final def schedule(
   initialDelay: FiniteDuration,
   interval: FiniteDuration,
   receiver: ActorRef,
   message: Any)(implicit executor: ExecutionContext,
                            sender: ActorRef = Actor.noSender): Cancellable =
   schedule(initialDelay, interval, new Runnable {
      def run = {
         receiver ! message
         if (receiver.isTerminated)
            throw new SchedulerException("timer active for terminated actor")
      }
    })
agilesteel
  • 16,775
  • 6
  • 44
  • 55
  • 1
    Why on earth would it send the message first before checking if `receiver.isTerminated`... – 2rs2ts Feb 26 '15 at 02:19
1

The question is already quite old, but I'd like to mention that you often can avoid a repeating schedule.

In many cases an actor wants to send messages to itself at a later time. In that case, a useful pattern is to not use schedule, but let the message handler schedule a message to itself using scheduleOnce.

class MyActor extends Actor {
  def receive = {
    case "Message" =>
      context.system.scheduler.scheduleOnce(5.seconds, self, "Message")
  }
}

The scheduler will attempt a message delivery once after the actor has stopped but since the actor will be local, it will not even send the message (see Roland Kuhn's answer).

If you have a really large amount of actors that need scheduled messages, you may have to look at the other answers, but the approach above is often sufficient. You can even let the interval vary based on factors such as load etc.

gpremer
  • 11
  • 2