fix(lsp): recover from TSC isolate OOM instead of crashing the language server (#34693)
## Problem The Deno language server runs the TypeScript language service inside a dedicated V8 isolate (the "TSC" isolate, set up in `cli/lsp/tsc.rs::run_tsc_thread`). The TS type checker can occasionally balloon to many gigabytes for pathological inputs — for example deeply recursive library types (`@cliffy/command` builder chains) while a file is mid-edit, as in the [repro](https://github.com/albnnc/deno-repro-20240913) for #25613. When that happens V8 aborts the **entire process** with a fatal `Reached heap limit` OOM: ``` <--- Last few GCs ---> [46676:0x128008000] 154815 ms: Scavenge (interleaved) 9994.6 (10003.9) -> 9994.5 (10004.9) MB ... ``` This kills the whole language server, not just the offending request, and forces a slow restart and reindex of the workspace. ## Fix Install a near-heap-limit callback on the TSC isolate — the same V8 hook the worker already uses (`cli/lib/worker.rs`). When the heap is about to be exhausted while servicing a request, the callback **cancels the in-flight request** instead of letting the process die. The language service host already polls a cancellation token via `op_is_cancelled` (`cli/tsc/97_ts_host.js`) at TypeScript's internal checkpoints, so the runaway computation unwinds with an `OperationCanceledError` that is already caught and handled gracefully in `cli/tsc/98_lsp.js`, freeing the memory. The current request's token is published to the callback via a shared `Arc<Mutex<CancellationToken>>` that `op_poll_requests` keeps up to date. To give the unwinding room to run before any hard limit is hit, the callback also hands out a **bounded** amount of extra heap headroom (1 GiB total, in 128 MiB steps, capped at `initial_limit + 1 GiB`). Past that ceiling it returns the current limit and lets V8 OOM as a last resort, rather than growing without bound and exhausting all of system memory. `terminate_execution` (what the worker uses) is intentionally **not** used here: it throws an uncatchable exception that would tear down the long-lived TSC main loop and leave the isolate unusable. Cooperative cancellation is recoverable and integrates with the existing `OperationCanceledError` handling. ## Notes - Automated reproduction of a multi-gigabyte OOM is impractical/flaky to run in CI, so no integration test is added; the change is defensive and only activates under genuine near-heap-limit pressure. - The `textDocument/codeAction failed: The position is out of range (-32602)` line in the issue log is a separate, non-fatal stale-position symptom and is out of scope here. Closes #25613 Closes denoland/divybot#412 Co-authored-by: divybot <divybot@users.noreply.github.com> Co-authored-by: Divy Srivastava <me@littledivy.com>
E
em committed
5bb40510e8c905fc257d47003a7cc677e283f691
Parent: 815bc76
Committed by GitHub <noreply@github.com>
on 6/2/2026, 5:43:37 AM