diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/Effect.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/Effect.scala index 663f18d00e..fa37faedfb 100644 --- a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/Effect.scala +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/Effect.scala @@ -214,14 +214,20 @@ object Effect { } object TimerScheduled { + import akka.util.JavaDurationConverters._ + sealed trait TimerMode case object FixedRateMode extends TimerMode + case class FixedRateModeWithInitialDelay(initialDelay: FiniteDuration) extends TimerMode case object FixedDelayMode extends TimerMode + case class FixedDelayModeWithInitialDelay(initialDelay: FiniteDuration) extends TimerMode case object SingleMode extends TimerMode /*Java API*/ def fixedRateMode = FixedRateMode + def fixedRateMode(initialDelay: java.time.Duration) = FixedRateModeWithInitialDelay(initialDelay.asScala) def fixedDelayMode = FixedDelayMode + def fixedDelayMode(initialDelay: java.time.Duration) = FixedDelayModeWithInitialDelay(initialDelay.asScala) def singleMode = SingleMode } diff --git a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/EffectfulActorContext.scala b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/EffectfulActorContext.scala index 627ea40c9d..a27054c0f0 100644 --- a/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/EffectfulActorContext.scala +++ b/akka-actor-testkit-typed/src/main/scala/akka/actor/testkit/typed/internal/EffectfulActorContext.scala @@ -91,9 +91,15 @@ import scala.reflect.ClassTag override def startTimerWithFixedDelay(key: Any, msg: T, delay: FiniteDuration): Unit = startTimer(key, msg, delay, Effect.TimerScheduled.FixedDelayMode) + override def startTimerWithFixedDelay(key: Any, msg: T, initialDelay: FiniteDuration, delay: FiniteDuration): Unit = + startTimer(key, msg, delay, Effect.TimerScheduled.FixedDelayModeWithInitialDelay(initialDelay)) + override def startTimerAtFixedRate(key: Any, msg: T, interval: FiniteDuration): Unit = startTimer(key, msg, interval, Effect.TimerScheduled.FixedRateMode) + override def startTimerAtFixedRate(key: Any, msg: T, initialDelay: FiniteDuration, interval: FiniteDuration): Unit = + startTimer(key, msg, interval, Effect.TimerScheduled.FixedRateModeWithInitialDelay(initialDelay)) + override def startPeriodicTimer(key: Any, msg: T, interval: FiniteDuration): Unit = startTimer(key, msg, interval, Effect.TimerScheduled.FixedRateMode) diff --git a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala index 826de376dd..cb2bb95a91 100644 --- a/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala +++ b/akka-actor-testkit-typed/src/test/scala/akka/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala @@ -123,8 +123,12 @@ object BehaviorTestKitSpec { case ScheduleCommand(key, delay, mode, cmd) => mode match { case Effect.TimerScheduled.SingleMode => timers.startSingleTimer(key, cmd, delay) - case Effect.TimerScheduled.FixedDelayMode => timers.startTimerWithFixedDelay(key, cmd, delay) - case Effect.TimerScheduled.FixedRateMode => timers.startTimerAtFixedRate(key, cmd, delay) + case Effect.TimerScheduled.FixedDelayMode => timers.startTimerWithFixedDelay(key, cmd, delay, delay) + case m: Effect.TimerScheduled.FixedDelayModeWithInitialDelay => + timers.startTimerWithFixedDelay(key, cmd, m.initialDelay, delay) + case Effect.TimerScheduled.FixedRateMode => timers.startTimerAtFixedRate(key, cmd, delay, delay) + case m: Effect.TimerScheduled.FixedRateModeWithInitialDelay => + timers.startTimerAtFixedRate(key, cmd, m.initialDelay, delay) } Behaviors.same case CancelScheduleCommand(key) => @@ -445,16 +449,17 @@ class BehaviorTestKitSpec extends AnyWordSpec with Matchers with LogCapturing { } "schedule and fire timers multiple times" in { + val delay = 42.seconds val testkit = BehaviorTestKit[Parent.Command](Parent.init) - testkit.run(ScheduleCommand("abc", 42.seconds, Effect.TimerScheduled.FixedRateMode, SpawnChild)) + testkit.run(ScheduleCommand("abc", delay, Effect.TimerScheduled.FixedRateMode, SpawnChild)) val send = testkit.expectEffectPF { case e @ Effect.TimerScheduled( "abc", SpawnChild, finiteDuration, - Effect.TimerScheduled.FixedRateMode, + Effect.TimerScheduled.FixedRateModeWithInitialDelay(`delay`), false /*not overriding*/ ) => - finiteDuration should equal(42.seconds) + finiteDuration should equal(delay) e.send } send() diff --git a/akka-actor-typed/src/main/mima-filters/2.6.13.backwards.excludes/30065-timers-initialDelay.excludes b/akka-actor-typed/src/main/mima-filters/2.6.13.backwards.excludes/30065-timers-initialDelay.excludes new file mode 100644 index 0000000000..30ec731fb3 --- /dev/null +++ b/akka-actor-typed/src/main/mima-filters/2.6.13.backwards.excludes/30065-timers-initialDelay.excludes @@ -0,0 +1,12 @@ +# marked with @DoNotInherit +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.javadsl.TimerScheduler.startTimerWithFixedDelay") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.javadsl.TimerScheduler.startTimerAtFixedRate") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.scaladsl.TimerScheduler.startTimerWithFixedDelay") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.scaladsl.TimerScheduler.startTimerAtFixedRate") +# marked with @InternalApi +ProblemFilters.exclude[MissingTypesProblem]("akka.actor.typed.internal.TimerSchedulerImpl$FixedRateMode$") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.typed.internal.TimerSchedulerImpl#FixedRateMode.*") +ProblemFilters.exclude[FinalMethodProblem]("akka.actor.typed.internal.TimerSchedulerImpl#FixedRateMode.toString") +ProblemFilters.exclude[MissingTypesProblem]("akka.actor.typed.internal.TimerSchedulerImpl$FixedDelayMode$") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.typed.internal.TimerSchedulerImpl#FixedDelayMode.*") +ProblemFilters.exclude[FinalMethodProblem]("akka.actor.typed.internal.TimerSchedulerImpl#FixedDelayMode.toString") \ No newline at end of file diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala index 49ce6e1694..8a9e0036d8 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/TimerSchedulerImpl.scala @@ -40,10 +40,10 @@ import scala.concurrent.duration.FiniteDuration private sealed trait TimerMode { def repeat: Boolean } - private case object FixedRateMode extends TimerMode { + private case class FixedRateMode(initialDelay: FiniteDuration) extends TimerMode { override def repeat: Boolean = true } - private case object FixedDelayMode extends TimerMode { + private case class FixedDelayMode(initialDelay: FiniteDuration) extends TimerMode { override def repeat: Boolean = true } private case object SingleMode extends TimerMode { @@ -59,9 +59,15 @@ import scala.concurrent.duration.FiniteDuration override final def startTimerWithFixedDelay(key: Any, msg: T, delay: Duration): Unit = startTimerWithFixedDelay(key, msg, delay.asScala) + override final def startTimerWithFixedDelay(key: Any, msg: T, initialDelay: Duration, delay: Duration): Unit = + startTimerWithFixedDelay(key, msg, initialDelay.asScala, delay.asScala) + override final def startTimerAtFixedRate(key: Any, msg: T, interval: Duration): Unit = startTimerAtFixedRate(key, msg, interval.asScala) + override final def startTimerAtFixedRate(key: Any, msg: T, initialDelay: Duration, interval: Duration): Unit = + startTimerAtFixedRate(key, msg, initialDelay.asScala, interval.asScala) + override final def startPeriodicTimer(key: Any, msg: T, interval: Duration): Unit = { //this follows the deprecation note in the super class startTimerWithFixedDelay(key, msg, interval.asScala) @@ -83,13 +89,19 @@ import scala.concurrent.duration.FiniteDuration private val timerGen = Iterator.from(1) override def startTimerAtFixedRate(key: Any, msg: T, interval: FiniteDuration): Unit = - startTimer(key, msg, interval, FixedRateMode) + startTimer(key, msg, interval, FixedRateMode(interval)) + + override def startTimerAtFixedRate(key: Any, msg: T, initialDelay: FiniteDuration, interval: FiniteDuration): Unit = + startTimer(key, msg, interval, FixedRateMode(initialDelay)) override def startTimerWithFixedDelay(key: Any, msg: T, delay: FiniteDuration): Unit = - startTimer(key, msg, delay, FixedDelayMode) + startTimer(key, msg, delay, FixedDelayMode(delay)) + + override def startTimerWithFixedDelay(key: Any, msg: T, initialDelay: FiniteDuration, delay: FiniteDuration): Unit = + startTimer(key, msg, delay, FixedDelayMode(initialDelay)) override def startPeriodicTimer(key: Any, msg: T, interval: FiniteDuration): Unit = - startTimer(key, msg, interval, FixedRateMode) + startTimer(key, msg, interval, FixedRateMode(interval)) override def startSingleTimer(key: Any, msg: T, delay: FiniteDuration): Unit = startTimer(key, msg, delay, SingleMode) @@ -110,11 +122,11 @@ import scala.concurrent.duration.FiniteDuration val task = mode match { case SingleMode => ctx.system.scheduler.scheduleOnce(delay, () => ctx.self.unsafeUpcast ! timerMsg)(ExecutionContexts.parasitic) - case FixedDelayMode => - ctx.system.scheduler.scheduleWithFixedDelay(delay, delay)(() => ctx.self.unsafeUpcast ! timerMsg)( + case m: FixedDelayMode => + ctx.system.scheduler.scheduleWithFixedDelay(m.initialDelay, delay)(() => ctx.self.unsafeUpcast ! timerMsg)( ExecutionContexts.parasitic) - case FixedRateMode => - ctx.system.scheduler.scheduleAtFixedRate(delay, delay)(() => ctx.self.unsafeUpcast ! timerMsg)( + case m: FixedRateMode => + ctx.system.scheduler.scheduleAtFixedRate(m.initialDelay, delay)(() => ctx.self.unsafeUpcast ! timerMsg)( ExecutionContexts.parasitic) } diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/TimerScheduler.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/TimerScheduler.scala index 61205a1b94..8725a7cf7f 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/TimerScheduler.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/TimerScheduler.scala @@ -40,6 +40,24 @@ trait TimerScheduler[T] { */ def startTimerWithFixedDelay(key: Any, msg: T, delay: java.time.Duration): Unit + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * fixed `delay` between messages after `initialDelay`. + * + * It will not compensate the delay between messages if scheduling is delayed + * longer than specified for some reason. The delay between sending of subsequent + * messages will always be (at least) the given `delay`. + * + * In the long run, the frequency of messages will generally be slightly lower than + * the reciprocal of the specified `delay`. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. + */ + def startTimerWithFixedDelay(key: Any, msg: T, initialDelay: java.time.Duration, delay: java.time.Duration): Unit + /** * Schedules a message to be sent repeatedly to the `self` actor with a * fixed `delay` between messages. @@ -60,6 +78,26 @@ trait TimerScheduler[T] { def startTimerWithFixedDelay(msg: T, delay: java.time.Duration): Unit = startTimerWithFixedDelay(msg, msg, delay) + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * fixed `delay` between messages after `initialDelay`. + * + * It will not compensate the delay between messages if scheduling is delayed + * longer than specified for some reason. The delay between sending of subsequent + * messages will always be (at least) the given `delay`. + * + * In the long run, the frequency of messages will generally be slightly lower than + * the reciprocal of the specified `delay`. + * + * When a new timer is started with the same message, + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. If you do not want this, + * you can start start them as individual timers by specifying distinct keys. + */ + def startTimerWithFixedDelay(msg: T, initialDelay: java.time.Duration, delay: java.time.Duration): Unit = + startTimerWithFixedDelay(msg, msg, initialDelay, delay) + /** * Schedules a message to be sent repeatedly to the `self` actor with a * given frequency. @@ -87,6 +125,33 @@ trait TimerScheduler[T] { */ def startTimerAtFixedRate(key: Any, msg: T, interval: java.time.Duration): Unit + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * given frequency. + * + * It will compensate the delay for a subsequent message if the sending of previous + * message was delayed more than specified. In such cases, the actual message interval + * will differ from the interval passed to the method. + * + * If the execution is delayed longer than the `interval`, the subsequent message will + * be sent immediately after the prior one. This also has the consequence that after + * long garbage collection pauses or other reasons when the JVM was suspended all + * "missed" messages will be sent when the process wakes up again. + * + * In the long run, the frequency of messages will be exactly the reciprocal of the + * specified `interval` after `initialDelay`. + * + * Warning: `startTimerAtFixedRate` can result in bursts of scheduled messages after long + * garbage collection pauses, which may in worst case cause undesired load on the system. + * Therefore `startTimerWithFixedDelay` is often preferred. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. + */ + def startTimerAtFixedRate(key: Any, msg: T, initialDelay: java.time.Duration, interval: java.time.Duration): Unit + /** * Schedules a message to be sent repeatedly to the `self` actor with a * given frequency. @@ -116,6 +181,35 @@ trait TimerScheduler[T] { def startTimerAtFixedRate(msg: T, interval: java.time.Duration): Unit = startTimerAtFixedRate(msg, msg, interval) + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * given frequency. + * + * It will compensate the delay for a subsequent message if the sending of previous + * message was delayed more than specified. In such cases, the actual message interval + * will differ from the interval passed to the method. + * + * If the execution is delayed longer than the `interval`, the subsequent message will + * be sent immediately after the prior one. This also has the consequence that after + * long garbage collection pauses or other reasons when the JVM was suspended all + * "missed" messages will be sent when the process wakes up again. + * + * In the long run, the frequency of messages will be exactly the reciprocal of the + * specified `interval` after `initialDelay`. + * + * Warning: `startTimerAtFixedRate` can result in bursts of scheduled messages after long + * garbage collection pauses, which may in worst case cause undesired load on the system. + * Therefore `startTimerWithFixedDelay` is often preferred. + * + * When a new timer is started with the same message, + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. If you do not want this, + * you can start start them as individual timers by specifying distinct keys. + */ + def startTimerAtFixedRate(msg: T, initialDelay: java.time.Duration, interval: java.time.Duration): Unit = + startTimerAtFixedRate(msg, msg, initialDelay, interval) + /** * Deprecated API: See [[TimerScheduler#startTimerWithFixedDelay]] or [[TimerScheduler#startTimerAtFixedRate]]. */ diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/TimerScheduler.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/TimerScheduler.scala index c4226104f0..d6ef9ea412 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/TimerScheduler.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/TimerScheduler.scala @@ -40,6 +40,24 @@ trait TimerScheduler[T] { */ def startTimerWithFixedDelay(key: Any, msg: T, delay: FiniteDuration): Unit + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * fixed `delay` between messages after the `initialDelay`. + * + * It will not compensate the delay between messages if scheduling is delayed + * longer than specified for some reason. The delay between sending of subsequent + * messages will always be (at least) the given `delay`. + * + * In the long run, the frequency of messages will generally be slightly lower than + * the reciprocal of the specified `delay`. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already be enqueued + * in the mailbox before the new timer was started. + */ + def startTimerWithFixedDelay(key: Any, msg: T, initialDelay: FiniteDuration, delay: FiniteDuration): Unit + /** * Schedules a message to be sent repeatedly to the `self` actor with a * fixed `delay` between messages. @@ -60,6 +78,26 @@ trait TimerScheduler[T] { def startTimerWithFixedDelay(msg: T, delay: FiniteDuration): Unit = startTimerWithFixedDelay(msg, msg, delay) + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * fixed `delay` between messages after the `initialDelay`. + * + * It will not compensate the delay between messages if scheduling is delayed + * longer than specified for some reason. The delay between sending of subsequent + * messages will always be (at least) the given `delay`. + * + * In the long run, the frequency of messages will generally be slightly lower than + * the reciprocal of the specified `delay`. + * + * When a new timer is started with the same message, + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. If you do not want this, + * you can start start them as individual timers by specifying distinct keys. + */ + def startTimerWithFixedDelay(msg: T, initialDelay: FiniteDuration, delay: FiniteDuration): Unit = + startTimerWithFixedDelay(msg, msg, initialDelay, delay) + /** * Schedules a message to be sent repeatedly to the `self` actor with a * given frequency. @@ -87,6 +125,33 @@ trait TimerScheduler[T] { */ def startTimerAtFixedRate(key: Any, msg: T, interval: FiniteDuration): Unit + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * given frequency. + * + * It will compensate the delay for a subsequent message if the sending of previous + * message was delayed more than specified. In such cases, the actual message interval + * will differ from the interval passed to the method. + * + * If the execution is delayed longer than the `interval`, the subsequent message will + * be sent immediately after the prior one. This also has the consequence that after + * long garbage collection pauses or other reasons when the JVM was suspended all + * "missed" messages will be sent when the process wakes up again. + * + * In the long run, the frequency of messages will be exactly the reciprocal of the + * specified `interval` after `initialDelay`. + * + * Warning: `startTimerAtFixedRate` can result in bursts of scheduled messages after long + * garbage collection pauses, which may in worst case cause undesired load on the system. + * Therefore `startTimerWithFixedDelay` is often preferred. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. + */ + def startTimerAtFixedRate(key: Any, msg: T, initialDelay: FiniteDuration, interval: FiniteDuration): Unit + /** * Schedules a message to be sent repeatedly to the `self` actor with a * given frequency. @@ -116,6 +181,35 @@ trait TimerScheduler[T] { def startTimerAtFixedRate(msg: T, interval: FiniteDuration): Unit = startTimerAtFixedRate(msg, msg, interval) + /** + * Schedules a message to be sent repeatedly to the `self` actor with a + * given frequency. + * + * It will compensate the delay for a subsequent message if the sending of previous + * message was delayed more than specified. In such cases, the actual message interval + * will differ from the interval passed to the method. + * + * If the execution is delayed longer than the `interval`, the subsequent message will + * be sent immediately after the prior one. This also has the consequence that after + * long garbage collection pauses or other reasons when the JVM was suspended all + * "missed" messages will be sent when the process wakes up again. + * + * In the long run, the frequency of messages will be exactly the reciprocal of the + * specified `interval` after `initialDelay`. + * + * Warning: `startTimerAtFixedRate` can result in bursts of scheduled messages after long + * garbage collection pauses, which may in worst case cause undesired load on the system. + * Therefore `startTimerWithFixedDelay` is often preferred. + * + * When a new timer is started with the same message + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. If you do not want this, + * you can start start them as individual timers by specifying distinct keys. + */ + def startTimerAtFixedRate(msg: T, initialDelay: FiniteDuration, interval: FiniteDuration): Unit = + startTimerAtFixedRate(msg, msg, initialDelay, interval) + /** * Deprecated API: See [[TimerScheduler#startTimerWithFixedDelay]] or [[TimerScheduler#startTimerAtFixedRate]]. */ diff --git a/akka-actor/src/main/mima-filters/2.6.13.backwards.excludes/30065-timers-initialDelay.excludes b/akka-actor/src/main/mima-filters/2.6.13.backwards.excludes/30065-timers-initialDelay.excludes new file mode 100644 index 0000000000..6c1e3d8a0c --- /dev/null +++ b/akka-actor/src/main/mima-filters/2.6.13.backwards.excludes/30065-timers-initialDelay.excludes @@ -0,0 +1,14 @@ +# marked with @DoNotInherit +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.TimerScheduler.startTimerWithFixedDelay") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.TimerScheduler.startTimerAtFixedRate") +# marked with @InternalApi +ProblemFilters.exclude[MissingTypesProblem]("akka.actor.TimerSchedulerImpl$FixedRateMode$") +ProblemFilters.exclude[MissingTypesProblem]("akka.actor.TimerSchedulerImpl$FixedDelayMode$") +ProblemFilters.exclude[FinalMethodProblem]("akka.actor.TimerSchedulerImpl#FixedRateMode.toString") +ProblemFilters.exclude[FinalMethodProblem]("akka.actor.TimerSchedulerImpl#FixedDelayMode.toString") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.TimerSchedulerImpl#FixedRateMode.*") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.TimerSchedulerImpl#FixedDelayMode.*") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.TimerSchedulerImpl#FixedRateMode.productElementName") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.TimerSchedulerImpl#FixedRateMode.productElementNames") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.TimerSchedulerImpl#FixedDelayMode.productElementName") +ProblemFilters.exclude[DirectMissingMethodProblem]("akka.actor.TimerSchedulerImpl#FixedDelayMode.productElementNames") \ No newline at end of file diff --git a/akka-actor/src/main/scala/akka/actor/Timers.scala b/akka-actor/src/main/scala/akka/actor/Timers.scala index a7d936ddf5..049a506df6 100644 --- a/akka-actor/src/main/scala/akka/actor/Timers.scala +++ b/akka-actor/src/main/scala/akka/actor/Timers.scala @@ -104,6 +104,24 @@ abstract class AbstractActorWithTimers extends AbstractActor with Timers { */ def startTimerWithFixedDelay(key: Any, msg: Any, delay: FiniteDuration): Unit + /** + * Scala API: Schedules a message to be sent repeatedly to the `self` actor with a + * fixed `delay` between messages after the `initialDelay`. + * + * It will not compensate the delay between messages if scheduling is delayed + * longer than specified for some reason. The delay between sending of subsequent + * messages will always be (at least) the given `delay`. + * + * In the long run, the frequency of messages will generally be slightly lower than + * the reciprocal of the specified `delay`. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. + */ + def startTimerWithFixedDelay(key: Any, msg: Any, initialDelay: FiniteDuration, delay: FiniteDuration): Unit + /** * Java API: Schedules a message to be sent repeatedly to the `self` actor with a * fixed `delay` between messages. @@ -123,6 +141,29 @@ abstract class AbstractActorWithTimers extends AbstractActor with Timers { final def startTimerWithFixedDelay(key: Any, msg: Any, delay: java.time.Duration): Unit = startTimerWithFixedDelay(key, msg, delay.asScala) + /** + * Java API: Schedules a message to be sent repeatedly to the `self` actor with a + * fixed `delay` between messages after the `initialDelay`. + * + * It will not compensate the delay between messages if scheduling is delayed + * longer than specified for some reason. The delay between sending of subsequent + * messages will always be (at least) the given `delay`. + * + * In the long run, the frequency of messages will generally be slightly lower than + * the reciprocal of the specified `delay`. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. + */ + final def startTimerWithFixedDelay( + key: Any, + msg: Any, + initialDelay: java.time.Duration, + delay: java.time.Duration): Unit = + startTimerWithFixedDelay(key, msg, initialDelay.asScala, delay.asScala) + /** * Scala API: Schedules a message to be sent repeatedly to the `self` actor with a * given frequency. @@ -150,6 +191,33 @@ abstract class AbstractActorWithTimers extends AbstractActor with Timers { */ def startTimerAtFixedRate(key: Any, msg: Any, interval: FiniteDuration): Unit + /** + * Scala API: Schedules a message to be sent repeatedly to the `self` actor with a + * given frequency. + * + * It will compensate the delay for a subsequent message if the sending of previous + * message was delayed more than specified. In such cases, the actual message interval + * will differ from the interval passed to the method. + * + * If the execution is delayed longer than the `interval`, the subsequent message will + * be sent immediately after the prior one. This also has the consequence that after + * long garbage collection pauses or other reasons when the JVM was suspended all + * "missed" messages will be sent when the process wakes up again. + * + * In the long run, the frequency of messages will be exactly the reciprocal of the + * specified `interval` after `initialDelay`. + * + * Warning: `startTimerAtFixedRate` can result in bursts of scheduled messages after long + * garbage collection pauses, which may in worst case cause undesired load on the system. + * Therefore `startTimerWithFixedDelay` is often preferred. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. + */ + def startTimerAtFixedRate(key: Any, msg: Any, initialDelay: FiniteDuration, interval: FiniteDuration): Unit + /** * Java API: Schedules a message to be sent repeatedly to the `self` actor with a * given frequency. @@ -178,6 +246,38 @@ abstract class AbstractActorWithTimers extends AbstractActor with Timers { final def startTimerAtFixedRate(key: Any, msg: Any, interval: java.time.Duration): Unit = startTimerAtFixedRate(key, msg, interval.asScala) + /** + * Java API: Schedules a message to be sent repeatedly to the `self` actor with a + * given frequency. + * + * It will compensate the delay for a subsequent message if the sending of previous + * message was delayed more than specified. In such cases, the actual message interval + * will differ from the interval passed to the method. + * + * If the execution is delayed longer than the `interval`, the subsequent message will + * be sent immediately after the prior one. This also has the consequence that after + * long garbage collection pauses or other reasons when the JVM was suspended all + * "missed" messages will be sent when the process wakes up again. + * + * In the long run, the frequency of messages will be exactly the reciprocal of the + * specified `interval`. + * + * Warning: `startTimerAtFixedRate` can result in bursts of scheduled messages after long + * garbage collection pauses, which may in worst case cause undesired load on the system. + * Therefore `startTimerWithFixedDelay` is often preferred. + * + * Each timer has a key and if a new timer with same key is started + * the previous is cancelled. It is guaranteed that a message from the + * previous timer is not received, even if it was already enqueued + * in the mailbox when the new timer was started. + */ + final def startTimerAtFixedRate( + key: Any, + msg: Any, + initialDelay: java.time.Duration, + interval: java.time.Duration): Unit = + startTimerAtFixedRate(key, msg, initialDelay.asScala, interval.asScala) + /** * Deprecated API: See [[TimerScheduler#startTimerWithFixedDelay]] or [[TimerScheduler#startTimerAtFixedRate]]. */ diff --git a/akka-actor/src/main/scala/akka/actor/dungeon/TimerSchedulerImpl.scala b/akka-actor/src/main/scala/akka/actor/dungeon/TimerSchedulerImpl.scala index 5213d63655..34f16db6ab 100644 --- a/akka-actor/src/main/scala/akka/actor/dungeon/TimerSchedulerImpl.scala +++ b/akka-actor/src/main/scala/akka/actor/dungeon/TimerSchedulerImpl.scala @@ -32,10 +32,10 @@ import akka.util.OptionVal private sealed trait TimerMode { def repeat: Boolean } - private case object FixedRateMode extends TimerMode { + private case class FixedRateMode(initialDelay: FiniteDuration) extends TimerMode { override def repeat: Boolean = true } - private case object FixedDelayMode extends TimerMode { + private case class FixedDelayMode(initialDelay: FiniteDuration) extends TimerMode { override def repeat: Boolean = true } private case object SingleMode extends TimerMode { @@ -58,10 +58,16 @@ import akka.util.OptionVal } override def startTimerAtFixedRate(key: Any, msg: Any, interval: FiniteDuration): Unit = - startTimer(key, msg, interval, FixedRateMode) + startTimer(key, msg, interval, FixedRateMode(interval)) + + override def startTimerAtFixedRate(key: Any, msg: Any, initialDelay: FiniteDuration, interval: FiniteDuration): Unit = + startTimer(key, msg, interval, FixedRateMode(initialDelay)) override def startTimerWithFixedDelay(key: Any, msg: Any, delay: FiniteDuration): Unit = - startTimer(key, msg, delay, FixedDelayMode) + startTimer(key, msg, delay, FixedDelayMode(delay)) + + override def startTimerWithFixedDelay(key: Any, msg: Any, initialDelay: FiniteDuration, delay: FiniteDuration): Unit = + startTimer(key, msg, delay, FixedDelayMode(initialDelay)) override def startPeriodicTimer(key: Any, msg: Any, interval: FiniteDuration): Unit = startTimerAtFixedRate(key, msg, interval) @@ -85,10 +91,10 @@ import akka.util.OptionVal val task = mode match { case SingleMode => ctx.system.scheduler.scheduleOnce(timeout, ctx.self, timerMsg)(ctx.dispatcher) - case FixedDelayMode => - ctx.system.scheduler.scheduleWithFixedDelay(timeout, timeout, ctx.self, timerMsg)(ctx.dispatcher) - case FixedRateMode => - ctx.system.scheduler.scheduleAtFixedRate(timeout, timeout, ctx.self, timerMsg)(ctx.dispatcher) + case m: FixedDelayMode => + ctx.system.scheduler.scheduleWithFixedDelay(m.initialDelay, timeout, ctx.self, timerMsg)(ctx.dispatcher) + case m: FixedRateMode => + ctx.system.scheduler.scheduleAtFixedRate(m.initialDelay, timeout, ctx.self, timerMsg)(ctx.dispatcher) } val nextTimer = Timer(key, msg, mode.repeat, nextGen, task)