Skip to content

Sleep

ctx.Sleep pauses a workflow for any duration, from seconds to weeks. The run is parked in Postgres and resumed by a worker when the timer fires, so it survives restarts, crashes, and deployments that keep the in-flight workflow’s durable call order compatible. A sleeping workflow does not hold a thread, a worker slot, or any memory.

await ctx.Activity((EmailActivities a) => a.SendWelcome(input.Email), cancellationToken);
await ctx.Sleep(TimeSpan.FromDays(11), cancellationToken);
await ctx.Activity((EmailActivities a) => a.SendTrialEndingReminder(input.Email), cancellationToken);

When the workflow reaches ctx.Sleep, the run’s visible_at is pushed to the deadline and its lease is released, freeing the worker for other work immediately. When the deadline passes, the run becomes visible again and the next free worker resumes it, replaying the already-completed steps from their stored results.

The deadline is persisted the first time the sleep is encountered, so it stays stable across replays: a run that crashes and resumes mid-sleep does not restart the clock, and a sleep that already elapsed is skipped on replay instead of sleeping again.

Parking is implemented by throwing an internal control-flow exception that unwinds the workflow method, so don’t wrap ctx.Sleep in a broad catch, which would swallow the park. If it happens, the run fails loudly rather than silently skipping the timer.

Timer precision is bounded by worker polling: the run resumes on the first poll after the deadline, so the resolution is roughly the worker’s PollInterval (250 ms by default). For sleeps measured in minutes to months this is irrelevant.