Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(engine): prevent instant rescheduling #9237

Merged
merged 1 commit into from
Apr 29, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ public void schedule(final long dueDate) {
// We schedule only one runnable for all timers.
// - The runnable is scheduled when the first timer is scheduled.
// - If a new timer is scheduled which should be triggered before the current runnable is
// executed then the runnable is canceled and re-scheduled with the new duration.
// executed then the runnable is canceled and re-scheduled with the new delay.
// - Otherwise, we don't need to cancel the runnable. It will be rescheduled when it is
// executed.

final Duration duration = Duration.ofMillis(dueDate - ActorClock.currentTimeMillis());
final Duration delay = calculateDelayForNextRun(dueDate);

if (scheduledTimer == null) {
scheduledTimer = actor.runDelayed(duration, this::triggerEntities);
scheduledTimer = actor.runDelayed(delay, this::triggerEntities);
nextDueDate = dueDate;

} else if (nextDueDate - dueDate > timerResolution) {
scheduledTimer.cancel();

scheduledTimer = actor.runDelayed(duration, this::triggerEntities);
scheduledTimer = actor.runDelayed(delay, this::triggerEntities);
nextDueDate = dueDate;
}
}
Expand All @@ -62,14 +62,28 @@ private void triggerEntities() {
// reschedule the runnable if there are timers left

if (nextDueDate > 0) {
final Duration duration = Duration.ofMillis(nextDueDate - ActorClock.currentTimeMillis());
scheduledTimer = actor.runDelayed(duration, this::triggerEntities);
final Duration delay = calculateDelayForNextRun(nextDueDate);
scheduledTimer = actor.runDelayed(delay, this::triggerEntities);

} else {
scheduledTimer = null;
}
}

/**
* Calculates the delay for the next run so that it occurs at (or close to) due date. If due date
* is in the future, the delay will be precise. If due date is in the past, now or in the very
* near future, then a lower floor is applied to the delay. The lower floor is {@code
* timerResolution}. This is to prevent the checker from being immediately rescheduled and thus
* not giving any other tasks a chance to run.
*
* @param dueDate due date for which a scheduling delay is calculated
* @return delay to hit the next due date; will be {@code >= timerResolution}
*/
private Duration calculateDelayForNextRun(final long dueDate) {
return Duration.ofMillis(Math.max(dueDate - ActorClock.currentTimeMillis(), timerResolution));
}

@Override
public void onRecovered(final ReadonlyProcessingContext processingContext) {
actor = processingContext.getActor();
Expand Down