Repository navigation
perf(index): run supervised index workers at below-normal CPU priority - #2838
Conversation
A full index saturates every core in the parallel extraction pass, and interactive CLI/MCP queries (CPU-bound at start-up) were measured taking twice their wall time behind it with flat CPU time: pure run-queue contention. The index-worker role now lowers its own priority once, before any indexing thread is created: nice 10 on POSIX, BELOW_NORMAL_PRIORITY_CLASS on Windows. It never raises priority: a process already nicer than 10 (or in the IDLE / BELOW_NORMAL class) is left alone. Plain nice only, not macOS background QoS, which would pin indexing to efficiency cores and throttle its I/O. Failure is logged and ignored. The test runs the helper in a forked child and checks both legs: a normal-priority child ends at nice >= 10, and a child started nicer than that is left where it was. Refs DeusData#2834 Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
QA Round 1Contract: #2834 + PR acceptance criteria (CPU-priority half only; the per-call executable hash is out of scope) Contract Verification
Finding 1 — Windows path can raise an IDLE-class worker to BELOW_NORMAL
The POSIX branch checks the current nice value before changing it. The Windows branch calls Expected: return early when the current class is already Finding 2 — No test covers the worker-role wiring
The new test exercises the helper on its own. Removing the 6-line block in Expected: some test fails if the index-worker role stops lowering its priority. Finding 3 — New test passes vacuously under an already-niced runner; the "never raise" branch is never exercised
The test asserts only Expected: record the starting nice and assert a strict change when it starts below 10. Add a leg that starts above 10 and asserts the value is unchanged. Summary
Panel: contract-security, regression-edges, test-quality. All 3 lenses landed on ObservationsNone. SAST review not applicableTarget QA performed by Claude Code (claude-opus-5-5), manager + 3-lens panel Dispositions (orchestrator, Step 3B)
Verification
QA-Fix-Commit: 704a208 Posted by the developer account (QA-agent token unavailable). Review by Claude Opus 5.5 (Anthropic) in Claude Code via qa-panel. |
QA Round 2Scope: PR #4 ( Contract VerificationContract: upstream issue #2834 + PR acceptance criteria.
Finding 1 — [claude] No test pins the index-worker wiring: removing or moving the main.c call fails nothing
Finding 2 — [claude] Helper test passes vacuously when the test runner is already at nice >= 10
Summary
No blocking findings. Both minors are test-coverage gaps. Neither is on code that round 1 introduced. ObservationsNone. Fix review: <filled in by main after Step 3B> SAST review not applicableTarget QA performed by Claude Code (claude-opus-5-5), manager + 3-lens panel Dispositions (orchestrator, Step 3B)No code changed this round. Both minors were declined as known test limits:
Cycle outcome: clean of critical and major findings after round 2, and every contract criterion passes. Not approved: no QA-agent token is available, and approval is the human reviewer's call. Posted by the developer account (QA-agent token unavailable). Review by Claude Opus 5.5 (Anthropic) in Claude Code via qa-panel. |
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you, @halindrome -- this is a clean, well-scoped change. Lowering the priority once, inside the index-worker role, covers every path that spawns a worker (index_repository from MCP and the CLI, the crash-retry and single-thread fallback rounds, and the daemon's background re-index), and the never-raise rule plus the forked-child test make the contract easy to trust. We also re-checked the graph side: indexing the same corpus (redis) with and without the change gives identical nodes and edges, and with the change the worker really runs at nice 10 while the CLI and daemon keep their priority. Two small things we did on our side so it can land:
If you push again, please pull first so the merge commit is not lost. We will merge as soon as the required checks are green. Thanks again! |
|
Merged in 7a9b1cb. Thank you very much, @halindrome! One nice-10 call in the worker role covers every place we spawn an index worker, so supervised indexing now stays out of the way of the editor and the agent without any change to what gets indexed - the graph bench on redis came out identical (same nodes, edges and set hashes). That is exactly the kind of low-risk, measurable improvement we like to land. As noted on the PR, #2834 stays open for the per-call executable-hash cost, which is its own piece of work. Thanks again for the careful, well-scoped change. |
Brings the branch up to main 9c19b5d. Two same-spot insertion conflicts, each resolved as the main version of the file plus the own diff of this branch: - src/daemon/application.c: the job struct gained watch_owners on main and request_started_ms plus the request facts here; both kept. - tests/test_daemon_application.c: main added the DeusData#1818 autoindex-skipped helpers where this branch added the worker resource failure test; both kept, the test placed after the doc_candidates test. Everything else merged cleanly. src/main.c changed on both sides: main (DeusData#2838) lowers the worker CPU priority inside the worker role block, and this branch strips the publication fence flag before that block and installs the fence after the worker role options; the hunks are disjoint and both are kept. tests/test_pipeline.c gained the Next.js route tests (DeusData#2747) on main and the publication gate test here, also disjoint. No call site needed adapting: no new main code calls a function this branch re-signed, and main re-signed none that this branch calls. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Summary
Run supervised index workers (
cli --index-worker) at below-normal CPU priority so a long full index can't starve interactive CLI/MCP queries.Refs #2834
During a full index the parallel extraction pass saturates every core. One-shot
codebase-memory-mcp cli <tool>queries are CPU-bound at start-up, and measurement showed their wall time doubling during an index while their CPU time stayed flat — run-queue contention, not a lock (same-project and other-project queries behaved identically). Nothing insrc/lowered worker priority before this change.Change
cbm_lower_cpu_priority()(src/foundation/platform.c/.h): POSIXsetpriority(PRIO_PROCESS, 0, 10); WindowsBELOW_NORMAL_PRIORITY_CLASS. It never raises priority: an already-nicer process, or one in the IDLE / BELOW_NORMAL class, is left alone. Plain nice only — not macOS background QoS, which would pin indexing to efficiency cores and throttle its I/O.run_cli(src/main.c) calls it once in the index-worker role, before any indexing thread exists, so every pipeline thread inherits it. Failure is logged at info and ignored.Acceptance criteria
Testing
platform_lower_cpu_priority_nices_the_process(tests/test_platform.c): forks a child, calls the helper, and reportsgetprioritythrough a pipe. It checks two things:scripts/test.sh --suites platform,index_supervisor,index_resilience,worker_pool,cli: 442 passed, 1 pre-existing skip, 0 failed.🤖 Generated with Claude Code
https://claude.ai/code/session_01CDDUoWEZMNZ9fLvzp2pPGT