From c2705ffb454481e5bb7b2cddfcfeddaf9a5d5fc2 Mon Sep 17 00:00:00 2001 From: l3ops Date: Tue, 6 Dec 2022 16:55:03 +0100 Subject: [PATCH] fix(vscode): add a timeout on the stream reading tasks (#3995) --- editors/vscode/src/main.ts | 86 ++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/editors/vscode/src/main.ts b/editors/vscode/src/main.ts index 96b0ccbe7e5..954c91d512c 100644 --- a/editors/vscode/src/main.ts +++ b/editors/vscode/src/main.ts @@ -1,5 +1,4 @@ -import { spawn } from "child_process"; -import type { Readable } from "stream"; +import { type ChildProcess, spawn } from "child_process"; import { connect, type Socket } from "net"; import { promisify } from "util"; import { @@ -248,45 +247,88 @@ async function fileExists(path: Uri) { } } -function collectStream(stream: Readable) { - return new Promise((resolve, reject) => { - let buffer = ""; - stream.on("data", (data) => { - buffer += data.toString("utf-8"); - }); +interface MutableBuffer { + content: string; +} - stream.on("error", reject); +function collectStream( + outputChannel: OutputChannel, + process: ChildProcess, + key: "stdout" | "stderr", + buffer: MutableBuffer, +) { + return new Promise((resolve, reject) => { + const stream = process[key]; + stream.setEncoding("utf-8"); + + stream.on("error", (err) => { + outputChannel.appendLine(`[cli-${key}] error`); + reject(err); + }); + stream.on("close", () => { + outputChannel.appendLine(`[cli-${key}] close`); + resolve(); + }); + stream.on("finish", () => { + outputChannel.appendLine(`[cli-${key}] finish`); + resolve(); + }); stream.on("end", () => { - resolve(buffer); + outputChannel.appendLine(`[cli-${key}] end`); + resolve(); + }); + + stream.on("data", (data) => { + outputChannel.appendLine(`[cli-${key}] data ${data.length}`); + buffer.content += data; }); }); } +function withTimeout(promise: Promise, duration: number) { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(resolve, duration)), + ]); +} + async function getSocket( outputChannel: OutputChannel, command: string, ): Promise { const process = spawn(command, ["__print_socket"], { - stdio: "pipe", + stdio: [null, "pipe", "pipe"], }); - const exitCode = new Promise((resolve, reject) => { + const stdout = { content: "" }; + const stderr = { content: "" }; + + const stdoutPromise = collectStream(outputChannel, process, "stdout", stdout); + const stderrPromise = collectStream(outputChannel, process, "stderr", stderr); + + const exitCode = await new Promise((resolve, reject) => { process.on("error", reject); - process.on("exit", resolve); + process.on("exit", (code) => { + outputChannel.appendLine(`[cli] exit ${code}`); + resolve(code); + }); + process.on("close", (code) => { + outputChannel.appendLine(`[cli] close ${code}`); + resolve(code); + }); }); - const [stdout, stderr, code] = await Promise.all([ - collectStream(process.stdout), - collectStream(process.stderr), - exitCode, + await Promise.all([ + withTimeout(stdoutPromise, 1000), + withTimeout(stderrPromise, 1000), ]); - const pipeName = stdout.trimEnd(); + const pipeName = stdout.content.trimEnd(); - if (code !== 0 || pipeName.length === 0) { - let message = `Command "${command} __print_socket" exited with code ${code}`; - if (stderr.length > 0) { - message += `\nOutput:\n${stderr}`; + if (exitCode !== 0 || pipeName.length === 0) { + let message = `Command "${command} __print_socket" exited with code ${exitCode}`; + if (stderr.content.length > 0) { + message += `\nOutput:\n${stderr.content}`; } throw new Error(message);