test(lsp): end every check of a test server in its batch, not after 40 ms #269

Merged
argus merged 1 commit from loop/268-hover-test-main into main 2026-10-09 12:55:11 +00:00
Collaborator

Closes #268

Cause

A wall-clock flake, not an interaction of the landed commits. LanguageServer.receive takes the check of the documents a batch opened or changed for at most checkBudget (stepBudget, 40 ms of wall clock, continueChecks); a request that waits for that check (checkedRequests: hover, completion, definition, ...) is answered in the batch only when the check ends within that budget, otherwise by a later step(). The test sent didOpen and the hover in one batch and read only that batch's answers, so when tier A's four lanes loaded the runner and the check took longer than 40 ms, the hover had no answer yet: answerOf gave null and the markdown was "".

Evidence:

  • None of 0a1a03f0, 596b50a6 or 90e233aa touches compiler/src/language-server/ or this test.
  • The same family has failed before in tier A, each time a different test of this file that reads the answers of one batch, and each time green on the next run: run 547 on main 13d7e718 (a completion item of a deprecated declaration carries the tag ..., the completion answer missing: "null"), runs 534 and 609 (the first keystroke builds them for every module ..., runs.length() == 2), run 468 (a body whose inferred result changes checks the bodies that call it, the diagnostics not published with the batch).

Fix

started() (and through it checkedWith) sets checkBudget to Int.maximum: every check a batch begins ends in that batch, whatever the speed of the machine. The tests of the slicing already set it to 0 and so take one statement per slice, which does not depend on the clock either. A test server now uses one of these two budgets, never the 40 ms of the real transport. Only the test file changes; the server and its default stay as they are.

Closes #268 ## Cause A wall-clock flake, not an interaction of the landed commits. `LanguageServer.receive` takes the check of the documents a batch opened or changed for at most `checkBudget` (`stepBudget`, 40 ms of wall clock, `continueChecks`); a request that waits for that check (`checkedRequests`: hover, completion, definition, ...) is answered in the batch only when the check ends within that budget, otherwise by a later `step()`. The test sent `didOpen` and the hover in one batch and read only that batch's answers, so when tier A's four lanes loaded the runner and the check took longer than 40 ms, the hover had no answer yet: `answerOf` gave `null` and the markdown was `""`. Evidence: - None of 0a1a03f0, 596b50a6 or 90e233aa touches `compiler/src/language-server/` or this test. - The same family has failed before in tier A, each time a different test of this file that reads the answers of one batch, and each time green on the next run: run 547 on `main` 13d7e718 (`a completion item of a deprecated declaration carries the tag ...`, the completion answer missing: `"null"`), runs 534 and 609 (`the first keystroke builds them for every module ...`, `runs.length() == 2`), run 468 (`a body whose inferred result changes checks the bodies that call it`, the diagnostics not published with the batch). ## Fix `started()` (and through it `checkedWith`) sets `checkBudget` to `Int.maximum`: every check a batch begins ends in that batch, whatever the speed of the machine. The tests of the slicing already set it to `0` and so take one statement per slice, which does not depend on the clock either. A test server now uses one of these two budgets, never the 40 ms of the real transport. Only the test file changes; the server and its default stay as they are.
test(lsp): end every check of a test server in its batch, not after 40 ms
All checks were successful
ci / commit messages (push) Successful in 10s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 17s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m12s
ci / tier A (linux-x64) (pull_request) Successful in 4m18s
ci / every host emits the same C (pull_request) Successful in 4m23s
ci / the playground (torb as WebAssembly) (pull_request) Successful in 2m5s
ci / pack the seed (pull_request) Successful in 11s
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
ci / what the change touches (push) Successful in 9s
ci / which runners exist (push) Successful in 1s
ci / bootstrap and conformance (windows-x64) (push) Has been skipped
ci / bootstrap and conformance (linux-arm64) (push) Has been skipped
ci / bootstrap and conformance (macos-arm64) (push) Has been skipped
ci / bootstrap (linux-x64) (push) Successful in 16s
ci / cross-compiled on linux-x64 (windows-x64) (push) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (push) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (push) Has been skipped
ci / the VS Code extension (editors/vscode) (push) Successful in 19s
ci / tier A (linux-x64) (push) Successful in 4m38s
ci / tools/release-sync and tools/registry (linux-x64) (push) Successful in 1m18s
ci / every host emits the same C (push) Successful in 5m9s
ci / tier B (linux-x64) (push) Successful in 9m25s
ci / pack the seed (push) Has been skipped
ci / archives (push) Has been skipped
ci / the playground (torb as WebAssembly) (push) Successful in 2m6s
ci / gates (push) Successful in 0s
2990ac8f46
argus approved these changes 2026-10-09 12:55:05 +00:00
argus left a comment

Approved at 2990ac8.

  • Tests: the change is test-only and removes a wall-clock dependency. started() is the single constructor of the checked-request tests, checkedWith goes through it, and the slicing tests still set checkBudget = 0 afterwards. CI is green on this head (tier A, gates).
  • Correctness: checkBudget is a var of the server (server.trb:99), so the assignment is valid; Int.maximum makes spent >= checkBudget never fire, so a check ends in its batch. The remaining direct LanguageServer.over sites (life cycle, symbols, tests of a file) use requests outside checkedRequests, so they do not depend on the budget.
  • Scope: only compiler/tests/language-server.test.trb, matches #268; server and default untouched.
  • Security/data/docs: nothing affected.

Non-blocking: the doc comment says every test server has no time budget, while the three direct LanguageServer.over sites keep the default (harmless today, as none of them waits for a check).

Approved at 2990ac8. - Tests: the change is test-only and removes a wall-clock dependency. `started()` is the single constructor of the checked-request tests, `checkedWith` goes through it, and the slicing tests still set `checkBudget = 0` afterwards. CI is green on this head (tier A, gates). - Correctness: `checkBudget` is a `var` of the server (`server.trb:99`), so the assignment is valid; `Int.maximum` makes `spent >= checkBudget` never fire, so a check ends in its batch. The remaining direct `LanguageServer.over` sites (life cycle, symbols, tests of a file) use requests outside `checkedRequests`, so they do not depend on the budget. - Scope: only `compiler/tests/language-server.test.trb`, matches #268; server and default untouched. - Security/data/docs: nothing affected. Non-blocking: the doc comment says every test server has no time budget, while the three direct `LanguageServer.over` sites keep the default (harmless today, as none of them waits for a check).
argus merged commit 2990ac8f46 into main 2026-10-09 12:55:11 +00:00
argus deleted branch loop/268-hover-test-main 2026-10-09 12:55:12 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!269
No description provided.