Durable timers: sleep 30 days on zero compute

A workflow sleep records a TimerStarted event and the workflow goes fully dormant on zero compute until the engine fires TimerFired at the deadline — so a long wait survives any number of crashes and redeploys, because the timer is an event in history, not a sleeping thread.

Previously

We've now survived crashes at every step of a FAST order. But real orders aren't fast — ORDER #1001 must wait 7 days for the 'rate this product' email. A thread that sleeps for a week dies on the first crash in that week. So the engine records the wait as a durable event in the history and the workflow goes completely dormant, using zero compute, until the engine fires the wake-up event. Time becomes a first-class durable object: the timer.

Scene 07

Durable timers: sleep 30 days on zero compute

  1. Watch
  2. Try it
  3. Predict
  4. Capture
ORDER #1001 · wait 7d before the “rate this product” emailcrashes during the wait: 0THREAD-BASED sleep(7d)a real worker is HELD for the whole waitworker processin-RAM sleep⏳ 4d leftworker compute while waitingHIGH (held)✓ fires on timeDURABLE TIMER — the wait is an event the engine ownsworkflow dormant · the engine tracks the deadlineworkflow (blue)dormant · 0 computeworker compute while waiting≈ 0 (free)ENGINE COUNTDOWN4d leftEVENT HISTORY (append-only) — the timer lives here, not in a threadWorkflowStartedorder=1001ActivityCompletedShipPackage okTimerStartedrate-email +7dTimerStartedrate-email +7dSame wait, two models: a held thread dies with the first crash; a durable timer is an event the engine owns, so it …
What to watch for

ORDER #1001 has shipped. Now it must wait 7 days before sending the "rate this product" email. The obvious way — call sleep(7 days) — holds a real worker process for the whole week, which is what the TOP model shows. But the engine has a better way. When your workflow code asks to wait, the engine doesn't block a thread; it appends a TimerStarted event to ORDER #1001's history and the workflow box goes completely dark: dormant — holding no worker and burning essentially zero compute, because the only thing tracking the deadline now is the engine itself. When the countdown hits zero, the engine appends a TimerFired event and wakes the workflow via the task queue. That whole mechanism — a wait recorded as a TimerStarted/TimerFired event pair that the engine owns — is a durable timer: time is stored as an event in history, not held in a sleeping thread. Watch the durable model start the wait and go dormant, then fire on schedule.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Workflow.waitForRating
two ways to wait — one dies on crash, one records an event
def waitForRating(order):
ship(order)
# BROKEN: held thread, countdown lives in RAM
sleep(days=7) # dies on first crash
# DURABLE: yields, recording a TimerStarted event
await workflow.sleep(days=7) # returns control; goes dormant
send_rating_email(order)
Engine.onTimerStarted
persist the deadline to history, then unload the workflow
def onTimerStarted(wf_id, duration):
fire_at = now() + duration
history.append(wf_id, TimerStarted(fire_at))
schedule.add(wf_id, fire_at) # engine owns the countdown
unload(wf_id) # dormant: no worker held
Engine.fireTimers
deadline (or recovery) appends TimerFired and re-queues
def fireTimers(): # also runs on recovery after a crash
for wf_id, fire_at in schedule.due(now()):
history.append(wf_id, TimerFired())
task_queue.put(wf_id) # wake it
replay(wf_id) # resumes right after the sleep

Where this sits in Build a workflow engine (Temporal / Airflow / Cadence style)

Scene 07 of 13, in the Time & actors act — Durable timers and the workflow as an actor.. A thread that sleeps for a month dies on the first crash; a durable timer records the wait as an event, so the workflow goes dormant until the engine fires the wake-up.

Up next. Waiting on a clock is one thing; reacting to the outside world is another. A running workflow is a long-lived addressable thing you can send messages to — a fire-and-forget signal that changes its path (cancel the order), or a read-only query that peeks at its state. The workflow becomes an actor.

All 13 scenes in Build a workflow engine (Temporal / Airflow / Cadence style) · Every curriculum

Built with Arqly
Every scene in Build a workflow engine (Temporal / Airflow / Cadence style) builds on the one before it.All 13 Build a workflow engine (Temporal / Airflow / Cadence style) scenes