Skip to content

test(gate): measure admission under Worker suite contention - #177

Merged
kixelated merged 3 commits into
mainfrom
quest/m0/local-gate
Oct 21, 2026
Merged

kixelated merged 3 commits into
mainfrom
quest/m0/local-gate

Conversation

@kixelated

@kixelated kixelated commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m0/local-gate.md: measures quest gate under representative build/test contention, now that load and memory admission landed in kixelated/quest#128.

Changes

  • scripts/gate-bench.sh makes the measurement repeatable (run inside nix develop):
    • gated|ungated <suites> [cpus] starts N Worker suites (bun run --cwd cloudflare test, the suite with 5-second test timeouts) at once, each in its own detached worktree, through quest gate or bypassing it with QUEST_GATE_ADMITTED=1. An optional taskset CPU list oversubscribes a few cores without saturating a shared host. It reports failed suites, timeouts, admission waits, run times, makespan, peak load and CPU pressure, least available memory, and the most frequent failing tests. Worktrees are removed on exit.
    • overhead [runs] times quest gate true with a fresh claim directory against the same call with the gate bypassed.
  • Deletes the quest and its links (quest rm).
  • README: a burst of waiters is admitted about 10 seconds apart, not every 5 seconds, because a claim is refused while the previous window's claim exists.

Measurements

Host: 24 CPUs, 61 GiB RAM, shared with other agent sessions. Background 1-minute load was 2 to 20 during these runs, shown as "load before". One Worker suite alone uses about 1 core for 17 to 20 s and peaks at about 650 MB RSS. One just test uses about 1.2 cores for 30 s. Pinned runs confine N single-core suites to 4 or 2 CPUs, so "8×" means 8 suites per core.

Scenario Mode Failed suites 5 s timeouts Wait median / max Run median Makespan Peak load1 Peak CPU PSI avg10 Load before
8 suites, all CPUs ungated 0/8 0 0.1 / 0.1 s 19.0 s 19.4 s 5.7 1.5 3.6
8 suites, all CPUs gated 0/8 0 29 / 89 s 14.8 s 103.6 s 5.2 0.4 5.2
8 on CPUs 0-3 (2×) ungated 0/8 0 0.2 / 0.3 s 32.5 s 33.6 s 11.3 49.1 8.9
16 on CPUs 0-3 (4×) ungated 0/16 0 0.4 / 0.6 s 78.1 s 79.0 s 22.1 54.6 8.5
16 on CPUs 0-3 (4×) gated 0/16 0 68 / 158 s 15.2 s 175.9 s 15.0 11.1 15.0
16 on CPUs 0-1 (8×) ungated 10/16 10 0.9 / 1.2 s 148.0 s 151.1 s 28.2 52.2 8.6
16 on CPUs 0-1 (8×), repeat ungated 10/16 10 0.8 / 1.0 s 137.9 s 140.4 s 20.5 73.9 4.5
16 on CPUs 0-1 (8×) gated 0/16 0 68 / 178 s 14.3 s 191.5 s 17.5 16.9 17.5

Every timeout in both 8× runs is the same test: test/seed.test.ts > seeds a representative inbox through the Worker's own routes, which takes 514 ms alone.

Admission overhead: quest gate true took 33 ms against 32 ms bypassed, and in a second run 43 ms against 40 ms, as medians of 20 runs each. Admission costs about 1 to 3 ms on top of CLI startup. The earlier 37 ms figure included the removed systemd probe.

What it shows

  • The Worker suite no longer times out under realistic contention on this host. Even 4× oversubscription per core passes. The failures start near 8×, which on 24 CPUs would mean about 190 concurrent suites, far more than concurrent sessions produce. The /dev/shm and shared-runtime changes in cloudflare/vitest.config.ts likely removed the earlier failures.
  • The gate does eliminate timeouts once cores are badly oversubscribed: at 8×, 10 of 16 suites failed without it and none with it, at 1.27× to 1.36× the makespan. Without the gate the failed suites still need reruns.
  • On a host with room, the gate is pure latency. A burst of 8 gated suites took 104 s instead of 19 s, with the last waiting 89 s, and the host never came close to its load limit (peak load 5.7 against a limit of 24).
  • Effective spacing is about 10 s per admission, not 5 s. Waiters wake together at window boundaries, and a claim is refused while the previous window's claim exists, so admissions skip every other window. Other sessions' gated commands share those windows, which explains the observed 11 to 12 s.
  • The pinned runs exercise only the spacing. The load limit is host-wide (24), and the host never exceeded it, so the load and memory checks never held a command. Their benefit when foreign work saturates every core follows from the design but was not measured: that would mean saturating a shared host.

So these runs do not support a responsiveness improvement claim on this 24-core host. The gate is cheap insurance for small hosts or saturated cores, and it costs about 10 s per queued command when a burst arrives.

Decisions

  • ✅ Keep scripts/gate-bench.sh in the repository, not wired into CI: the quest asked for a repeatable measurement, and the script is a load generator.
  • ✅ Delete the quest in this PR: the measurement is done; spacing work would be new quests rather than leftover scope.
  • Burst latency on hosts with room: moved to a /quest-plan follow-up; this PR changes no admission behavior. Admitting immediately while the host is far below its limit would reverse the 2026-10-15 "no adaptive scheduler" decision; the narrower alternative is one admission per 5 s instead of about 10 s.

Follow-ups

  • /quest-plan the burst-latency follow-up described under Decisions (adaptive admission, or fixing the spacing to 5 s).
  • test/seed.test.ts "seeds a representative inbox" is the only timing-sensitive Worker test (514 ms alone). It is low priority because it needs about 8× oversubscription to fail; slim it rather than raising its timeout.
  • Follow-ups from kixelated/quest#128, not folded in because the measurements do not touch them and none is a one-line change:
    • A stable claim directory: callers with and without XDG_RUNTIME_DIR use different directories and do not see each other's claims. Derive it from the uid, for example /run/user/<uid> when it exists. gate-bench.sh overhead uses XDG_RUNTIME_DIR for a fresh claim directory and would need another way.
    • Ctrl-C reaches the child twice: once from the terminal's process group and once forwarded by the gate.
    • A backward wall-clock step across a window boundary.
  • quest/m0/worktree-agents.md listed this quest as "resource control before removing agent-count throttles". The data suggests agent count is not what starved Worker tests on this host.

Validation

just check and just test pass in the Nix shell. shellcheck and shfmt cover the new script through just check.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 21, 2026 14:06
Quest-Claim: 470bfc40d20c751d
Add scripts/gate-bench.sh, which starts N Worker suites at once in detached
worktrees, gated or bypassing the gate, optionally pinned to a few CPUs, and
reports failed suites, 5-second timeouts, waits, run times, makespan, and peak
load, CPU pressure, and least available memory. It also measures admission
overhead against a bypassed call.

The measurements finish the gate quest, so delete it and its links. Correct
the README: waiters are admitted about 10 seconds apart, since a claim is
refused while the previous window's claim exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Measure quest gate under representative contention test(gate): measure admission under Worker suite contention Oct 21, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome: I measured the gate with scripts/gate-bench.sh (results in the description). Worker suites only time out at about 8× per-core oversubscription. There the gate cut failures from 10 of 16 suites to 0 of 16, at about 1.3× the makespan. On this 24-core host under realistic load, nothing timed out with or without the gate, and the gate added up to 89 s of wait to a burst of 8. Admission costs about 1 to 3 ms. I am keeping this a draft until the decisions in the description are settled.

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review October 21, 2026 21:34
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T21:37:37.356011Z c054f67 Draft marked ready
🔒 Security Review ✅ Completed 2026-10-21T21:36:40.810278Z c054f67 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary:

  • Merged main in (no conflicts; no links to the deleted quest remain). just check and just test pass in the Nix shell; all 17 CI checks pass.
  • Codex completed code and security review on c054f67 with no findings.
  • quest check --base origin/main --branch quest/m0/local-gate, run with a quest built from main, passes.
  • Decisions: keep scripts/gate-bench.sh (not in CI) ✅; delete the quest in this PR ✅. Burst latency / adaptive admission moved to a /quest-plan follow-up; this PR changes no admission behavior.

Enabling auto-merge on c054f6701294698c77c4800562cbfd95a1b6f5e0.

(Written by Claude Opus 5.5)

@kixelated
kixelated merged commit d3b6e6c into main Oct 21, 2026
17 checks passed
@kixelated
kixelated deleted the quest/m0/local-gate branch October 21, 2026 21:40
kixelated added a commit that referenced this pull request Oct 21, 2026
* docs(quest): plan gate admission with headroom

Follow-up of the contention measurements in #177: admit at once while the
host has room, fix the two-window spacing, a stable claim directory,
backward clock steps, and forwarding Ctrl-C once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(quest): address review of the gate headroom plan

Count recent admissions against free memory as well as load, define the
spacing band, require atomic minute-long admission records, and pin down
the claim directory, clock-step, and Ctrl-C rules. The Ctrl-C fix covers
`quest worktree run` only if #178 has landed. The quest is
now [M]. Also fix the seed test path in the inbox-accuracy quest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(quest): count unexpired gate records and survive large clock steps

Derive the recent-admission count from unexpired records with a
record-then-count step instead of slot numbers, which expiry leaves with
holes. Rewrite a future-dated record's time to now rather than discarding
it, so a large backward clock step still spaces admissions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(quest): elect one gate admission at a time under a lock

Record-then-count prevents over-admission but can livelock when a whole
burst withdraws together. Suggest serializing prune, count, and record
under a short host-wide lock reclaimed from exited owners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(quest): share the Ctrl-C fix with quest worktree run

#178 landed, so the gate headroom quest fixes the double
Ctrl-C in both commands unconditionally, and the worktree quest links to
it instead of the planning PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(quest): state gate admission as requirements and hazards

Four review rounds kept finding edge cases in the prescribed records,
lock, and clock-step mechanisms. Keep the decisions and the three-band
rule, state what the admission mechanism must guarantee, and list every
review finding as a hazard the implementation must test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(quest): require a claim location stable across login sessions

/run/user/<uid> comes and goes with logind sessions, so choosing by its
existence can split callers again. State the requirement and the
hazards instead of a directory rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(quest): keep forwarding SIGHUP from the gate

A SIGHUP sent with kill to Quest alone cannot be told apart from a
terminal hangup, so skip forwarding only SIGINT in the foreground group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant