Skip to content

Reliability and operations

Attempts use capped exponential backoff with equal jitter by default, stop after 5 attempts, and time out after 5 minutes. Override the policy globally or for one operation:

import { exponential, jitter } from 'durability/utils';
const durability = createDurability(this.ctx, handlers, {
alarmConcurrency: 10,
alarmHandoffMs: 14 * 60_000,
attemptTimeoutMs: 60_000,
retries: {
maxAttempts: 5,
delay: (attempt) => jitter(exponential(attempt)),
},
methods: {
resizeImage: {
attemptTimeoutMs: 10 * 60_000,
retries: {
maxAttempts: 2,
delay: (attempt) => exponential(attempt, 500, 30_000),
},
},
},
});

The delay(attempt) function controls the complete retry schedule and returns milliseconds. durability/utils exports:

  • exponential(attempt, baseMs?, capMs?)
  • jitter(delayMs)

Every attempt receives an AbortSignal. Abort-aware APIs stop promptly when the attempt times out; arbitrary handler code cannot be forcibly terminated.

const handlers = {
sendEmail: async ({ payload, signal }: DurableCall<EmailPayload>) =>
fetch(payload.url, { method: 'POST', signal }),
};

Throw NonRetryableError for a permanent failure:

import { NonRetryableError } from 'durability';
throw new NonRetryableError('Recipient permanently rejected');

Durability also recognizes NonRetryableError values from cloudflare:workflows.

alarmConcurrency controls how many due operations one alarm invocation executes concurrently. Durability reconciles the earliest pending operation, retry, or named alarm into the object’s physical alarm.

Cloudflare alarm invocations have a 15-minute wall-time limit. Before that limit, Durability can return and arm an immediate handoff alarm while retaining pending promises in memory:

createDurability(this.ctx, handlers, {
alarmHandoffMs: 14 * 60_000,
});

The next invocation attaches to the existing promise if the object remains alive. After eviction or restart, it reconstructs persisted work and can execute the handler again.

Registration and earliest wake-up reconciliation happen in one Durable Object storage transaction. Stable IDs deduplicate concurrent calls and completed records, but arbitrary external effects cannot be strictly exactly once:

  1. An external effect succeeds.
  2. The process stops before Durability commits completion.
  3. A later alarm retries the operation.

Pass the operation ID or named alarm idempotencyKey downstream to close this gap where the external system supports deduplication.

A Durability-enabled coordinator can persist saga progress and retry participant steps. Give every participant a stable, step-specific ID so its own Durability instance deduplicates retries.

This provides eventual coordination, not a transaction across Durable Objects. Intermediate states can be visible. Persist progress, define terminal handling, and implement compensating operations when partial work must be undone.