Summary
Add hibernation support for outgoing WebSocket connections created by Durable Objects using new WebSocket(). Currently, hibernation only works for incoming WebSocket connections accepted via ctx.acceptWebSocket(), but not for outgoing connections that a Durable Object initiates to external services.
Current State
- ✅ Durable Objects support hibernation for incoming WebSocket connections via
ctx.acceptWebSocket(socket)
- ✅ This works when a DO accepts a WebSocket connection from an HTTP upgrade request
- ❌ No hibernation support for outgoing WebSocket connections created via
new WebSocket('some-url')
Problem
Durable Objects that maintain persistent connections to external WebSocket services cannot hibernate and will remain pinned in memory indefinitely. This prevents cost optimization during idle periods and limits the scalability benefits of hibernation.
Use Cases
Many real-world applications need Durable Objects to maintain persistent connections to external services while benefiting from hibernation:
- Database connections: Real-time database subscriptions (Supabase, Firebase, etc.)
- Message brokers: Persistent connections to Redis Streams, Apache Kafka, etc.
- Real-time APIs: WebSocket connections to external real-time services
- Inter-service communication: WebSocket connections between different parts of a distributed system
Proposed Solution
Extend the existing ctx.acceptWebSocket() method to work with outgoing WebSocket instances, maintaining API consistency with the current hibernation infrastructure:
const ws = new WebSocket('wss://external-service.com/ws');
ctx.acceptWebSocket(ws); // Should work for outgoing connections too
This approach leverages the existing hibernation handler methods (webSocketMessage(), webSocketClose(), webSocketError()) for both incoming and outgoing connections.
Expected Behavior
When a Durable Object hibernates with outgoing WebSocket connections:
- Connection persistence: WebSocket connections remain alive during hibernation
- Wake on message: When messages arrive on these connections, the DO wakes up automatically
- Handler invocation: The appropriate
webSocketMessage(), webSocketClose(), or webSocketError() handlers are called after wake-up
- State restoration: The DO can access and interact with WebSocket instances after waking up
- Transparent operation: From the application's perspective, hibernation should be transparent
Implementation Example
export default {
async fetch(request, env, ctx) {
const id = env.MY_DURABLE_OBJECT.idFromName('example');
const obj = env.MY_DURABLE_OBJECT.get(id);
return await obj.fetch(request);
}
}
export class MyDurableObject {
constructor(ctx, env) {
this.ctx = ctx;
this.env = env;
}
async fetch(request) {
// Create outgoing WebSocket connection
if (\!this.externalWs) {
this.externalWs = new WebSocket('wss://external-api.com/stream');
// Enable hibernation for outgoing WebSocket (proposed feature)
this.ctx.acceptWebSocket(this.externalWs);
// Tag the WebSocket to distinguish from incoming connections
this.externalWs.serializeAttachment({
type: 'outgoing',
source: 'external-api'
});
}
return new Response('Outgoing WebSocket connection established');
}
// Same handler methods used for BOTH incoming AND outgoing WebSockets
async webSocketMessage(ws, message) {
// Handler distinguishes between different WebSocket sources
const attachment = ws.deserializeAttachment();
if (attachment?.type === 'outgoing') {
console.log('Received from external service:', message);
// Process external data, potentially send to connected clients
this.broadcastToClients(message);
} else {
console.log('Received from client:', message);
// Process client data, potentially forward to external service
if (this.externalWs?.readyState === WebSocket.OPEN) {
this.externalWs.send(message);
}
}
}
async webSocketClose(ws, code, reason, wasClean) {
const attachment = ws.deserializeAttachment();
if (attachment?.type === 'outgoing') {
console.log('External WebSocket closed:', code, reason);
// Handle external connection loss, maybe attempt reconnection
this.handleExternalDisconnection();
} else {
console.log('Client WebSocket closed:', code, reason);
// Handle client disconnection
}
}
async webSocketError(ws, error) {
const attachment = ws.deserializeAttachment();
console.log('WebSocket error:', attachment?.type || 'unknown', error);
}
broadcastToClients(message) {
// Send data from external service to all connected clients
this.ctx.getWebSockets().forEach(ws => {
const attachment = ws.deserializeAttachment();
if (attachment?.type \!== 'outgoing') {
ws.send(message);
}
});
}
handleExternalDisconnection() {
// Implement reconnection logic or cleanup
this.externalWs = null;
}
}
Key Benefits of This Approach
- API Consistency: Uses the same hibernation infrastructure and handler methods for both incoming and outgoing WebSockets
- Hibernation Support: When the DO hibernates and wakes up on messages from outgoing WebSockets, it calls the familiar
webSocketMessage() handler
- Connection Management:
ctx.getWebSockets() would return both incoming and outgoing hibernating WebSockets
- State Preservation:
serializeAttachment()/deserializeAttachment() allows distinguishing between connection types
- Cost Optimization: DOs can hibernate during idle periods even with persistent external connections
- Better Resource Utilization: Reduced memory usage across the Cloudflare network
- Improved Scalability: Enable more complex real-time applications that need external integrations
Technical Considerations
- Connection Identification: WebSocket instances need to be tagged (via
serializeAttachment()) so handlers can distinguish between incoming vs outgoing connections
- Handler Unification: This removes the need for standard WebSocket event callbacks (
onmessage, onclose, etc.) which don't work with hibernation
- Backward Compatibility: The existing
ctx.acceptWebSocket() method would be extended to work with outgoing WebSocket instances without breaking changes
- Memory Management: All hibernation benefits (memory efficiency, cost optimization) apply to outgoing connections
Alternative Workarounds
Currently, developers must choose between suboptimal solutions:
- No hibernation: Keep DOs active permanently (expensive, poor resource utilization)
- Connection recreation: Constantly reconnect to external services (unreliable, connection overhead)
- Hybrid architecture: Use separate services for external connections (increased complexity)
None of these workarounds provide the optimal developer experience or cost efficiency that hibernation with outgoing WebSocket support would enable.
Related Documentation
Summary
Add hibernation support for outgoing WebSocket connections created by Durable Objects using
new WebSocket(). Currently, hibernation only works for incoming WebSocket connections accepted viactx.acceptWebSocket(), but not for outgoing connections that a Durable Object initiates to external services.Current State
ctx.acceptWebSocket(socket)new WebSocket('some-url')Problem
Durable Objects that maintain persistent connections to external WebSocket services cannot hibernate and will remain pinned in memory indefinitely. This prevents cost optimization during idle periods and limits the scalability benefits of hibernation.
Use Cases
Many real-world applications need Durable Objects to maintain persistent connections to external services while benefiting from hibernation:
Proposed Solution
Extend the existing
ctx.acceptWebSocket()method to work with outgoing WebSocket instances, maintaining API consistency with the current hibernation infrastructure:This approach leverages the existing hibernation handler methods (
webSocketMessage(),webSocketClose(),webSocketError()) for both incoming and outgoing connections.Expected Behavior
When a Durable Object hibernates with outgoing WebSocket connections:
webSocketMessage(),webSocketClose(), orwebSocketError()handlers are called after wake-upImplementation Example
Key Benefits of This Approach
webSocketMessage()handlerctx.getWebSockets()would return both incoming and outgoing hibernating WebSocketsserializeAttachment()/deserializeAttachment()allows distinguishing between connection typesTechnical Considerations
serializeAttachment()) so handlers can distinguish between incoming vs outgoing connectionsonmessage,onclose, etc.) which don't work with hibernationctx.acceptWebSocket()method would be extended to work with outgoing WebSocket instances without breaking changesAlternative Workarounds
Currently, developers must choose between suboptimal solutions:
None of these workarounds provide the optimal developer experience or cost efficiency that hibernation with outgoing WebSocket support would enable.
Related Documentation