microsoft / microsoft/TypeScript

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

Đang mở
#63,862 1 bình luận 0 reaction 1 người được giao Được @jakebailey nhận Xem trên GitHub
Bug
Ngôn ngữ chính
Go
Star
111k
Fork
14.3k
Merge trung bình
2 ngày 4 giờ
Pull request đã merge (30 ngày)
132

Mô tả

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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.