Skip to content

Code updates

When you deploy, new requests to your Worker run the new code within seconds. A Durable Object that is already running is different: it keeps running the code it started with until it is safe to switch. Knowing when that happens tells you how to write code that survives a deploy and tests that see what you just shipped.

Each deploy runs in new isolates. The Construction phase runs again, and anything the old isolate held in memory is gone:

export default Cloudflare.Worker(
"Worker",
{ main: import.meta.url },
Effect.gen(function* () {
// Runs once per isolate, so again after every deploy
const counters = yield* Counter;
return {
fetch: Effect.gen(function* () {
return HttpServerResponse.text("Hello from my Worker!");
}),
};
}),
);

The new version can take a few seconds to reach every location, so retry the first request after a deploy. See Retry the first request.

A Durable Object keeps the code it started with

Section titled “A Durable Object keeps the code it started with”

Each object runs one copy of your code, the inner Effect, from the moment it starts:

export default class Counter extends Cloudflare.DurableObject<Counter>()(
"Counter",
Effect.gen(function* () {
const state = yield* Cloudflare.DurableObjectState;
return Effect.gen(function* () {
// Runs each time the object starts
let count = (yield* state.storage.get<number>("count")) ?? 0;
return {
increment: () =>
Effect.gen(function* () {
count += 1;
yield* state.storage.put("count", count);
return count;
}),
};
});
}),
) {}

An object stops when it goes idle. It hibernates after about 10 seconds without work, or is evicted after 70 to 140 seconds if something keeps it from hibernating, such as a pending setTimeout or an open outbound connection. Its next request starts it again on the latest code. count is read back from storage, so it survives. Anything kept only in memory does not.

By default an object that is busy during a deploy keeps running its current code for up to 5 minutes while it waits to hibernate. Then it resets onto the new code:

{
main: import.meta.url,
// the default
codeUpdateStrategy: { mode: "deferred", maxDelay: "5 minutes" },
}

Requests in progress get time to finish, and hibernatable WebSockets stay connected when the object picks up the new code after hibernating.

Set maxDelay to anything from 0 to 24 hours:

{
main: import.meta.url,
codeUpdateStrategy: { mode: "deferred", maxDelay: "1 minute" },
}

Waiting is best effort. An object can still reset sooner, for example when Cloudflare updates the Workers runtime.

Use immediate when every object must run the new code as soon as the deploy finishes, such as when you ship a fix during an incident:

{
main: import.meta.url,
codeUpdateStrategy: { mode: "immediate" },
}

Every running object resets. A request it was handling fails with Durable Object reset because its code was updated. Data in state.storage is kept; in-memory values are lost.

The strategy is not a setting stored on the Worker. It is sent with each deploy. Changing only codeUpdateStrategy does not deploy anything, because a redeploy would reset the objects it is meant to protect. Your next deploy uses the new value.

While an object waits, a Worker running the new code can call an object still running the old code. Calling a method the old code doesn’t have fails:

src/worker.ts
const counter = counters.getByName(name);
const next = yield* counter.increment();
const next = yield* counter.incrementBy(5); // missing on objects still running the old code

Ship the new method in one deploy and start calling it in the next, once maxDelay has passed. Keep method arguments and return values readable by both versions in the meantime.

An object can reset at any time: on deploy, when maxDelay runs out, or during a runtime update. Save each change to storage when it happens, as increment does, rather than when the object stops. Make operations safe to retry, so a caller can repeat a request that failed during a reset.

Gradual deployments assign objects to versions

Section titled “Gradual deployments assign objects to versions”

During a gradual deployment, Cloudflare assigns each object to one version. Raising the new version’s traffic moves more objects onto it. Only objects that move wait for maxDelay; the rest keep running.

A test that deploys and then calls a Durable Object can reach an object an earlier deploy started. If that object stayed busy, it can answer with the old code for up to 5 minutes.

Deploy your test stages with immediate, so every object runs the code the test just deployed:

src/worker.ts
import * as Cloudflare from "@alchemy.run/cloudflare";
import { Stack } from "alchemy/Stack";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
import Counter from "./counter.ts";
export default Cloudflare.Worker(
"Worker",
Stack.useSync(({ stage }) => ({
main: import.meta.url,
codeUpdateStrategy: stage === "prod" ? undefined : { mode: "immediate" },
})),
Effect.gen(function* () {
const counters = yield* Counter;
return {
fetch: Effect.gen(function* () {
return HttpServerResponse.text("Hello from my Worker!");
}),
};
}),
);

Or give each test its own object. A new object always starts on the latest code:

test(
"increment starts at 1",
Effect.gen(function* () {
const { url } = yield* stack;
const name = crypto.randomUUID();
const response = yield* HttpClient.post(`${url}/counter/${name}`);
expect(yield* response.text).toBe("1");
}),
);