microsoft / microsoft/TypeScript

wasip1: LSP over node:wasi hangs after `initialize` — signal watcher starves the scheduler (fix included)

Ouverte
#63,862 1 commentaire 0 réactions 1 personne assignée Voir sur GitHub

@jakebailey y travaille déjà.

Depuis le 28/7/2026.

Bug
Langage dominant
Go
Étoiles
111k
Forks
14.3k
Merge moyen
2 j 4 h
PR mergées (30 j)
132

Description

Split out from microsoft/TypeScript#63858. `tsgo --lsp --stdio` built for wasip1 and run under Node's `node:wasi` answers `initialize`, then ignores everything after it, spinning at 100% CPU. CLI invocations (`tsgo -p`) are unaffected — output is identical to the native binary on node >= 23.

**Cause:** `cmd/tsgo` calls `signal.NotifyContext` unconditionally. On wasip1 signals are never delivered, and Go's signal-watcher goroutine busy-spins (a `sched_yield` loop — we measured ~190k calls in 8s of idle). Wasm is single-threaded, so that always-runnable goroutine keeps the scheduler from ever reaching `poll_oneoff` — and the stdin reader, parked waiting for input, never wakes.

**Fix:** skip the signal watcher on wasip1 — since no signal can arrive, `context.WithCancel` is equivalent:

```go
func notifyContext(parent context.Context, sigs ...os.Signal) (context.Context, context.CancelFunc) {
if runtime.GOOS == "wasip1" {
return context.WithCancel(parent)
}
return signal.NotifyContext(parent, sigs...)
}
```

Patch applies at `typescript/v7.0.2`. With it, a full LSP session under node:wasi works — initialize, publishDiagnostics, hover, shutdown — with exit behavior matching native. It also lets the runtime idle in `poll_oneoff` instead of burning a core.

One note: this makes LSP-over-wasm node-only for now — wasmtime 46 refuses the non-blocking stdio call (`fd_fdstat_set_flags`) the LSP path needs, so it still freezes there even with the fix.

Minimal repro driver:

```js
import { readFile } from 'node:fs/promises';
import { WASI } from 'node:wasi';
const wasi = new WASI({ version: 'preview1', args: ['tsgo', '--lsp', '--stdio'],
env: { PWD: '/' }, preopens: { '/': process.cwd() }, returnOnExit: true });
const wasm = await WebAssembly.compile(await readFile('tsgo.wasm'));
const inst = await WebAssembly.instantiate(wasm, wasi.getImportObject());
wasi.start(inst);
```

Spawn it with piped stdio, send a framed `initialize` (answered), then anything else: the stock binary never reads it; the patched one proceeds.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.