silk.core.schedule.
Schedules: policies that say when to run an effect again, and when to stop.
When to use#
Use a Schedule to retry a failed effect with a delay, or to repeat an effect on a timer.
The drivers are Effect.retry, Effect.retryWhile and Effect.repeat in silk.core.effect.
| Constructor | Delay before recurrence n (from 0) |
|---|---|
Schedule.spaced | the same delay each time, counted from the end of the last run |
Schedule.fixed | the time to the next multiple of the interval since the start |
Schedule.exponential | base * 2^n |
Schedule.exponentialBy | base * factor^n |
Schedule.fibonacci | base, base, 2 * base, 3 * base, 5 * base, ... |
Schedule.linear | base * (n + 1) |
Schedule.forever | no delay, with no limit |
Schedule.recurs | no delay, at most times recurrences |
Schedule.limit, Schedule.capDelay, Schedule.upTo, Schedule.jittered and
Schedule.jitteredBetween change a schedule.
Details#
A Schedule is a plain value. It holds no clock and no progress, so one value can drive any
number of runs. A driver keeps the progress of one run in a ScheduleState that
Schedule.start makes, and asks Schedule.next for each delay. A custom driver, such as
one that honours a server's Retry-After, uses the same two operations.
Durations are u64 nanoseconds, so a duration literal such as 250ms works.
Gotchas#
A recurrence is a run after the first one.
Schedule.recurs(3)withEffect.repeatruns the effect four times, and withEffect.retrymakes at most four attempts.Jitter draws from the
InsecureRandomeffect, so the same seed gives the same delays. The drivers require&mut InsecureRandomeven when the schedule has no jitter.
Examples#
Repeat on a fixed beat#
import silk.core.effect { Effect }
import silk.core.schedule { Schedule }
import silk.system.insecure_random { InsecureRandom }
import silk.system.monotonic_clock { MonotonicClock }
import silk.system.system_clock { Instant, SystemClock }
struct TestClock {
mark: Instant,
}
impl MonotonicClock for TestClock {
effect fn now(self: &mut Self) -> Instant {
return SystemClock.make(SystemClock.seconds(&self.mark), SystemClock.nanoseconds(&self.mark))
}
effect fn getResolution(self: &mut Self) -> u64 {
return 1
}
effect fn waitUntil(self: &mut Self, when: Instant) -> () {
self.mark = move when
return ()
}
effect fn waitFor(self: &mut Self, howLong: u64) -> () {
self.mark = MonotonicClock.deadlineAfter(&self.mark, howLong)
return ()
}
}
struct Beats {
count: i32,
}
effect fn beat(beats: &mut Beats) -> () {
beats.count = beats.count + 1
return ()
}
pub fn main() -> i32 {
let mut clock = TestClock { mark: SystemClock.make(0, 0) }
let mut random = InsecureRandom.seeded(1)
let mut beats = Beats { count: 0 }
// One run, then three more, each on the next 500ms beat.
let steady = Schedule.fixed(500ms) |> Schedule.limit(3)
run Effect.repeat(beat(&mut beats), &steady)
|> Effect.provideMut<InsecureRandom>(&mut random)
|> Effect.provideMut<MonotonicClock>(&mut clock)
if beats.count != 4 || SystemClock.seconds(&clock.mark) != 1 || SystemClock.nanoseconds(&clock.mark) != 500000000 {
return 1
}
return 0
}Import as Schedule with import silk.core.schedule { Schedule }.
Public declarations: 2.
Schedule#
pub struct ScheduleA policy that says how long to wait before each recurrence, and when to stop.
When to use#
Pass a Schedule to Effect.retry, Effect.retryWhile or Effect.repeat. Make one with a
constructor such as Schedule.spaced or Schedule.exponential, and change it with
Schedule.limit, Schedule.capDelay, Schedule.upTo or Schedule.jittered.
Details#
A Schedule holds no progress, so one value can drive any number of runs. Durations are u64
nanoseconds.
Examples#
Back off exponentially, at most five times#
import silk.core.schedule { Schedule }
pub fn main() -> i32 {
let policy = Schedule.exponential(100ms) |> Schedule.capDelay(2s) |> Schedule.limit(5)
drop policy
return 0
}ScheduleState#
pub struct ScheduleStateThe progress of one run of a Schedule: how many recurrences it has made, and when it began.
When to use#
Only a driver needs it. Make one with Schedule.start when the run begins, and pass it to
each Schedule.next of the same run.
Implementation Schedule#
impl ScheduleAssociated function forever#
pub fn forever() -> ScheduleRecurs with no delay and no limit.
Gotchas#
Effect.repeat with this schedule never stops unless the effect fails, and Effect.retry
never stops unless the effect succeeds. Add Schedule.limit or Schedule.upTo to stop.
Associated function recurs#
pub fn recurs(times: u64) -> ScheduleRecurs with no delay, at most times times.
Details#
With Effect.repeat, the effect runs times + 1 times. With Effect.retry, it makes at most
times + 1 attempts.
Associated function spaced#
pub fn spaced(delay: u64) -> ScheduleWaits the same delay before each recurrence.
Details#
The delay counts from the end of the run before, so the time the effect takes adds to the
period. Use Schedule.fixed to keep a steady beat.
Associated function fixed#
pub fn fixed(interval: u64) -> ScheduleRecurs on each multiple of interval after the start of the run.
When to use#
Use it for a steady beat, such as a poll every second or a note every eighth of a bar. The time each run takes does not move later recurrences.
Details#
Recurrence n, counted from 0, is due at (n + 1) * interval after Schedule.start, and
the delay is the time from now to that beat. If a run ends after its next beat, the
recurrence happens at the first beat that is not past, or at once when the run ends exactly
on a beat. Thus missed beats do not pile up, and a late run does not move later beats. An
interval of 0 recurs at once.
Gotchas#
Schedule.jittered moves each recurrence off its beat by the jitter.
Associated function exponential#
pub fn exponential(base: u64) -> ScheduleWaits base * 2^n before recurrence n, counted from 0.
Details#
The delay is computed in f64. A delay that is more than a u64 can hold becomes u64.MAX.
Associated function exponentialBy#
pub fn exponentialBy(base: u64, factor: f64) -> ScheduleWaits base * factor^n before recurrence n, counted from 0.
Details#
A factor between 0 and 1 makes the delays shorter each time. A delay that is more than a
u64 can hold becomes u64.MAX.
Associated function fibonacci#
pub fn fibonacci(base: u64) -> ScheduleWaits base times the next Fibonacci number: base, base, 2 * base, 3 * base, ...
Associated function linear#
pub fn linear(base: u64) -> ScheduleWaits base * (n + 1) before recurrence n, counted from 0.
Method limit#
pub fn limit(self: Schedule, times: u64) -> ScheduleStops self after at most times recurrences.
Details#
When self already has a smaller limit, that limit stays.
Method capDelay#
pub fn capDelay(self: Schedule, most: u64) -> ScheduleMakes each delay of self at most most.
Details#
The cap applies after jitter. When self already has a smaller cap, that cap stays.
Method upTo#
pub fn upTo(self: Schedule, howLong: u64) -> ScheduleStops self when howLong has passed since the start of the run.
Details#
The check happens before each recurrence. A run that is already in progress is not stopped.
When self already has a shorter time, that time stays.
Method jittered#
pub fn jittered(self: Schedule) -> ScheduleMultiplies each delay of self by a random factor between 0.8 and 1.2.
When to use#
Use it so that many clients that retry at the same time spread out.
Method jitteredBetween#
pub fn jitteredBetween(self: Schedule, min: f64, max: f64) -> ScheduleMultiplies each delay of self by a random factor between min and max.
Details#
The factor is uniform in [min, max), from one InsecureRandom.nextU64 for each delay. Give
min at most max, and both at least 0: a delay below 0 becomes 0.
Method start#
pub fn start(self: &Schedule, now: Instant) -> ScheduleStateBegins one run of self at now.
When to use#
Only a driver needs it. Read the clock when the run begins, before the first run of the
effect, and pass the state to each Schedule.next of the run.
Method next#
pub effect fn next(self: &Schedule, state: &mut ScheduleState, now: &Instant) -> Option<u64>
? &mut InsecureRandomDecides the next recurrence of a run: the delay to wait before it, or None to stop.
Details#
Pass the time of the decision as now. A Some counts one recurrence in state. The draw
from InsecureRandom happens only when self has jitter.
Category: Core.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.