Built-in Schedules — exponential, fibonacci, forever
id_effect provides a library of common schedule types. This section catalogs them with typical use cases.
exponential
#![allow(unused)] fn main() { Schedule::exponential(Duration::from_millis(100)) // Delays: 100ms, 200ms, 400ms, 800ms, 1600ms, ... }
The standard backoff pattern. Each delay doubles. Use for most network retry scenarios — it backs off quickly and gives the downstream service time to recover.
#![allow(unused)] fn main() { // With cap: 100ms, 200ms, 400ms, 800ms, 800ms, 800ms (capped at 800ms) Schedule::exponential(Duration::from_millis(100)) .with_max_delay(Duration::from_millis(800)) }
fibonacci
#![allow(unused)] fn main() { Schedule::fibonacci(Duration::from_millis(100)) // Delays: 100ms, 100ms, 200ms, 300ms, 500ms, 800ms, 1300ms, ... }
Fibonacci backoff grows more gradually than exponential — useful when you want more retries in the early attempts before backing off significantly.
spaced
#![allow(unused)] fn main() { Schedule::spaced(Duration::from_secs(5)) // Delays: 5s, 5s, 5s, 5s, ... (constant) }
Fixed interval. Use for polling (checking status every N seconds), heartbeats, or scenarios where the delay should be predictable.
forever
#![allow(unused)] fn main() { Schedule::forever() // No delay between repetitions; runs indefinitely }
Run an effect as fast as possible, forever. Use with repeat for continuous background jobs (e.g., metrics collection, background syncs). Almost always combined with .take(n) or .until_total_duration(d).
Limiters
Limiters constrain any schedule:
#![allow(unused)] fn main() { // Stop after N attempts schedule.take(5) // Stop after N successes (for repeat) schedule.take_successes(3) // Stop after total elapsed time schedule.until_total_duration(Duration::from_secs(30)) // Stop after N total elapsed retries including initial schedule.take_with_initial(6) // 1 initial + 5 retries }
Jitter
#![allow(unused)] fn main() { // Add random delay in [0, jitter_max) to each wait schedule.with_jitter(Duration::from_millis(50)) // Alternatively, full jitter (random in [0, delay]) schedule.with_full_jitter() }
Jitter prevents retry storms. When 1000 clients all hit a failing service and all retry at the same time, they create a "thundering herd." Jitter spreads the retries out.
Combining with &&
#![allow(unused)] fn main() { let bounded_exponential = Schedule::exponential(Duration::from_millis(100)) .take(5) .with_jitter(Duration::from_millis(20)) .until_total_duration(Duration::from_secs(10)); }
The && (chain) operation means "continue only if both schedules agree to continue." The first schedule that says "done" wins.