microsoft / microsoft/TypeScript

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

Abierto
#63,862 1 comentario 0 reacciones 1 asignado Reclamado por @jakebailey Ver en GitHub
Bug
Lenguaje dominante
Go
Estrellas
111k
Forks
14.3k
Merge medio
2 d 4 h
PR fusionados (30 d)
132

Descripción

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.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.