diff --git a/development/testing.md b/development/testing.md index 0c42c77..2b449a0 100644 --- a/development/testing.md +++ b/development/testing.md @@ -165,10 +165,13 @@ transport and protocol plumbing are not hand-rolled (see any order; the file is an integration test (~1.6 s) that needs `node_modules`. `didOpen` is handled in order before the completion request, so no settle delay is needed. -- The Go server logs `context canceled` on stderr once per server as it shuts - down (nine lines per `test:unit` run). It is benign and predates the protocol - client — the hand-rolled client produced the same line — so it is left as is - rather than filtered out of the server's stderr. +- The TS 7 Go server has a shutdown race: `handleExit` returns `io.EOF`, which + cancels the background context while `Session.updateWatches` is still running + (`internal/project/session.go`), so the bare `context canceled` error is + flushed straight to stderr once the outgoing queue is closed, and the server + exits 1 even though LSP expects 0 after `shutdown`. The helper therefore sends + `shutdown` and then closes stdin instead of sending `exit`; EOF makes the + server exit cleanly (code 0, no output), and the kill is kept as a fallback. ## Known issues diff --git a/src/util/__tests__/lsp-completion.ts b/src/util/__tests__/lsp-completion.ts index 8c0891c..20f0d2e 100644 --- a/src/util/__tests__/lsp-completion.ts +++ b/src/util/__tests__/lsp-completion.ts @@ -11,7 +11,6 @@ import { ConfigurationRequest, createMessageConnection, DidOpenTextDocumentNotification, - ExitNotification, InitializedNotification, InitializeRequest, type MessageConnection, @@ -140,18 +139,24 @@ export class LspSession { ); } - /** `shutdown` + `exit`, then kill the server; safe after a failed probe. */ + /** `shutdown` + close stdin, then kill the server; safe after a failed probe. */ public close(): Promise { - return this.#connection - .sendRequest(ShutdownRequest.type) - .then(() => - this.#connection.sendNotification(ExitNotification.type), - ) - .then(() => delay(EXIT_DELAY_MS)) - .finally(() => { - this.#connection.dispose(); - this.#child.kill(); - }); + return ( + this.#connection + .sendRequest(ShutdownRequest.type) + // The TS 7 Go server races a background task against the `exit` + // notification and logs a bare `context canceled` to stderr as it + // tears down (see development/testing.md § Autocomplete). EOF on + // stdin shuts it down cleanly instead: exit 0, no output. + .then(() => { + this.#child.stdin.end(); + }) + .then(() => delay(EXIT_DELAY_MS)) + .finally(() => { + this.#connection.dispose(); + this.#child.kill(); + }) + ); } #ensureInitialized(): Promise {