cloudflare / cloudflare/agents

No supported way to assert a Durable Object's hibernation state / eligibility in tests

Open
#1,956 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
TypeScript
Stars
5.6k
Forks
711
Avg merge
1d 20h
Merged PRs (30d)
53

Description

## Summary

Durable Objects bill for **wall-clock residency**. WebSocket Hibernation avoids that for idle, connected DOs — but hibernation is silently disqualified while the isolate has **any pending `setTimeout` / `setInterval`**. A single keepalive timer (easy to pull in via a dependency) therefore pins a DO resident and bills full residency for every live connection, with **no error and nothing observable in the connection's behavior**.

I wanted a CI regression test for "this DO is still hibernation-eligible." It turns out you *can* write one — but only by instrumenting the DO itself with a durable reconstruction counter and idling past workerd's ~10s inactivity timer in real time. There is no supported way to simply *ask* whether a DO hibernated or is still eligible.

_Provenance: originally hit in a real sync Durable Object where a dependency's `setInterval` keepalive silently blocked hibernation — livestorejs/livestore#1328, whose thread independently asks for the same thing: "an API of any sort to check DO state (hibernated or not) as replacement for [the] current probe."_

## Reproduction

Minimal repo: **https://github.com/bohdanbirdie/cf-do-hibernation-repro** — a plain DO (`PlainRoom`) and the same DO holding one `setInterval(() => {}, 2**31-1)` (`TimerRoom`). The DO is instrumented with a durable `boots` counter (incremented in the constructor, read over a normal request), and driven via `SELF` under `@cloudflare/vitest-pool-workers`. Across a 20s idle window:

```
plain DO: boots 1 -> 2 # reconstructed after idle => hibernated
timer DO: boots 1 -> 1 # never reconstructed => pinned by one setInterval
```

`npm test` — control + plain-hibernates + timer-pinned, **3/3 passing, reproducible across runs**.

Versions: `@cloudflare/vitest-pool-workers@0.18.5`, `vitest@4.1.10`, `miniflare@4.20260710.0`, `workerd@1.20260710.1`.

So the platform models timer-pinning correctly and the regression **is** catchable — the problem is what it costs to catch it.

## Why it's awkward

1. **You must instrument production code to observe hibernation at all.** There's no supported "did it hibernate / is it eligible" signal, so the DO has to carry a durable reconstruction counter purely for the test. Cloudflare's own `y-partyserver` does exactly this — [`YHibernateTracker`](https://github.com/cloudflare/partykit/blob/main/packages/y-partyserver/src/tests/worker.ts) counts `onStart` calls in `ctx.storage`, and its `hibernation.test.ts` drives a real `wrangler dev` subprocess it kills/restarts rather than asserting hibernation in-process.

2. **A plain probe can't tell you either — reading a DO wakes it.** The counter above only works because it persists the reconstruction count to storage; a direct check would transition the DO back to active and report the wrong state. This is the observer effect @MattieTK describes in [workerd#5835](https://github.com/cloudflare/workerd/issues/5835) ("Durable Object Lifecycle Event Emission for Local Dev Tooling") — the same need, from the runtime/tooling side.

3. **`evictDurableObject` doesn't cover it.** It *forces* hibernation, bypassing the eligibility gate — so it structurally can't detect a pending-timer regression, which is the exact failure that matters.

(There's also a wall-clock cost — every hibernation test idles ~10s of real time because the inactivity timer can't be advanced — but that's secondary.)

## What I'm asking for

Some supported way to observe or assert a DO's hibernation state / eligibility from a test — without having to instrument the DO, and without the probe itself waking it. I'm not proposing a specific API shape; workerd#5835 already lays out this need on the runtime side, and this is its test-time counterpart. Raising it here since the Agents SDK depends on hibernatable DOs and hits hibernation issues directly (e.g. agents#996).

cc @threepointone — filing here as we discussed in person. Thanks!

Contributor guide

Open the contributing guide

Research direction

Start with the linked reproduction and its three Vitest cases, then read workerd#5835 and y-partyserver's packages/y-partyserver/src/tests/hibernation.test.ts and worker.ts. Define a supported test-time observation or assertion that does not instrument the Durable Object or wake it; done should cover eligibility and hibernation without relying on a real-time reconstruction counter.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, cloud, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.