Browse the docs

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.

ConstructorDelay before recurrence n (from 0)
Schedule.spacedthe same delay each time, counted from the end of the last run
Schedule.fixedthe time to the next multiple of the interval since the start
Schedule.exponentialbase * 2^n
Schedule.exponentialBybase * factor^n
Schedule.fibonaccibase, base, 2 * base, 3 * base, 5 * base, ...
Schedule.linearbase * (n + 1)
Schedule.foreverno delay, with no limit
Schedule.recursno 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) with Effect.repeat runs the effect four times, and with Effect.retry makes at most four attempts.

  • Jitter draws from the InsecureRandom effect, so the same seed gives the same delays. The drivers require &mut InsecureRandom even when the schedule has no jitter.

Examples#

Repeat on a fixed beat#

silk
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#

silk
pub struct Schedule

A 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#

silk
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#

silk
pub struct ScheduleState

The 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#

silk
impl Schedule

Associated function forever#

silk
pub fn forever() -> Schedule

Recurs 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#

silk
pub fn recurs(times: u64) -> Schedule

Recurs 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#

silk
pub fn spaced(delay: u64) -> Schedule

Waits 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#

silk
pub fn fixed(interval: u64) -> Schedule

Recurs 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#

silk
pub fn exponential(base: u64) -> Schedule

Waits 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#

silk
pub fn exponentialBy(base: u64, factor: f64) -> Schedule

Waits 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#

silk
pub fn fibonacci(base: u64) -> Schedule

Waits base times the next Fibonacci number: base, base, 2 * base, 3 * base, ...

Associated function linear#

silk
pub fn linear(base: u64) -> Schedule

Waits base * (n + 1) before recurrence n, counted from 0.

Method limit#

silk
pub fn limit(self: Schedule, times: u64) -> Schedule

Stops self after at most times recurrences.

Details#

When self already has a smaller limit, that limit stays.

Method capDelay#

silk
pub fn capDelay(self: Schedule, most: u64) -> Schedule

Makes 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#

silk
pub fn upTo(self: Schedule, howLong: u64) -> Schedule

Stops 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#

silk
pub fn jittered(self: Schedule) -> Schedule

Multiplies 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#

silk
pub fn jitteredBetween(self: Schedule, min: f64, max: f64) -> Schedule

Multiplies 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#

silk
pub fn start(self: &Schedule, now: Instant) -> ScheduleState

Begins 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#

silk
pub effect fn next(self: &Schedule, state: &mut ScheduleState, now: &Instant) -> Option<u64>
    ? &mut InsecureRandom

Decides 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.

View the source of this page