2012-01-24 11:59:57 +01:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
2009-08-17 20:46:05 +02:00
|
|
|
*/
|
2012-01-24 11:59:57 +01:00
|
|
|
|
2010-10-26 12:49:25 +02:00
|
|
|
package akka.actor
|
2009-08-17 18:42:41 +02:00
|
|
|
|
2011-10-17 18:34:34 +02:00
|
|
|
import akka.util.Duration
|
2012-01-25 15:38:04 +01:00
|
|
|
import org.jboss.netty.akka.util.{ TimerTask, HashedWheelTimer, Timeout ⇒ HWTimeout }
|
2012-01-17 09:34:34 +01:00
|
|
|
import akka.event.LoggingAdapter
|
|
|
|
|
import akka.dispatch.MessageDispatcher
|
|
|
|
|
import java.io.Closeable
|
|
|
|
|
|
2011-12-13 01:44:18 +01:00
|
|
|
//#scheduler
|
2011-12-06 16:27:10 +01:00
|
|
|
/**
|
|
|
|
|
* An Akka scheduler service. This one needs one special behavior: if
|
|
|
|
|
* Closeable, it MUST execute all outstanding tasks upon .close() in order
|
|
|
|
|
* to properly shutdown all dispatchers.
|
|
|
|
|
*
|
|
|
|
|
* Furthermore, this timer service MUST throw IllegalStateException if it
|
|
|
|
|
* cannot schedule a task. Once scheduled, the task MUST be executed. If
|
|
|
|
|
* executed upon close(), the task may execute before its timeout.
|
|
|
|
|
*/
|
2011-11-23 11:07:16 +01:00
|
|
|
trait Scheduler {
|
2011-11-23 15:15:44 +01:00
|
|
|
/**
|
2011-12-15 14:26:17 +01:00
|
|
|
* Schedules a message to be sent repeatedly with an initial delay and
|
|
|
|
|
* frequency. E.g. if you would like a message to be sent immediately and
|
2012-01-18 13:26:11 +01:00
|
|
|
* thereafter every 500ms you would set delay=Duration.Zero and
|
|
|
|
|
* frequency=Duration(500, TimeUnit.MILLISECONDS)
|
2011-12-13 01:44:18 +01:00
|
|
|
*
|
|
|
|
|
* Java & Scala API
|
2011-11-23 15:15:44 +01:00
|
|
|
*/
|
2011-12-15 14:26:17 +01:00
|
|
|
def schedule(
|
|
|
|
|
initialDelay: Duration,
|
|
|
|
|
frequency: Duration,
|
|
|
|
|
receiver: ActorRef,
|
|
|
|
|
message: Any): Cancellable
|
2011-11-23 15:15:44 +01:00
|
|
|
|
|
|
|
|
/**
|
2011-12-15 14:26:17 +01:00
|
|
|
* Schedules a function to be run repeatedly with an initial delay and a
|
|
|
|
|
* frequency. E.g. if you would like the function to be run after 2 seconds
|
|
|
|
|
* and thereafter every 100ms you would set delay = Duration(2, TimeUnit.SECONDS)
|
|
|
|
|
* and frequency = Duration(100, TimeUnit.MILLISECONDS)
|
2011-12-13 01:44:18 +01:00
|
|
|
*
|
|
|
|
|
* Scala API
|
2011-11-23 15:15:44 +01:00
|
|
|
*/
|
2011-12-15 14:26:17 +01:00
|
|
|
def schedule(
|
|
|
|
|
initialDelay: Duration, frequency: Duration)(f: ⇒ Unit): Cancellable
|
2011-11-23 15:15:44 +01:00
|
|
|
|
2011-12-14 00:06:36 +01:00
|
|
|
/**
|
2011-12-15 14:26:17 +01:00
|
|
|
* Schedules a function to be run repeatedly with an initial delay and
|
|
|
|
|
* a frequency. E.g. if you would like the function to be run after 2
|
|
|
|
|
* seconds and thereafter every 100ms you would set delay = Duration(2,
|
|
|
|
|
* TimeUnit.SECONDS) and frequency = Duration(100, TimeUnit.MILLISECONDS)
|
2011-12-14 00:06:36 +01:00
|
|
|
*
|
|
|
|
|
* Java API
|
|
|
|
|
*/
|
2011-12-15 14:26:17 +01:00
|
|
|
def schedule(
|
|
|
|
|
initialDelay: Duration, frequency: Duration, runnable: Runnable): Cancellable
|
2011-12-14 00:06:36 +01:00
|
|
|
|
2011-11-23 15:15:44 +01:00
|
|
|
/**
|
2011-12-15 14:26:17 +01:00
|
|
|
* Schedules a Runnable to be run once with a delay, i.e. a time period that
|
|
|
|
|
* has to pass before the runnable is executed.
|
2011-12-13 01:44:18 +01:00
|
|
|
*
|
|
|
|
|
* Java & Scala API
|
2011-11-23 15:15:44 +01:00
|
|
|
*/
|
2011-12-02 17:13:46 +01:00
|
|
|
def scheduleOnce(delay: Duration, runnable: Runnable): Cancellable
|
2011-11-23 15:15:44 +01:00
|
|
|
|
|
|
|
|
/**
|
2011-12-15 14:26:17 +01:00
|
|
|
* Schedules a message to be sent once with a delay, i.e. a time period that has
|
|
|
|
|
* to pass before the message is sent.
|
2011-12-13 01:44:18 +01:00
|
|
|
*
|
|
|
|
|
* Java & Scala API
|
2011-11-23 15:15:44 +01:00
|
|
|
*/
|
2011-12-02 17:13:46 +01:00
|
|
|
def scheduleOnce(delay: Duration, receiver: ActorRef, message: Any): Cancellable
|
2011-11-23 15:15:44 +01:00
|
|
|
|
|
|
|
|
/**
|
2011-12-15 14:26:17 +01:00
|
|
|
* Schedules a function to be run once with a delay, i.e. a time period that has
|
|
|
|
|
* to pass before the function is run.
|
2011-12-13 01:44:18 +01:00
|
|
|
*
|
|
|
|
|
* Scala API
|
2011-11-23 15:15:44 +01:00
|
|
|
*/
|
2011-12-02 17:13:46 +01:00
|
|
|
def scheduleOnce(delay: Duration)(f: ⇒ Unit): Cancellable
|
2011-10-17 18:34:34 +02:00
|
|
|
}
|
2011-12-13 01:44:18 +01:00
|
|
|
//#scheduler
|
2011-10-17 18:34:34 +02:00
|
|
|
|
2011-12-13 01:44:18 +01:00
|
|
|
//#cancellable
|
|
|
|
|
/**
|
|
|
|
|
* Signifies something that can be cancelled
|
|
|
|
|
* There is no strict guarantee that the implementation is thread-safe,
|
|
|
|
|
* but it should be good practice to make it so.
|
|
|
|
|
*/
|
2011-11-10 11:53:36 +01:00
|
|
|
trait Cancellable {
|
2011-11-23 15:15:44 +01:00
|
|
|
/**
|
2011-12-13 01:44:18 +01:00
|
|
|
* Cancels this Cancellable
|
|
|
|
|
*
|
|
|
|
|
* Java & Scala API
|
2011-11-23 15:15:44 +01:00
|
|
|
*/
|
2011-11-10 11:53:36 +01:00
|
|
|
def cancel(): Unit
|
2011-11-23 15:15:44 +01:00
|
|
|
|
|
|
|
|
/**
|
2011-12-13 01:44:18 +01:00
|
|
|
* Returns whether this Cancellable has been cancelled
|
|
|
|
|
*
|
|
|
|
|
* Java & Scala API
|
2011-11-23 15:15:44 +01:00
|
|
|
*/
|
2011-11-10 11:53:36 +01:00
|
|
|
def isCancelled: Boolean
|
2011-12-13 01:44:18 +01:00
|
|
|
}
|
2011-12-15 14:26:17 +01:00
|
|
|
//#cancellable
|
2012-01-17 09:34:34 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scheduled tasks (Runnable and functions) are executed with the supplied dispatcher.
|
|
|
|
|
* Note that dispatcher is by-name parameter, because dispatcher might not be initialized
|
|
|
|
|
* when the scheduler is created.
|
|
|
|
|
*
|
|
|
|
|
* The HashedWheelTimer used by this class MUST throw an IllegalStateException
|
|
|
|
|
* if it does not enqueue a task. Once a task is queued, it MUST be executed or
|
|
|
|
|
* returned from stop().
|
|
|
|
|
*/
|
2012-01-19 20:55:27 +01:00
|
|
|
class DefaultScheduler(hashedWheelTimer: HashedWheelTimer,
|
|
|
|
|
log: LoggingAdapter,
|
|
|
|
|
dispatcher: ⇒ MessageDispatcher) extends Scheduler with Closeable {
|
2012-01-17 09:34:34 +01:00
|
|
|
|
|
|
|
|
def schedule(initialDelay: Duration, delay: Duration, receiver: ActorRef, message: Any): Cancellable = {
|
|
|
|
|
val continuousCancellable = new ContinuousCancellable
|
|
|
|
|
val task = new TimerTask with ContinuousScheduling {
|
|
|
|
|
def run(timeout: HWTimeout) {
|
|
|
|
|
receiver ! message
|
|
|
|
|
// Check if the receiver is still alive and kicking before reschedule the task
|
2012-05-03 21:14:47 +02:00
|
|
|
if (receiver.isTerminated) {
|
2012-02-01 11:42:27 +01:00
|
|
|
log.debug("Could not reschedule message to be sent because receiving actor has been terminated.")
|
2012-01-17 09:34:34 +01:00
|
|
|
} else {
|
|
|
|
|
scheduleNext(timeout, delay, continuousCancellable)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continuousCancellable.init(hashedWheelTimer.newTimeout(task, initialDelay))
|
|
|
|
|
continuousCancellable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def schedule(initialDelay: Duration, delay: Duration)(f: ⇒ Unit): Cancellable = {
|
|
|
|
|
val continuousCancellable = new ContinuousCancellable
|
|
|
|
|
val task = new TimerTask with ContinuousScheduling with Runnable {
|
|
|
|
|
def run = f
|
|
|
|
|
def run(timeout: HWTimeout) {
|
|
|
|
|
dispatcher execute this
|
|
|
|
|
scheduleNext(timeout, delay, continuousCancellable)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continuousCancellable.init(hashedWheelTimer.newTimeout(task, initialDelay))
|
|
|
|
|
continuousCancellable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def schedule(initialDelay: Duration, delay: Duration, runnable: Runnable): Cancellable = {
|
|
|
|
|
val continuousCancellable = new ContinuousCancellable
|
|
|
|
|
val task = new TimerTask with ContinuousScheduling {
|
|
|
|
|
def run(timeout: HWTimeout) {
|
|
|
|
|
dispatcher.execute(runnable)
|
|
|
|
|
scheduleNext(timeout, delay, continuousCancellable)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continuousCancellable.init(hashedWheelTimer.newTimeout(task, initialDelay))
|
|
|
|
|
continuousCancellable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def scheduleOnce(delay: Duration, runnable: Runnable): Cancellable = {
|
|
|
|
|
val task = new TimerTask() {
|
|
|
|
|
def run(timeout: HWTimeout) { dispatcher.execute(runnable) }
|
|
|
|
|
}
|
|
|
|
|
new DefaultCancellable(hashedWheelTimer.newTimeout(task, delay))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def scheduleOnce(delay: Duration, receiver: ActorRef, message: Any): Cancellable = {
|
|
|
|
|
val task = new TimerTask {
|
|
|
|
|
def run(timeout: HWTimeout) {
|
|
|
|
|
receiver ! message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
new DefaultCancellable(hashedWheelTimer.newTimeout(task, delay))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def scheduleOnce(delay: Duration)(f: ⇒ Unit): Cancellable = {
|
|
|
|
|
val task = new TimerTask {
|
|
|
|
|
def run(timeout: HWTimeout) {
|
|
|
|
|
dispatcher.execute(new Runnable { def run = f })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
new DefaultCancellable(hashedWheelTimer.newTimeout(task, delay))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private trait ContinuousScheduling { this: TimerTask ⇒
|
|
|
|
|
def scheduleNext(timeout: HWTimeout, delay: Duration, delegator: ContinuousCancellable) {
|
|
|
|
|
try {
|
|
|
|
|
delegator.swap(timeout.getTimer.newTimeout(this, delay))
|
|
|
|
|
} catch {
|
|
|
|
|
case _: IllegalStateException ⇒ // stop recurring if timer is stopped
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private def execDirectly(t: HWTimeout): Unit = {
|
|
|
|
|
try t.getTask.run(t) catch {
|
|
|
|
|
case e: InterruptedException ⇒ throw e
|
|
|
|
|
case e: Exception ⇒ log.error(e, "exception while executing timer task")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def close() = {
|
|
|
|
|
import scala.collection.JavaConverters._
|
|
|
|
|
hashedWheelTimer.stop().asScala foreach execDirectly
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Wrapper of a [[org.jboss.netty.akka.util.Timeout]] that delegates all
|
|
|
|
|
* methods. Needed to be able to cancel continuous tasks,
|
|
|
|
|
* since they create new Timeout for each tick.
|
|
|
|
|
*/
|
|
|
|
|
private[akka] class ContinuousCancellable extends Cancellable {
|
|
|
|
|
@volatile
|
|
|
|
|
private var delegate: HWTimeout = _
|
|
|
|
|
@volatile
|
|
|
|
|
private var cancelled = false
|
|
|
|
|
|
|
|
|
|
private[akka] def init(initialTimeout: HWTimeout): Unit = {
|
|
|
|
|
delegate = initialTimeout
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private[akka] def swap(newTimeout: HWTimeout): Unit = {
|
|
|
|
|
val wasCancelled = isCancelled
|
|
|
|
|
delegate = newTimeout
|
|
|
|
|
if (wasCancelled || isCancelled) cancel()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def isCancelled(): Boolean = {
|
|
|
|
|
// delegate is initially null, but this object will not be exposed to the world until after init
|
|
|
|
|
cancelled || delegate.isCancelled()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def cancel(): Unit = {
|
|
|
|
|
// the underlying Timeout will not become cancelled once the task has been started to run,
|
|
|
|
|
// therefore we keep a flag here to make sure that rescheduling doesn't occur when cancelled
|
|
|
|
|
cancelled = true
|
|
|
|
|
// delegate is initially null, but this object will not be exposed to the world until after init
|
|
|
|
|
delegate.cancel()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class DefaultCancellable(val timeout: HWTimeout) extends Cancellable {
|
|
|
|
|
def cancel() {
|
|
|
|
|
timeout.cancel()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def isCancelled: Boolean = {
|
|
|
|
|
timeout.isCancelled
|
|
|
|
|
}
|
2012-01-18 14:20:13 +01:00
|
|
|
}
|