Automatic Retries for Durable Object Calls: Maybe No More Hand-Written Retry Loops?
Hi, it's Shii-chan!
Cloudflare ChangelogHere is good news for anyone using Durable Objects: you may be able to delete some retry code!
What was announced?
In the Cloudflare Changelog, the runtime now retries fetch() and RPC calls to a Durable Object when they fail with a retryable error before reaching it. This applies whether the caller is a Worker or another Durable Object.
The story so far
Even for short disruptions such as a brief network interruption, you had to write a retry loop in the calling code. With this change, short disruptions no longer need one.
What changes
- You don't need your own retry code for brief network problems
- You can opt specific idempotent methods in to safe re-execution
Dive Deep
Default behavior
- Up to four retries per call
- No retry starts more than 10 seconds after the initial call starts
- Errors thrown by your Durable Object code are not retried
- Errors with
.overloadedset to true are not retried - If the connection drops and the runtime can't tell whether the Durable Object received the call, it still retries, but the Durable Object rejects the retry if the first attempt arrived
Methods and double execution
A method never runs twice because of a retry unless you mark it with retryable() from cloudflare:durable-objects. Only mark idempotent methods, and leave things like increments unmarked.
import { DurableObject } from "cloudflare:workers";
import { retryable } from "cloudflare:durable-objects";
export class Inventory extends DurableObject {
static {
retryable(this.prototype.setStock);
}
// Idempotent: running it again leaves the same stock count
async setStock(sku, count) {
await this.ctx.storage.put(sku, count);
}
// Not retryable: running it again would remove another unit
async decrementStock(sku) {
const current = (await this.ctx.storage.get(sku)) ?? 0;
await this.ctx.storage.put(sku, current - 1);
}
}
Per-stub settings
Pass a retry option to get() or getByName():
maxRetries: 0 to 10timeoutMs: 500 to 60,000 milliseconds
const stub = env.INVENTORY.getByName("warehouse-7", {
retry: { maxRetries: 2, timeoutMs: 5000 },
});
Things to check
- If your code already retries, each of your attempts now includes the runtime's retries. Lower your own attempt count, or set
maxRetries: 0on stubs where your code handles retries - Using
retryable()and theretryoption requires Wrangler 4.158.0 or later
Wrap-up
fetch()and RPC calls to a Durable Object are retried automatically on retryable errors before reaching it- Defaults are up to four retries within 10 seconds of the start
- Mark only idempotent methods with
retryable() - Tune with the
retryoption (Wrangler 4.158.0 or later)
If you've been writing your own retries around Durable Objects, now is a good time to review that code!