microsoft / microsoft/TypeScript

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

オープン
#63,862 コメント 1 件 リアクション 0 件 担当者 1 名 GitHub で見る

@jakebailey がすでに取り組んでいます。

2026年7月28日 から。

Bug
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
2日 4時間
マージ済み PR(30日)
132

説明

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.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。