28
votes

I want to have the possibility to put actors to sleep for a while. The actors should decide themselves how long they are going to sleep. As Thread.sleep() is not a recommended way of doing this I thought of using the scheduler in akka. I therefore defined an actor were another actor can register for being woken up.

class Scheduler extends Actor {

  def receive = {
    case Sleep(duration) => context.system.scheduler.scheduleOnce(duration) {
      sender ! Ring
    }
  }
}

But the sending actor never receives the Ring message. So my questions are

  • Is scheduling with the scheduler recommended inside an actor?
  • Why is the sending actor never receiving the Ring message?
  • If this is not possible what is the recommended way of solving the problem?
3

3 Answers

45
votes

Let me first answer the title question: yes, it is possible to use the scheduler inside an actor.

case Sleep(duration) =>
  context.system.scheduler.scheduleOnce(duration, self, Ring)

Now to the question behind the question

You did not say what you actually want to achieve, so I’m making an educated guess here that you want the actor—which normally does something called “X”—to do something called “Y” for a while, suspending the “X” activity. The full solutions to this would be

class Sleepy extends Actor {
  def receive = {

    ... // cases doing “X”

    case Sleep(duration) =>
      case object WakeUp
      context.system.scheduler.scheduleOnce(duration, self, WakeUp)
      context.become({
        case WakeUp => context.unbecome()
        // drop the rest
      }, discardOld = false)
  }
}

The same could as well be implemented using the FSM trait and switching between the normal and the sleeping state. And of course you can do whatever you want while sleeping, e.g. mix in the Stash trait in Akka 2.1 and call stash() for all (or some) messages while sleeping, unstashAll() when getting the WakeUp message; or you could do something else altogether. Actors are very flexible.

What actors don’t do

Actors never really sleep, they always handle incoming messages. As shown above, you can define what that means, but the basic principle is that you cannot suspend an actor such that it will not process the messages in its mailbox.

16
votes

You are closing over "sender" in your closure that's passed to the scheduler. This means that the Ring message is most likely being sent to the wrong actor. You should do this instead:

case Sleep(duration) => 
  val s = sender
  context.system.scheduler.scheduleOnce(duration) {
    s ! Ring
  }
}
0
votes

Roland Kuhn's answer covers the subject, I just wanted to add that there's another common use-case for using the scheduler: when sending a message to a different actor and waiting for this actor to respond, it's quite common to cap the wait with a timeout.

otherActor ! Request(...)
context.system.scheduler.scheduleOnce(duration, self, WakeUp)
...
case Response(...) => ...
case WakeUp => context stop self