Cancellation and shutdown
Coordinate AbortSignal cancellation, Worker stop and Runtime ownership.
Cancellation is cooperative. awaitResult can stop waiting without cancelling
the persisted Job, and a handler observes the attempt's CurrentAbortSignal.
Use an application AbortController when a request or process boundary owns
that cancellation.
The following continuation assumes the canonical SendEmail descriptor from
Defining Jobs and a jobId supplied by the caller; it shows
how to stop waiting without cancelling the persisted Job.
const controller = new AbortController()
const waiting = SendEmail.awaitResult(jobId, { signal: controller.signal })
controller.abort(new Error('request closed'))
// The Job remains persisted; only this wait ends.
await waitingA graceful long-lived process owns Layer-first Worker resources in this order:
- stop accepting new work;
- quiesce Worker admission;
- await active attempts and store settlements/releases;
- release the Worker and dispose the Runtime root resources.
const AppWorker = Worker.service('@app/Worker')
const AppWorkerLive = AppWorker.layer(() => ({ handlers }))
const application = Layer.complete(Layer.merge(AppLive, AppWorkerLive))
const runtime = await Runtime.make(application)
try {
await serveRequests()
} finally {
// Runtime disposal quiesces, drains and releases AppWorker.
await runtime.dispose()
}Layer-owned Worker release is idempotent. It stops claim loops, waits for active attempts according to the configured shutdown policy and cleans supervision resources. Runtime disposal rejects new executions, waits for active executions, closes root scopes and disposes the backend last. Do not replace this sequence with a forced process exit: in-flight settlement can otherwise be delivered again.
A lease expiry or shutdown can race with a handler result. The store's current lease token and atomic settlement determine the winner. Make side effects safe for either outcome.
Keep payload validation independent from shutdown ownership: define boundary
schemas with Zod 4 and better-effect-schema, then pass them to
Codec.standardSchema. The trusted JSON escape hatch
is only for trusted JSON values and does not replace validation.