Heartbeat Mechanism in Computer's Sync Protocol: How Stale Connections Are Detected
The heartbeat mechanism in Cloudflare's Computer sync protocol is an application-level timer that periodically issues a lightweight sync.watermarks() RPC to detect dead peers and prevent middlebox timeouts, automatically triggering reconnection when pings fail.
The sync protocol powering Cloudflare's Computer workspaces relies on a robust heartbeat system to maintain reliable WebSocket connections between the client and container-based backends. This mechanism ensures that silently broken connections are discovered quickly rather than waiting for user-initiated operations to fail.
How the Heartbeat Mechanism Works
The heartbeat serves two critical functions in the sync protocol:
- Dead peer detection — Forces connection teardown when the remote side becomes unresponsive
- Middlebox keepalive — Prevents NATs and edge routers from dropping idle connections after 100–600 seconds
The implementation centers on packages/computer/src/heartbeat.ts, which exports the startHeartbeat function used by container backends to manage connection health.
Core Detection Flow
When a heartbeat is active, the following sequence occurs:
- Timer fires —
setIntervalinvokes the suppliedpingfunction every configured period (default 20 seconds) - Ping succeeds — No action taken; the connection remains healthy
- Ping rejects — The
catchblock executes:- The timer is cleared and a
stoppedflag prevents further pings onFailureis invoked with the error- The container backend closes the WebSocket (
ws.close())
- The timer is cleared and a
- Transport failure propagates — The closed socket triggers
WorkspaceTransportErrorclassification viapackages/computer/src/transport-failure.ts, invalidating the cachedBackendHandle
This design guarantees stale connection detection in O(interval) time rather than potentially minutes of silent failure.
Heartbeat Implementation in Key Files
heartbeat.ts: The Timer and Ping Orchestrator
The core heartbeat logic lives in packages/computer/src/heartbeat.ts. This module implements:
- Periodic
setIntervalscheduling - Promise-based ping execution with rejection handling
- Safe cleanup via returned
stop()function
import { startHeartbeat } from "./heartbeat.js";
const stop = startHeartbeat({
intervalMs: 20_000, // 20 seconds
ping: async () => rpc.sync.watermarks(),
onFailure: (err) => {
console.error("heartbeat failed:", err);
ws.close(); // forces reconnection
},
});
// Cleanup when heartbeat is no longer needed:
stop();
The watermarks RPC is intentionally lightweight, returning only three SQL scalars to minimize overhead while still exercising the full request/response path.
cloudflare-container.ts: Backend Integration
The container backend in packages/computer/src/backends/container/cloudflare-container.ts instantiates and manages the heartbeat lifecycle:
if (this.#options.heartbeatIntervalMs > 0) {
stopHeartbeat = startHeartbeat({
intervalMs: this.#options.heartbeatIntervalMs,
ping: () => (stub as unknown as WorkspaceRPC).sync.watermarks(),
onFailure: () => {
try { ws.close(); } catch { /* already closed */ }
},
});
}
Setting heartbeatIntervalMs to 0 disables the heartbeat entirely, which is useful in testing scenarios or when operating behind connection managers with their own keepalive strategies.
transport-failure.ts: Failure Classification
The packages/computer/src/transport-failure.ts module provides the glue between heartbeat failures and workspace recovery:
import { isWorkspaceTransportFailure } from "./transport-failure.js";
try {
await backend.rpc.someMethod();
} catch (e) {
if (isWorkspaceTransportFailure(e)) {
// Invalidate cached handle, trigger reconnect
workspace.clearBackendHandle();
}
throw e;
}
This classifier distinguishes transport errors (including heartbeat-induced closes) from application-level failures, ensuring appropriate recovery actions.
Stale Connection Detection in Practice
When investigating sync protocol connectivity issues, understanding the heartbeat's role helps diagnose whether failures are:
- Heartbeat-reported — Immediate detection via failed
watermarkscalls - User-operation-reported — Delayed detection when the next real RPC executes
The heartbeat's proactive approach eliminates the ambiguity of "silent" WebSocket deaths, where the TCP connection remains open but no data flows. This commonly occurs with:
- Laptop sleep/wake cycles
- Network interface changes
- Container migrations
- Cloudflare edge rerouting events
Configuring Heartbeat Behavior
The heartbeat interval is configurable per-workspace through backend options. The default 20-second balance provides:
| Interval | Detection Latency | Middlebox Compatibility | Overhead |
|---|---|---|---|
| 5 s | Fast | Excellent | Higher RPC load |
| 20 s (default) | Moderate | Good | Minimal |
| 60 s | Slow | Marginal | Very low |
| 0 (disabled) | None | Requires external keepalive | None |
Production deployments typically use the default or slight variations based on observed middlebox behavior in their specific network topology.
Summary
- The heartbeat mechanism in Computer's sync protocol is implemented in
packages/computer/src/heartbeat.tsas a configurable periodic timer - Stale connection detection works by issuing lightweight
sync.watermarks()RPCs and closing the WebSocket when they fail - Automatic recovery occurs through
transport-failure.tsclassification andBackendHandleinvalidation - The default 20-second interval balances quick detection against minimal overhead
- Container backends in
cloudflare-container.tsintegrate heartbeat lifecycle management with WebSocket control
Frequently Asked Questions
What RPC does the heartbeat use for health checks?
The heartbeat calls sync.watermarks(), which returns three SQL scalar values. This RPC is chosen specifically because it exercises the full sync protocol stack without triggering side effects or heavy computation, making it ideal for frequent health probes.
How does the heartbeat handle already-closed WebSockets?
The onFailure callback in cloudflare-container.ts wraps ws.close() in a try/catch block to gracefully handle cases where the socket was already closed by another path. This prevents exception propagation from masking the original failure reason.
Can the heartbeat interval be disabled or changed at runtime?
The interval is set during backend initialization via heartbeatIntervalMs in the options object. It cannot be changed dynamically for an active connection, but different workspace instances can use different values. Setting the interval to 0 completely disables heartbeat functionality.
What's the difference between the sync protocol heartbeat and the exec-side heartbeat in runner.ts?
The sync protocol heartbeat discussed here maintains the WebSocket between client and workspace container. A separate heartbeat mechanism in packages/computerd/src/exec/runner.ts monitors child processes spawned for code execution within the workspace. These serve different purposes and operate at different protocol layers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →