Skip to content

test(driver-turso): await the "remote was reached" latch instead of sampling a request counter (#17121) - #17282

Merged
os-sam merged 2 commits into
mainfrom
claude/issue-17121-turso-timeout-test-robustness
Sep 10, 2026
Merged

test(driver-turso): await the "remote was reached" latch instead of sampling a request counter (#17121)#17282
os-sam merged 2 commits into
mainfrom
claude/issue-17121-turso-timeout-test-robustness

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #17121

turso-driver-timeout.test.ts ejected PR #17101 from the merge queue on expect(remote.requests()).toBeGreaterThan(0). The card asked for three things in order — reproduce under load, discriminate the two hypotheses mechanically, then fix accordingly. All three are below, with the measurements.

1. The mechanism, measured

The counter increments only when Node dispatches a request handler. The instant it is read is chosen by the very timer under test, and libuv runs the TIMERS phase before the POLL phase: any stall that spans the window delivers the abort — and every assertion that follows it — before the server's already-arrived bytes are parsed into a request event. The counter then reads 0 for a request that is demonstrably on the wire.

Instrumented against the real fixture and the real driver, subscribing to undici's own undici:client:sendHeaders / undici:client:beforeConnect channels (client-side proof that bytes left the process) and sampling the counter both at the assertion instant and after a settle window:

condition envelope counter at assertion sendHeaders fired counter after settle
idle box, 8 runs TIMEOUT / 504 in 8/8 1 in 8/8 8/8 1 in 8/8
6 CPU hogs on 4 cores, 60 runs TIMEOUT / 504 in 60/60 1 in 60/60 60/60 1 in 60/60
loop frozen 300 ms from sendHeaders, 6 runs TIMEOUT / 504 in 6/6 0 in 6/6 6/6 1 in 6/6

Idle, the server dispatches the handler ~2.1 ms after the call — against a 100 ms window. Under six CPU hogs on four cores that stretched to a worst observed 38.3 ms, an 18x stretch that still never reddened the assertion: plain CPU pressure does not reproduce it, a stall that spans the deadline does. In the frozen runs the handler dispatched ~2 ms after the assertion would have run, every time.

2. Which hypothesis — discriminated, not argued

The card's competing hypothesis was a driver defect: TIMEOUT / 504 for a request that never left the process, making the 504 a lie. Under the reproduction that matches the CI signature it is excluded: undici:client:sendHeaders had fired in 6/6, the fixture had accepted the TCP connection in 6/6, and the request handler dispatched in 6/6 — only late. The bytes were on the wire; the observation lagged.

A separate probe froze the loop before the connect instead, and there the request genuinely never leaves — but the process was stopped for 3x the whole budget, so no implementation could have issued it and the deadline is answering correctly. No driver change is required, and none is made — the declared Clause-②: no stands and turso-driver.ts is untouched.

3. The fix

The fixture's counter becomes a latching firstRequest promise the tests AWAIT, at both call sites — :122 and the :138 in the negative control that the duplicate card #17079 correctly pointed out would otherwise be left live.

The positive case goes one step further. A window may legitimately close before its own request is issued, so "this timed request reached the remote" is not a promise the timed operation can be made to keep. "This fixture is a remote the transport reaches" is — off the timed path. The case now proves it with an unbounded driver against the same server before the timed one runs: nothing can preempt that request, so the condition is awaited rather than raced, and a fixture nothing ever reaches still fails, loudly.

Both latch assertions carry a failure message naming the bound they missed. Merge-queue triage classifies a red by asking whether the assertion names a duration; expected 0 to be greater than 0 answers no, which is precisely why this flake was first read as a behaviour regression.

Nothing the test protects is weakened: the envelope (TIMEOUT / 504), the message content, the elapsed bound and all three negative controls are unchanged and still pin that a stalled remote fails within the configured window.

4. Proof under the failure condition

Both legs run the real file through vitest with a setup that freezes the loop 300 ms from undici:client:sendHeaders — the shipped file restored from the merge base for the first leg, byte-verified by git hash-object against origin/main's blob and restored with git checkout HEAD -- afterwards (git diff HEAD empty).

Shipped assertion under that condition — the CI signature, reproduced exactly:

FAIL  src/turso-driver-timeout.test.ts > TursoDriverConfig.timeout — remote mode over HTTP > a stalled remote fails the operation within the configured window, as TIMEOUT / 504
AssertionError: expected 0 to be greater than 0
 ❯ src/turso-driver-timeout.test.ts:122:31
Test Files  1 failed (1)   ·   Tests  1 failed | 4 passed (5)

This branch under the same condition, five consecutive runs plus one at a 1500 ms freeze (15x the window):

rep 1..5   exit=0   Tests  5 passed (5)
freeze 1500 ms   exit=0   Tests  5 passed (5)

Verification

At 246fff55ca, in a dedicated worktree off cf6e0a193b:

pnpm --filter @objectstack/driver-turso typecheck        exit 0
  tsc --listFiles: the edited file IS in the program (1 occurrence)
pnpm --filter @objectstack/driver-turso test             Test Files 50 passed (50) · Tests 1231 passed (1231)
pnpm lint  (eslint . --no-inline-config, the whole repo) exit 0
dispatch-gates: 51 derived families, 51 run, 0 UNRUN
  48 exit 0; 3 exit 3 = PREREQUISITE NOT MET = NOT MEASURED, all three read built
  output of the whole monorepo (check:dual-build-cjs-loads, check:lean-entry-closure,
  check:type-check-debt) and need a full `pnpm build` this round did not spend.
  None can move on a `*.test.ts`: no compiled test reaches any dist, and
  driver-turso carries no type-check DEBT/EXEMPT entry while its typecheck is clean.

Changeset

skip-changeset, measured rather than assumed. @objectstack/driver-turso ships files: ["dist", "README.md", "CHANGELOG.md"]. After a package build, every symbol this diff introduces has zero hits across all three — settlesWithin 0, firstRequest 0, REACH_BOUND_MS 0, stalledHttpServer 0 — while the positive control hits 6 files each (TursoDriver, TursoDriverConfig), and dist/ contains no compiled test. Nothing published moves.

Acceptance notes

  • Both call sites are fixed. The duplicate card [finding] turso-driver-timeout.test.ts races its own 100 ms window against issuing the request — reddened the hourly full run on main, and the same assertion appears twice #17079 noted that a remedy applied only to :122 would leave :138 live; :138 gets the same awaited latch, for the same reason.
  • Noted, not filed: in the extreme regime where a stall spends the whole budget before the transport dispatches, the refusal prose says "the remote did not answer inside the window" for a request that was never issued. It is prose, not contract — the code/status envelope is exactly right and no implementation could have issued that request — so it is an observation, not a defect, and no card is opened for it.
  • The instrument itself (an undici diagnostics-channel probe and a vitest setup that freezes the loop) is deliberately not committed: it is a one-time proof, cited above, not a permanent test.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU


Generated by Claude Code

…ampling a request counter

`turso-driver-timeout.test.ts` asserted "the remote really was reached" by
reading a request COUNTER at the instant the 100 ms window closed, twice. The
sample instant is chosen by the very timer under test, and libuv runs the
TIMERS phase before the POLL phase: any stall that spans the window delivers
the abort -- and every assertion that follows it -- before the server's
already-arrived bytes are parsed into a `request` event. The counter then reads
0 for a request that is demonstrably on the wire, and the file fails as
`expected 0 to be greater than 0` from a merge-queue runner, presenting as a
behaviour regression when it is a load artefact.

Measured on the fixture, freezing the loop 300 ms from the moment undici
publishes `undici:client:sendHeaders`: 6/6 runs read the counter as 0 while
`undici:client:sendHeaders` had fired, the server had accepted the connection,
and the request handler dispatched ~2 ms AFTER the assertion would have run.
The driver answered TIMEOUT / 504 in every one of them, so the competing
hypothesis -- a 504 for a request that never left the process -- is excluded
under this reproduction.

The fixture now exposes a latching `firstRequest` promise the tests AWAIT, and
the positive case proves reachability with an UNBOUNDED driver before the timed
one runs. A window may legitimately close before its own request is issued, so
"this timed request reached the remote" is not a promise the timed operation
can be made to keep; "this fixture is a remote the transport reaches" is, off
the timed path, and that is the anti-vacuity guard the case needs. Every
product assertion is unchanged: the envelope (TIMEOUT / 504), the message, the
elapsed bound, and the three negative controls all still pin what they pinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
…essage

A red from a sampled counter reads `expected 0 to be greater than 0`, which
names no duration -- and merge-queue triage classifies a red by asking exactly
that, so the flake this file produced was read as a behaviour regression. Both
latch assertions now carry a message naming the bound they missed, so the red
says in words that a duration went unmet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 10, 2026
@github-actions github-actions Bot added the tests label Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d64bcb63772b850b3ca8ae39779d492e46d3a0a6packageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants