| 262 | * so the daemon outlives its launcher. |
| 263 | */ |
| 264 | export class StdioTransport extends LineBasedJsonRpcTransport { |
| 265 | private rl: readline.Interface | null = null; |
| 266 | private opts: Required<StdioTransportOptions>; |
| 267 | |
| 268 | constructor(opts: StdioTransportOptions = {}) { |
| 269 | super(); |
| 270 | this.opts = { |
| 271 | exitOnClose: opts.exitOnClose ?? true, |
| 272 | onClose: opts.onClose ?? (() => { /* no-op */ }), |
| 273 | }; |
| 274 | } |
| 275 | |
| 276 | start(handler: MessageHandler): void { |
| 277 | this.messageHandler = handler; |
| 278 | |
| 279 | this.rl = readline.createInterface({ |
| 280 | input: process.stdin, |
| 281 | output: process.stdout, |
| 282 | terminal: false, |
| 283 | }); |
| 284 | |
| 285 | this.rl.on('line', async (line) => { |
| 286 | await this.handleLine(line); |
| 287 | }); |
| 288 | |
| 289 | // readline 'close' fires on a clean stdin EOF. But a socket-backed stdin |
| 290 | // (the VS Code stdio shape) can fail with an 'error' (ECONNRESET/hangup) |
| 291 | // that readline doesn't surface as 'close' — unhandled, it escalated to |
| 292 | // the global uncaughtException handler (which keeps running), orphaning |
| 293 | // the server and, on Linux, busy-spinning a POLLHUP fd at 100% CPU. Treat |
| 294 | // 'error' as terminal too, and destroy stdin so the fd leaves epoll (#799). |
| 295 | let closed = false; |
| 296 | const onStreamEnd = (): void => { |
| 297 | if (closed) return; |
| 298 | closed = true; |
| 299 | try { process.stdin.destroy(); } catch { /* already gone */ } |
| 300 | this.opts.onClose(); |
| 301 | if (this.opts.exitOnClose) { |
| 302 | process.exit(0); |
| 303 | } |
| 304 | }; |
| 305 | this.rl.on('close', onStreamEnd); |
| 306 | process.stdin.on('error', onStreamEnd); |
| 307 | } |
| 308 | |
| 309 | stop(): void { |
| 310 | if (this.stopped) return; |
| 311 | this.stopped = true; |
| 312 | this.rejectPending('Transport stopped'); |
| 313 | if (this.rl) { |
| 314 | this.rl.close(); |
| 315 | this.rl = null; |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | protected write(line: string): void { |
| 320 | process.stdout.write(line + '\n'); |
| 321 | } |
nothing calls this directly
no outgoing calls
no test coverage detected