Skip to content

0.0.11: the completion budget follows the file, so clangd's ~1 s module-importer answers are not cancelled (UP-25) - #41

Merged
Sunrisepeak merged 3 commits into
mainfrom
completion-budget-1-5s
Oct 4, 2026
Merged

Sunrisepeak merged 3 commits into
mainfrom
completion-budget-1-5s

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

What and why

Typing nlohmann::js in a module-importing file (qt-demo), completion was slow (~1 s) and sometimes did not come up at all. Root cause measured and split in two:

  • clangd (UP-25, issue #24 comment): clangd 23.1 answers every completion on a file that imports modules in 0.95–1.05 s, even when the BMIs are long built and cached — the module context is re-loaded per request (a file without imports in the same project, same CDB and flags: 60 ms; the imports are not in the preamble, so nothing amortizes between requests). This half is upstream.
  • mcppls (this PR): the flat completion budget is 1000 ms, so in that regime every engine answer was cancelled right at the line and the fallback answered instead — and after ., -> or :: the fallback is empty by design, so the popup never came.

The change

The budget now follows the file (EnginePace, src/orchestrator/completion.cppm; wired in workspace.cpp):

  • The workspace records each core-engine completion answer for a file and its latency (including the late answers C-2 already keeps).
  • Two answers in the last ten seconds that all landed past the flat budget extend that file's completion and signature-help budget to their slowest + 500 ms, capped at 2.5 s. qt-demo's importer answers at ~1.05 s: from the third completion on, the engine's real answer lands instead of being cancelled.
  • An engine that answers rarely (U16's module rewritten under autosave, U7a's fan-out save) or quickly (ordinary files, 0.3 s) keeps the flat 1 s budget — the fallback never gets slower for anyone.
  • The flat budget itself is unchanged from 0.0.10 (routing::answer_budget reverted to main); hover and go-to-definition keep theirs.

An earlier commit here raised the flat budget to 1.5 s; CI's UX gate rejected it with data — U16-module-autosave: 93 completions p95 1.50 s (every one riding to the budget, ux-xlings + ux-mcpp failed, run 37188779178) — which is the measured proof that no flat constant can hold both regimes. The adaptive design passes the same scenarios unchanged.

Registered upstream-defect handling as WA-CLANGD-012 (src/engine/clangd/workarounds.cpp): upstream, evidence, removeWhen, premise. Version 0.0.11 (mcpp run -p devtools -- version --set).

Review pass (1f8083b)

The first adaptive cut had two real holes, found in review and fixed:

  • A slow-but-answering engine could push its file's fallback 1.5 s past the flat budget (the regression the gate rejected, narrower): late answers up to 10 s old were all counted, and two 3–8 s answers sent the budget to the 2.5 s cap that those answers would never meet. Answers landing beyond cap − margin (2 s) are not counted — a busy engine is not a slow-and-steady one.
  • A think pause reset the learning: the 10 s window cleared on every pause, so the first two completions after pausing missed again — the very symptom being fixed. The window is now 60 s; a longer pause relearns with the answers C-2 already keeps.

Also from review: latency is measured from the ask (where the budget is counted from, too); signature help records its answers instead of only reading completion's; a closed file's pace is dropped; the ring is walked in place; package-lock.json synced to 0.0.11; zh-CN troubleshooting carries the same note as the English page.

Test

  • Unit: EnginePace policy (flat until two recent past-the-line answers; fast answer among them → flat; stale window → flat; cap) in tests/test_completion.cpp; R-7 assertions unchanged at 1000 ms.
  • Local: mcpp build, mcpp test (all suites, 0 failed), check all, and the completion-keywords conformance fixture against the real payload: 0 failures.
  • CI gates the calibrated UX scenarios (ux-xlings, ux-mcpp, ux-heavy-headers) — the first design failed them; this one keeps their numbers.

…nd module completions (UP-25)

clangd 23.1 costs 0.95-1.05 s per completion on a file that imports modules, even when
the BMIs are long built and cached (issue #24 UP-25: the module context is re-loaded per
request; a file without imports in the same project answers in 60 ms). The completion
budget of 1 s therefore cancelled the engine's answer right at the line, and after `.`,
`->` or `::` the fallback is empty by design -- in the editor this read as completion
being slow and sometimes not coming up at all (qt-demo: typing `nlohmann::js`).

The budget for completion and signature help is now 1.5 s: clangd's real answer arrives
instead of being cancelled at the line, the fallback still bounds the worst case, and
hover and go-to-definition keep their budgets. Version 0.0.11.
… answers per file (UP-25)

A flat 1.5 s budget failed the UX gate: in the regime the scenarios calibrate -- clangd
busy or broken, answering nothing (U16's module rewritten under autosave, U7a's fan-out
save) -- every completion rode to the budget, so the fallback came 500 ms later, and
ux-xlings and ux-mcpp failed (CI run 37188779178). A flat constant cannot hold both
regimes: either the just-past-the-line answers of a module importer are cancelled at
1 s (qt-demo, UP-25), or the fallback of a stuck engine is late everywhere.

The budget now follows the file. The workspace records each core-engine completion
answer and its latency (EnginePace); two answers in the last ten seconds that all
landed past the flat 1 s extend that file's completion and signature-help budget to
their slowest plus 500 ms, capped at 2.5 s. qt-demo's importer answers at about
1.05 s, so from the third completion on its answers land instead of being cancelled;
an engine that answers rarely or quickly keeps the flat budget, and every calibrated
scenario number stays. The flat budget itself is unchanged from 0.0.10. Registered as
WA-CLANGD-012.
@Sunrisepeak Sunrisepeak changed the title 0.0.11: completion budget 1.5 s so clangd's ~1 s module-file completions are not cancelled (UP-25) 0.0.11: the completion budget follows the file, so clangd's ~1 s module-importer answers are not cancelled (UP-25) Oct 4, 2026
…and outlives a think pause (PR #41 review)

The review of the first adaptive cut found two real holes. Late answers up to ten
seconds old were all counted, so a file whose engine answered slowly (a module
rebuilding answers in 3-8 s, not never) could hold its own fallback 1.5 s past the
flat budget -- the regression the UX gate rejected, in narrower form. And the ten
second window cleared on every think pause, so the first two completions after
pausing missed again, which is the symptom this fixes.

Answers landing beyond cap - margin (2 s) are no longer counted: a busy engine is
not a slow-and-steady one, and no budget this file could earn would meet them
anyway. The window is sixty seconds, so a pause inside a minute keeps the file's
pace; a longer one relearns with the answers C-2 already keeps. Latency is measured
from the ask (started), where the budget is counted from, too. Signature help now
also records its answers instead of only reading completion's, a closed file's pace
is dropped, and the pace ring is walked in place (no per-call allocation).

Review-found sync: editors/vscode/package-lock.json to 0.0.11, and the zh-CN
troubleshooting page carries the same budget note as the English one.
@Sunrisepeak
Sunrisepeak merged commit a0cd29b into main Oct 4, 2026
62 checks passed
@Sunrisepeak
Sunrisepeak deleted the completion-budget-1-5s branch October 4, 2026 11:59
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