Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/content/docs/durable-objects/api/base.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,60 @@ class MyDurableObject(DurableObject):

</TabItem> </Tabs>

### `[shutdown]`

:::note[Beta]
Shutdown hooks are in Beta.
:::

Import the `shutdown` well-known symbol from `cloudflare:workers`. Define the hook with a computed method name, as shown in the example.

- <code>[shutdown](info)</code>
: <Type text="void" /> | <Type text="Promise<void>" />
- Runs before idle eviction or a reset caused by a code update. Use this handler for best-effort clean shutdown logic, such as a final storage checkpoint.
- The hook is not guaranteed to run. It does not run after `ctx.abort()`, broken input or output gates, resource limit termination, loss of connectivity to the object's host, or a crash. Runtime process reloads and drains also do not trigger the hook.
- Once the hook starts, shutdown cannot be canceled. The instance receives no new requests, RPC calls, alarms, or WebSocket messages.
- A racing request waits for teardown and any pending storage writes to complete. The request then reaches the new instance.
- During code updates, the hook starts immediately and may run concurrently with in-flight requests already being processed; those requests continue until the hook's returned promise resolves. Account for changes made by those events.
- The runtime awaits the returned value, whether it is a promise or not. The handler has a 10-second wall-clock budget.
- After the hook finishes, throws an error, or times out, any storage writes it issued are still allowed to complete.
- The runtime logs rejections and timeouts, then continues shutdown. It does not retry the hook.
- The new instance's constructor starts after the previous instance's storage writes complete.
- The hook does not reset the inactivity timer.
- The hook runs before hibernatable WebSockets are hibernated. You can send a final message with `ws.send()`.
- During Beta, [Durable Object Facets](/dynamic-workers/usage/durable-object-facets/) are not supported.
- Local development follows the same behavior as production.
- The hook has no per-request `AsyncLocalStorage` context.
- The symbol is not exposed on a Durable Object stub. Therefore, callers cannot invoke the hook over RPC.
- Your code can call the symbol directly after importing it. Keep the hook idempotent and reentrant, or do not call it directly.

#### Parameters

- `info` - an object describing why shutdown is happening:
- `reason` <Type text="string"/> - `"inactive"` for idle eviction or `"codeUpdated"` for a code update. Handle unknown values because more reasons may be added.

#### Return values

- None.

#### Example

<TypeScriptExample filename="index.ts">
```ts
import { DurableObject, shutdown } from "cloudflare:workers";

export class MyDurableObject extends DurableObject<Env> {
async [shutdown](info) {
console.log(`Shutting down: ${info.reason}`);
await this.ctx.storage.put("lastShutdown", {
reason: info.reason,
timestamp: Date.now(),
});
}
}
```
</TypeScriptExample>

## Properties

### `ctx`
Expand All @@ -321,3 +375,4 @@ class MyDurableObject(DurableObject):
- [Use WebSockets](/durable-objects/best-practices/websockets/) for WebSocket handler best practices.
- [Alarms API](/durable-objects/api/alarms/) for scheduling future work.
- [RPC methods](/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/) for type-safe method calls.
- [Lifecycle of a Durable Object](/durable-objects/concepts/durable-object-lifecycle/#shutdown-notifications) for when `[shutdown]()` runs relative to eviction and code updates.
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,58 @@ export class ChatRoom extends DurableObject<Env> {

<Render file="working-without-shutdown-hooks" product="durable-objects" />

Use [`[shutdown]()`](/durable-objects/api/base/#shutdown) for a final best-effort checkpoint. Combine it with relaxed count-based or time-based checkpoints during normal requests.

The following counter accepts the loss of increments since the previous checkpoint after an unexpected shutdown. It checkpoints every 100 increments and again during clean shutdown:

<TypeScriptExample filename="index.ts">
```ts
import { DurableObject, shutdown } from "cloudflare:workers";

export interface Env {
COUNTER: DurableObjectNamespace<Counter>;
}

export class Counter extends DurableObject<Env> {
private count: number;

constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.ctx.storage.sql.exec(
"CREATE TABLE IF NOT EXISTS counter (id INTEGER PRIMARY KEY, value INTEGER NOT NULL)"
);
this.ctx.storage.sql.exec(
"INSERT OR IGNORE INTO counter (id, value) VALUES (1, 0)"
);
this.count = this.ctx.storage.sql
.exec<{ value: number }>("SELECT value FROM counter WHERE id = 1")
.one().value;
}

increment() {
this.count++;
if (this.count % 100 === 0) {
this.checkpoint();
}
return this.count;
}

async [shutdown](info) {
console.log(`Shutting down: ${info.reason}`);
this.checkpoint();
await this.ctx.storage.put("lastShutdownReason", info.reason);
}

private checkpoint() {
this.ctx.storage.sql.exec(
"UPDATE counter SET value = ? WHERE id = 1",
this.count
);
}
}
```
</TypeScriptExample>

## Anti-patterns to avoid

### Do not use a single Durable Object as a global singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ products:
- durable-objects
---

import { Render } from "~/components";
import { TypeScriptExample } from "~/components";

This section describes the lifecycle of a [Durable Object](/durable-objects/concepts/what-are-durable-objects/).

Expand Down Expand Up @@ -93,8 +93,8 @@ Durable Objects will occasionally shut down and objects are restarted, which wil

When a Durable Object is shut down, the object instance is automatically restarted and new requests are routed to the new instance. In-flight requests are handled as follows:

- **HTTP & RPC requests**: In-flight requests are allowed to finish if they do not access a Durable Object's storage. If a request attempts to access a Durable Object's storage, it will be stopped immediately and return an error to maintain Durable Objects global uniqueness property. When the Worker runtime system is being updated, in-flight requests have up to 30 seconds to complete.
- **WebSocket connections**: WebSocket requests are terminated automatically during shutdown. This is so that the new instance can take over the connection as soon as possible.
- **HTTP & RPC requests**: In-flight requests are allowed to finish if they do not access a Durable Object's storage. If a request attempts to access a Durable Object's storage, it will be stopped immediately and return an error to maintain Durable Objects global uniqueness property. When the Worker runtime system is being updated, in-flight requests have up to 30 seconds to complete.
- **WebSocket connections**: Standard WebSocket connections are terminated during shutdown. WebSockets accepted with the [WebSocket Hibernation API](/durable-objects/best-practices/websockets/) remain connected when an object is evicted due to inactivity.
- **Other invocations (email, cron)**: Other invocations are treated similarly to HTTP requests.

It is important to ensure that any services using Durable Objects are designed to handle the possibility of a Durable Object being shut down.
Expand All @@ -103,6 +103,56 @@ It is important to ensure that any services using Durable Objects are designed t

When your Durable Object code is updated, your Worker and Durable Objects are released globally in an eventually consistent manner. This will cause a Durable Object to shut down, with the behavior described above. Updates can also create a situation where a request reaches a new version of your Worker in one location, and calls to a Durable Object still running a previous version elsewhere. Refer to [Code updates](/durable-objects/platform/known-issues/#code-updates) for more information about handling this scenario.

### Working without shutdown hooks
### Shutdown notifications
Comment thread
iglesiasbrandon marked this conversation as resolved.

<Render file="working-without-shutdown-hooks" product="durable-objects" />
:::note[Beta]
Shutdown hooks are in Beta.
:::

A Durable Object can define a [`[shutdown]()`](/durable-objects/api/base/#shutdown) hook. Use it for best-effort clean shutdown logic and final checkpoints.

<TypeScriptExample filename="index.ts">
```ts
import { DurableObject, shutdown } from "cloudflare:workers";

export class Counter extends DurableObject<Env> {
async [shutdown](info) {
Comment thread
iglesiasbrandon marked this conversation as resolved.
console.log(`Shutting down: ${info.reason}`);
await this.ctx.storage.put("lastShutdown", Date.now());
}
}
```
</TypeScriptExample>

`info` describes why shutdown is happening. `info.reason` is `"inactive"` for idle eviction or `"codeUpdated"` for a code update — handle unknown values, since more reasons may be added. Refer to [`[shutdown]()` parameters](/durable-objects/api/base/#shutdown) for the full reference.

The runtime invokes the hook for the shutdown causes described above:

| Shutdown cause | `[shutdown]()` invoked |
| ------------------------------------------------------------ | ---------------------- |
| Lack of requests to an object (eviction due to inactivity) | Yes |
| New Worker deployment with a code update | Yes |
| Workers runtime updates | No |
| Workers runtime decisions on where to host objects | No |

The hook also does not run for abnormal terminations, since these bypass the storage-healthy shutdown path required to safely run cleanup logic:

- `ctx.abort()`
- A broken input or output gate
- A CPU or memory limit termination
- Loss of connectivity to the object's host, or a crash

For runtime updates, in-flight requests retain the existing drain behavior described above, but `[shutdown]()` is not invoked.

`[shutdown]()` is not guaranteed to run. Persist important changes incrementally. SQLite storage operations such as `sql.exec()` are synchronous, while key-value storage operations are strongly consistent and complete asynchronously. Use the hook for best-effort clean shutdown work that differs from regular request handling.

### Shutdown behavior

- Once the hook starts, the shutdown is committed. The instance receives no new events. A racing request waits for teardown and any pending storage writes to complete, then reaches the new instance.
- During code updates, the hook starts immediately and may run concurrently with in-flight requests already being processed; those requests continue until the hook's returned promise resolves. Account for concurrent changes when checkpointing state.
- The hook has up to 10 seconds to run. Whether it finishes, throws an error, or times out, any storage writes it issued are still allowed to complete, and the runtime does not retry the hook.
- The hook runs before hibernatable WebSockets are hibernated. This ordering permits a final `ws.send()` call. During Beta, [Durable Object Facets](/dynamic-workers/usage/durable-object-facets/) are not supported.
- The symbol is not exposed on a Durable Object stub, so callers cannot invoke it over RPC. For the complete contract, refer to [`[shutdown]()`](/durable-objects/api/base/#shutdown).
- When [Workers tracing](/workers/observability/traces/) is enabled, the shutdown invocation produces a span with a `cloudflare.handler_type` value of `shutdown`.

For an example that combines regular checkpoints with a final shutdown checkpoint, refer to [Design for unexpected shutdowns](/durable-objects/best-practices/rules-of-durable-objects/#design-for-unexpected-shutdowns).
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ Cloudflare Workers provides automatic tracing instrumentation **out of the box**

- `cloudflare.scheduled_time`

#### [Durable Object shutdown hook](/durable-objects/api/base/#shutdown)

A shutdown invocation produces a tracing span, like a constructor invocation. Its `cloudflare.handler_type` value is `shutdown`.

---

### [D1](/d1/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@
{}
---

Durable Objects may shut down at any time due to deployments, inactivity, or runtime decisions. Rather than relying on shutdown hooks (which are not provided), design your application to write state incrementally.
import { TypeScriptExample } from "~/components";

Shutdown hooks or lifecycle callbacks that run before shutdown are not provided because Cloudflare cannot guarantee these hooks would execute in all cases, and external software may rely too heavily on these (unreliable) hooks.
Durable Objects may shut down at any time due to deployments, inactivity, or runtime decisions. Design your application to write state incrementally rather than relying only on a shutdown notification.

Instead of relying on shutdown hooks, you can regularly write to storage to recover gracefully from shutdowns.
Use the best-effort [`[shutdown]()`](/durable-objects/api/base/#shutdown) hook for clean shutdown logic. The hook runs before idle eviction and code-update resets, but it is not guaranteed to run.

For example, if you are processing a stream of data and need to save your progress, write your position to storage as you go rather than waiting to persist it at the end:
Persist important changes during normal request processing rather than deferring them to shutdown. SQLite storage operations such as `sql.exec()` complete synchronously, and key-value storage writes issued without an intervening `await` are automatically coalesced into a single atomic transaction — so writing incrementally does not add meaningful overhead compared to batching writes until shutdown.

```js
// Good: Write progress as you go
async processData(data) {
data.forEach(async (item, index) => {
await this.processItem(item);
// Save progress frequently
await this.ctx.storage.put("lastProcessedIndex", index);
});
For example, persist each event as it arrives rather than buffering events in memory until shutdown:

<TypeScriptExample filename="index.ts">
```ts
import { DurableObject, shutdown } from "cloudflare:workers";

export class EventRecorder extends DurableObject<Env> {
async recordEvent(eventId: string, payload: string) {
await this.ctx.storage.put(`event:${eventId}`, payload);
}

async [shutdown](info) {
// Record shutdown metadata, but do not rely on this write for progress.
await this.ctx.storage.put("lastShutdownReason", info.reason);
}
}
```
</TypeScriptExample>

While this may feel unintuitive, Durable Object storage writes are fast and synchronous, so you can persist state with minimal performance concerns.
This approach ensures that recorded events survive an unexpected shutdown. Refer to [`[shutdown]()`](/durable-objects/api/base/#shutdown) for hook details.

This approach ensures your Durable Object can safely resume from any point, even if it shuts down unexpectedly.
```
Loading