Skip to content

Commit 7b2a3d8

Browse files
authored
fix: reject zero and negative periodic tasks schedule (#887)
* fix: reject zero and negative periodic tasks schedule * fix: undo the symbol change * use different test name, redescribe the exception * abstract check function * remove the printlns change * reduce time units scale convert
1 parent 19da736 commit 7b2a3d8

File tree

3 files changed

+74
-9
lines changed

3 files changed

+74
-9
lines changed

actor-tests/src/test/scala/org/apache/pekko/actor/SchedulerSpec.scala

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,45 @@ class LightArrayRevolverSchedulerSpec extends PekkoSpec(SchedulerSpec.testConfRe
661661
}
662662
}
663663

664+
"using scheduleAtFixedRate" must {
665+
"fixed rate reject periodic tasks scheduled with zero interval" taggedAs TimingTest in {
666+
val zeroInterval = 0.seconds
667+
import system.dispatcher
668+
intercept[IllegalArgumentException] {
669+
system.scheduler.scheduleAtFixedRate(100.millis, zeroInterval, testActor, "Not Allowed")
670+
}
671+
expectNoMessage(1.second)
672+
}
673+
674+
"fixed rate reject periodic tasks scheduled with negative interval" taggedAs TimingTest in {
675+
val negativeDelay = -1.seconds
676+
import system.dispatcher
677+
intercept[IllegalArgumentException] {
678+
system.scheduler.scheduleAtFixedRate(100.millis, negativeDelay, testActor, "Not Allowed")
679+
}
680+
expectNoMessage(1.second)
681+
}
682+
}
683+
684+
"using scheduleWithFixedDelay" must {
685+
"fixed delay reject periodic tasks scheduled with zero interval" taggedAs TimingTest in {
686+
val zeroInterval = 0.seconds
687+
import system.dispatcher
688+
intercept[IllegalArgumentException] {
689+
system.scheduler.scheduleWithFixedDelay(100.millis, zeroInterval, testActor, "Not Allowed")
690+
}
691+
expectNoMessage(1.second)
692+
}
693+
694+
"fixed delay reject periodic tasks scheduled with negative interval" taggedAs TimingTest in {
695+
val negativeDelay = -1.seconds
696+
import system.dispatcher
697+
intercept[IllegalArgumentException] {
698+
system.scheduler.scheduleWithFixedDelay(100.millis, negativeDelay, testActor, "Not Allowed")
699+
}
700+
expectNoMessage(1.second)
701+
}
702+
}
664703
// same tests for fixedDelay and fixedRate
665704
List(new ScheduleWithFixedDelayAdapter, new ScheduleAtFixedRateAdapter).foreach { scheduleAdapter =>
666705
s"using $scheduleAdapter" must {
@@ -684,6 +723,24 @@ class LightArrayRevolverSchedulerSpec extends PekkoSpec(SchedulerSpec.testConfRe
684723
}
685724
expectNoMessage(1.second)
686725
}
726+
727+
"reject periodic tasks scheduled with zero interval" taggedAs TimingTest in {
728+
val zeroInterval = 0.seconds
729+
import system.dispatcher
730+
intercept[IllegalArgumentException] {
731+
scheduleAdapter.schedule(100.millis, zeroInterval, testActor, "Not Allowed")
732+
}
733+
expectNoMessage(1.second)
734+
}
735+
736+
"reject periodic tasks scheduled with negative interval" taggedAs TimingTest in {
737+
val negativeInterval = -1.seconds
738+
import system.dispatcher
739+
intercept[IllegalArgumentException] {
740+
scheduleAdapter.schedule(100.millis, negativeInterval, testActor, "Not Allowed")
741+
}
742+
expectNoMessage(1.second)
743+
}
687744
}
688745
}
689746

actor/src/main/scala/org/apache/pekko/actor/LightArrayRevolverScheduler.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,14 @@ class LightArrayRevolverScheduler(config: Config, log: LoggingAdapter, threadFac
105105

106106
override def scheduleWithFixedDelay(initialDelay: FiniteDuration, delay: FiniteDuration)(runnable: Runnable)(
107107
implicit executor: ExecutionContext): Cancellable = {
108+
checkPeriod(delay)
108109
checkMaxDelay(roundUp(delay).toNanos)
109110
super.scheduleWithFixedDelay(initialDelay, delay)(runnable)
110111
}
111112

112113
override def schedule(initialDelay: FiniteDuration, delay: FiniteDuration, runnable: Runnable)(
113114
implicit executor: ExecutionContext): Cancellable = {
115+
checkPeriod(delay)
114116
checkMaxDelay(roundUp(delay).toNanos)
115117
new AtomicCancellable(InitialRepeatMarker) { self =>
116118
final override protected def scheduledFirst(): Cancellable =
@@ -193,6 +195,12 @@ class LightArrayRevolverScheduler(config: Config, log: LoggingAdapter, threadFac
193195
task
194196
}
195197

198+
private def checkPeriod(delay: FiniteDuration): Unit =
199+
if (delay.length <= 0)
200+
throw new IllegalArgumentException(
201+
s"Task scheduled with [${delay.toSeconds}] seconds delay, which means creating an infinite loop. " +
202+
s"The expected delay must be greater than 0.")
203+
196204
private def checkMaxDelay(delayNanos: Long): Unit =
197205
if (delayNanos / tickNanos > Int.MaxValue)
198206
// 1 second margin in the error message due to rounding

actor/src/main/scala/org/apache/pekko/actor/Scheduler.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ trait Scheduler {
7575
* If the `Runnable` throws an exception the repeated scheduling is aborted,
7676
* i.e. the function will not be invoked any more.
7777
*
78-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
78+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
7979
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
8080
*
8181
* Note: For scheduling within actors `with Timers` should be preferred.
@@ -116,7 +116,7 @@ trait Scheduler {
116116
* If the `Runnable` throws an exception the repeated scheduling is aborted,
117117
* i.e. the function will not be invoked any more.
118118
*
119-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
119+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
120120
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
121121
*
122122
* Note: For scheduling within actors `AbstractActorWithTimers` should be preferred.
@@ -215,7 +215,7 @@ trait Scheduler {
215215
* If the `Runnable` throws an exception the repeated scheduling is aborted,
216216
* i.e. the function will not be invoked any more.
217217
*
218-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
218+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
219219
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
220220
*
221221
* Note: For scheduling within actors `with Timers` should be preferred.
@@ -251,7 +251,7 @@ trait Scheduler {
251251
* If the `Runnable` throws an exception the repeated scheduling is aborted,
252252
* i.e. the function will not be invoked any more.
253253
*
254-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
254+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
255255
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
256256
*
257257
* Note: For scheduling within actors `AbstractActorWithTimers` should be preferred.
@@ -415,7 +415,7 @@ trait Scheduler {
415415
* Scala API: Schedules a message to be sent once with a delay, i.e. a time period that has
416416
* to pass before the message is sent.
417417
*
418-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
418+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
419419
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
420420
*
421421
* Note: For scheduling within actors `with Timers` should be preferred.
@@ -433,7 +433,7 @@ trait Scheduler {
433433
* Java API: Schedules a message to be sent once with a delay, i.e. a time period that has
434434
* to pass before the message is sent.
435435
*
436-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
436+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
437437
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
438438
*
439439
* Note: For scheduling within actors `AbstractActorWithTimers` should be preferred.
@@ -452,7 +452,7 @@ trait Scheduler {
452452
* Scala API: Schedules a function to be run once with a delay, i.e. a time period that has
453453
* to pass before the function is run.
454454
*
455-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
455+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
456456
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
457457
*
458458
* Note: For scheduling within actors `with Timers` should be preferred.
@@ -466,7 +466,7 @@ trait Scheduler {
466466
* Scala API: Schedules a Runnable to be run once with a delay, i.e. a time period that
467467
* has to pass before the runnable is executed.
468468
*
469-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
469+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
470470
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
471471
*
472472
* Note: For scheduling within actors `with Timers` should be preferred.
@@ -477,7 +477,7 @@ trait Scheduler {
477477
* Java API: Schedules a Runnable to be run once with a delay, i.e. a time period that
478478
* has to pass before the runnable is executed.
479479
*
480-
* @throws java.lang.IllegalArgumentException if the given delays exceed the maximum
480+
* @throws java.lang.IllegalArgumentException if the given delays is zero, negative or exceed the maximum
481481
* reach (calculated as: `delay / tickNanos > Int.MaxValue`).
482482
*
483483
* Note: For scheduling within actors `AbstractActorWithTimers` should be preferred.

0 commit comments

Comments
 (0)