Skip to content

Commit 7c82b60

Browse files
thomasgauvinOxyjunlambrospetrou
committed
Explain how Durable Objects hibernation works (#23103)
* Explain how Durable Objects hibernation works * Update src/content/docs/durable-objects/best-practices/websockets.mdx * Update src/content/docs/durable-objects/best-practices/websockets.mdx Co-authored-by: Jun Lee <[email protected]> * Update src/content/docs/durable-objects/best-practices/websockets.mdx Co-authored-by: Lambros Petrou <[email protected]> * Update src/content/docs/durable-objects/best-practices/websockets.mdx Co-authored-by: Lambros Petrou <[email protected]> * Update src/content/docs/durable-objects/best-practices/websockets.mdx Co-authored-by: Lambros Petrou <[email protected]> * Update src/content/docs/durable-objects/best-practices/websockets.mdx * Update src/content/docs/durable-objects/best-practices/websockets.mdx Co-authored-by: Lambros Petrou <[email protected]> --------- Co-authored-by: Jun Lee <[email protected]> Co-authored-by: Lambros Petrou <[email protected]>
1 parent 1b64746 commit 7c82b60

File tree

1 file changed

+38
-50
lines changed

1 file changed

+38
-50
lines changed

src/content/docs/durable-objects/best-practices/websockets.mdx

Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,43 @@ This guide covers how to use Durable Objects as WebSocket servers that can conne
1111

1212
There are two sets of WebSockets API:
1313

14-
1. Native Durable Object WebSocket API, which allows your Durable Object to hibernate without disconnecting clients when not actively doing work **(recommended)**.
15-
2. Web Standard WebSocket APIs, using the familiar `addEventListener` event pattern.
14+
1. **Native Durable Object WebSocket API**, which allows your Durable Object to hibernate without disconnecting clients when not actively doing work **(recommended)**.
15+
2. **Web Standard WebSocket APIs**, using the familiar `addEventListener` event pattern.
1616

17-
### What are WebSockets?
17+
## What are WebSockets?
1818

1919
WebSockets are long-lived TCP connections that enable bi-directional, real-time communication between client and server. Both Cloudflare Durable Objects and Workers can act as WebSocket endpoints – either as a client or as a server. Because WebSocket sessions are long-lived, applications commonly use Durable Objects to accept either the client or server connection. While there are other use cases for using Workers exclusively with WebSockets, for example proxying WebSocket messages, WebSockets are most useful when combined with Durable Objects.
2020

2121
Because Durable Objects provide a single-point-of-coordination between [Cloudflare Workers](/workers/), a single Durable Object instance can be used in parallel with WebSockets to coordinate between multiple clients, such as participants in a chat room or a multiplayer game. Refer to [Cloudflare Edge Chat Demo](https://github.com/cloudflare/workers-chat-demo) for an example of using Durable Objects with WebSockets.
2222

2323
Both Cloudflare Durable Objects and Workers can use the [Web Standard WebSocket API](/workers/runtime-apis/websockets/) to build applications, but a major differentiator of Cloudflare Durable Objects relative to other platforms is the ability to Hibernate WebSocket connections to save costs. Clients can remain connected when the Durable Object is idle (and not sending messages or running compute tasks), which allows you to push events to clients and minimize the active duration (GB-seconds) associated with long-running Durable Object processes.
2424

25-
### WebSocket Hibernation API
25+
## Durable Objects' Hibernation WebSocket API
2626

27-
In addition to [Workers WebSocket API](/workers/runtime-apis/websockets/), Cloudflare Durable Objects can use the WebSocket Hibernation API which extends the Web Standard WebSocket API to reduce costs. Specifically, [billable Duration (GB-s) charges](/durable-objects/platform/pricing/) are not incurred during periods of inactivity. Note that other events, for example [alarms](/durable-objects/api/alarms/), can prevent a Durable Object from being inactive and therefore prevent this cost saving.
27+
In addition to [the Web Standard WebSocket API](/workers/runtime-apis/websockets/), Cloudflare Durable Objects can use the WebSocket Hibernation API which extends the Web Standard WebSocket API to reduce costs. Specifically, [billable Duration (GB-s) charges](/durable-objects/platform/pricing/) are not incurred during periods of inactivity.
2828

29-
The WebSocket consists of Cloudflare-specific extensions to the Web Standard WebSocket API. These extensions are either present on the [DurableObjectState](/durable-objects/api/state) interface, or as handler methods on the Durable Object class.
29+
### How does Durable Object Hibernation work with WebSockets?
30+
31+
When a Durable Object receives no events (like alarms) or messages for 10 seconds, the Durable Object is evicted from memory to avoid unnecessary charges. The WebSocket clients remain connected to the Cloudflare network. When your Durable Object receives an event during hibernation, it is re-initialized, its `constructor` function is called, and it can access the WebSocket clients with the `this.ctx.getWebSockets()` function.
32+
33+
When the Durable Object is evicted from memory, its in-memory state is reset. It is common to rely on in-memory state to organize your WebSockets (for example, keeping your WebSockets in rooms with a `Map<WebSocket, Object>` data type). With Hibernation, you must restore the in-memory state of your Durable Object within the `constructor` function.
34+
35+
To do this, you can use the [`serializeAttachment`](#websocketserializeattachment) to persist additional data with the Durable Object WebSocket class, which will persist the data to the Durable Object's storage. Upon re-initialization of the Durable Object, you can access this data with [`deserializeAttachment`](#websocketdeserializeattachment).
36+
37+
The Durable Object WebSocket class consists of Cloudflare-specific extensions to the Web Standard WebSocket API. These extensions are either present on the [DurableObjectState](/durable-objects/api/state) interface, or as handler methods on the Durable Object class.
3038

3139
:::note
3240

3341
Hibernation is only supported when a Durable Object acts as a WebSocket server. Currently, outgoing WebSockets cannot hibernate.
3442

43+
Events, for example [alarms](/durable-objects/api/alarms/), incoming requests, and scheduled callbacks using `setTimeout/setInterval`) can prevent a Durable Object from being inactive and therefore prevent this cost saving.
44+
Read more in the section [When does a Durable Object incur duration charges?](/durable-objects/platform/pricing/#when-does-a-durable-object-incur-duration-charges).
45+
3546
:::
3647

37-
The Worker used in the WebSocket Standard API example does not require any code changes to make use of the WebSocket Hibernation API. The changes to the Durable Object are described in the code sample below. In summary, [`DurableObjectState::acceptWebSocket`](/durable-objects/api/state/#acceptwebsocket) is called to accept the server side of the WebSocket connection, and handler methods are defined on the Durable Object class for relevant event types rather than adding event listeners.
48+
### Example
49+
50+
To use WebSockets with Durable Objects, you first need to proxy the `request` object from the Worker to the Durable Object, as is done in the [WebSocket Standard API example](/durable-objects/examples/websocket-server/). Using the Hibernation WebSockets API in Durable Objects differs slightly from using WebSocket Standard APIs. In summary, [`DurableObjectState::acceptWebSocket`](/durable-objects/api/state/#acceptwebsocket) is called to accept the server side of the WebSocket connection, and handler methods are defined on the Durable Object class for relevant event types rather than adding event listeners.
3851

3952
If an event occurs for a hibernated Durable Object's corresponding handler method, it will return to memory. This will call the Durable Object's constructor, so it is best to minimize work in the constructor when using WebSocket hibernation.
4053

@@ -50,15 +63,9 @@ export class WebSocketHibernationServer extends DurableObject {
5063
const webSocketPair = new WebSocketPair();
5164
const [client, server] = Object.values(webSocketPair);
5265

53-
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
54-
// request within the Durable Object. It has the effect of "accepting" the connection,
55-
// and allowing the WebSocket to send and receive messages.
56-
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
57-
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
58-
// the connection is open. During periods of inactivity, the Durable Object can be evicted
59-
// from memory, but the WebSocket connection will remain open. If at some later point the
60-
// WebSocket receives a message, the runtime will recreate the Durable Object
61-
// (run the `constructor`) and deliver the message to the appropriate handler.
66+
// Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
67+
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
68+
// When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
6269
this.ctx.acceptWebSocket(server);
6370

6471
return new Response(null, {
@@ -69,8 +76,7 @@ export class WebSocketHibernationServer extends DurableObject {
6976

7077
async webSocketMessage(ws, message) {
7178
// Upon receiving a message from the client, reply with the same message,
72-
// but will prefix the message with "[Durable Object]: " and return the
73-
// total number of connections.
79+
// but will prefix the message with "[Durable Object]: " and return the number of connections.
7480
ws.send(
7581
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
7682
);
@@ -99,15 +105,9 @@ export class WebSocketHibernationServer extends DurableObject {
99105
const webSocketPair = new WebSocketPair();
100106
const [client, server] = Object.values(webSocketPair);
101107

102-
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
103-
// request within the Durable Object. It has the effect of "accepting" the connection,
104-
// and allowing the WebSocket to send and receive messages.
105-
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
106-
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
107-
// the connection is open. During periods of inactivity, the Durable Object can be evicted
108-
// from memory, but the WebSocket connection will remain open. If at some later point the
109-
// WebSocket receives a message, the runtime will recreate the Durable Object
110-
// (run the `constructor`) and deliver the message to the appropriate handler.
108+
// Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
109+
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
110+
// When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
111111
this.ctx.acceptWebSocket(server);
112112

113113
return new Response(null, {
@@ -117,8 +117,8 @@ export class WebSocketHibernationServer extends DurableObject {
117117
}
118118

119119
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
120-
// Upon receiving a message from the client, the server replies with the same message,
121-
// and the total number of connections with the "[Durable Object]: " prefix
120+
// Upon receiving a message from the client, reply with the same message,
121+
// but will prefix the message with "[Durable Object]: " and return the number of connections.
122122
ws.send(
123123
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
124124
);
@@ -138,7 +138,7 @@ export class WebSocketHibernationServer extends DurableObject {
138138

139139
</TabItem> </Tabs>
140140

141-
Similar to the WebSocket Standard API example, to execute this code, configure your Wrangler file to include a Durable Object [binding](/durable-objects/get-started/#4-configure-durable-object-bindings) and [migration](/durable-objects/reference/durable-objects-migrations/) based on the <GlossaryTooltip term="namespace">namespace</GlossaryTooltip> and class name chosen previously.
141+
Similar to the [WebSocket Standard API example](/durable-objects/examples/websocket-server/), to execute this code, configure your Wrangler file to include a Durable Object [binding](/durable-objects/get-started/#4-configure-durable-object-bindings) and [migration](/durable-objects/reference/durable-objects-migrations/) based on the <GlossaryTooltip term="namespace">namespace</GlossaryTooltip> and class name chosen previously.
142142

143143
```toml title="wrangler.toml"
144144
name = "websocket-hibernation-server"
@@ -162,24 +162,26 @@ If you are using older versions, note that while hibernatable WebSocket events s
162162

163163
:::
164164

165-
## Extended methods
165+
### Extended methods
166+
167+
The following are methods available on the **Native Durable Object WebSocket API**, the WebSocket class available in Durable Objects. These methods facilitate persisting state to storage to set and restore state before and after a Durable Object's hibernation.
166168

167-
### `serializeAttachment`
169+
#### `WebSocket.serializeAttachment()`
168170

169171
- <code> serializeAttachment(value <Type text="any" />)</code>: <Type text="void" />
170172

171173
- Keeps a copy of `value` associated with the WebSocket to survive hibernation. The value can be any type supported by the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), which is true of most types. If the value needs to be durable please use [Durable Object Storage](/durable-objects/api/storage-api/).
172174

173175
- If you modify `value` after calling this method, those changes will not be retained unless you call this method again. The serialized size of `value` is limited to 2,048 bytes, otherwise this method will throw an error. If you need larger values to survive hibernation, use the [Storage API](/durable-objects/api/storage-api/) and pass the corresponding key to this method so it can be retrieved later.
174176

175-
### `deserializeAttachment`
177+
#### `WebSocket.deserializeAttachment()`
176178

177179
- `deserializeAttachment()`: <Type text='any' />
178180

179181
- Retrieves the most recent value passed to `serializeAttachment()`, or `null` if none exists.
180182

181183

182-
### WebSocket Standard API
184+
## WebSocket Standard API
183185

184186
WebSocket connections are established by making an HTTP GET request with the `Upgrade: websocket` header. A Cloudflare Worker is commonly used to validate the request, proxy the request to the Durable Object to accept the server side connection, and return the client side connection in the response.
185187

@@ -285,11 +287,6 @@ export class WebSocketServer extends DurableObject {
285287
currentlyConnectedWebSockets;
286288

287289
constructor(ctx, env) {
288-
// This is reset whenever the constructor runs because
289-
// regular WebSockets do not survive Durable Object resets.
290-
//
291-
// WebSockets accepted via the Hibernation API can survive
292-
// a certain type of eviction, but we will not cover that here.
293290
super(ctx, env);
294291
this.currentlyConnectedWebSockets = 0;
295292
}
@@ -299,9 +296,7 @@ export class WebSocketServer extends DurableObject {
299296
const webSocketPair = new WebSocketPair();
300297
const [client, server] = Object.values(webSocketPair);
301298

302-
// Calling `accept()` tells the runtime that this WebSocket is to begin terminating
303-
// request within the Durable Object. It has the effect of "accepting" the connection,
304-
// and allowing the WebSocket to send and receive messages.
299+
// Calling `accept()` connects the WebSocket to this Durable Object
305300
server.accept();
306301
this.currentlyConnectedWebSockets += 1;
307302

@@ -335,11 +330,6 @@ export class WebSocketServer extends DurableObject {
335330
currentlyConnectedWebSockets: number;
336331

337332
constructor(ctx: DurableObjectState, env: Env) {
338-
// This is reset whenever the constructor runs because
339-
// regular WebSockets do not survive Durable Object resets.
340-
//
341-
// WebSockets accepted via the Hibernation API can survive
342-
// a certain type of eviction, but we will not cover that here.
343333
super(ctx, env);
344334
this.currentlyConnectedWebSockets = 0;
345335
}
@@ -349,9 +339,7 @@ export class WebSocketServer extends DurableObject {
349339
const webSocketPair = new WebSocketPair();
350340
const [client, server] = Object.values(webSocketPair);
351341

352-
// Calling `accept()` tells the runtime that this WebSocket is to begin terminating
353-
// request within the Durable Object. It has the effect of "accepting" the connection,
354-
// and allowing the WebSocket to send and receive messages.
342+
// Calling `accept()` connects the WebSocket to this Durable Object
355343
server.accept();
356344
this.currentlyConnectedWebSockets += 1;
357345

0 commit comments

Comments
 (0)