Path: blob/main/extensions/copilot/src/extension/mcp/vscode-node/util.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as cp from 'child_process';6import { CancellationToken } from '../../../util/vs/base/common/cancellation';78export interface ICommandExecutor {9executeWithTimeout(10command: string,11args: string[],12cwd: string,13timeoutMs?: number,14expectZeroExitCode?: boolean,15cancellationToken?: CancellationToken): Promise<{ stdout: string; stderr: string; exitCode: number }>;16}1718export class CommandExecutor implements ICommandExecutor {19async executeWithTimeout(20command: string,21args: string[],22cwd: string,23timeoutMs?: number,24expectZeroExitCode?: boolean,25cancellationToken?: CancellationToken26): Promise<{ stdout: string; stderr: string; exitCode: number }> {27return await executeWithTimeout(28command,29args,30cwd,31timeoutMs,32expectZeroExitCode,33cancellationToken34);35}36}3738const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 10000;3940async function executeWithTimeout(41command: string,42args: string[],43cwd: string,44timeoutMs: number = 60000,45expectZeroExitCode: boolean = true,46cancellationToken?: CancellationToken) {4748return await new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve, reject) => {49const stdout: string[] = [];50const stderr: string[] = [];51let settled = false;5253const child: cp.ChildProcessWithoutNullStreams = cp.spawn(command, args, {54stdio: 'pipe',55env: { ...process.env },56cwd: cwd,57});5859child.stdout.setEncoding('utf8');60child.stderr.setEncoding('utf8');6162child.stdout.on('data', (data) => stdout.push(data));63child.stderr.on('data', (data) => stderr.push(data));6465const timeoutHandler = setTimeout(() => {66if (!settled) {67settled = true;68child.kill('SIGTERM');69setTimeout(() => {70if (!child.killed) {71child.kill('SIGKILL');72}73}, GRACEFUL_SHUTDOWN_TIMEOUT_MS);74reject(new Error(`Process timed out after ${timeoutMs}ms`));75}76}, timeoutMs);7778const cancellationHandler = cancellationToken?.onCancellationRequested(() => {79if (!settled) {80settled = true;81clearTimeout(timeoutHandler);82child.kill('SIGTERM');83setTimeout(() => {84if (!child.killed) {85child.kill('SIGKILL');86}87}, GRACEFUL_SHUTDOWN_TIMEOUT_MS);88reject(new Error(`Process cancelled`));89}90});9192child.on('error', (error) => {93if (!settled) {94settled = true;95clearTimeout(timeoutHandler);96cancellationHandler?.dispose();97reject(error);98}99});100101child.on('close', (code) => {102if (!settled) {103settled = true;104clearTimeout(timeoutHandler);105cancellationHandler?.dispose();106107if (expectZeroExitCode && code !== 0) {108reject(new Error(`Process ${child.pid} (${command}) failed with code ${code}.109stdout: ${stdout.join('')}110stderr: ${stderr.join('')}`));111} else {112resolve({113stdout: stdout.join(''),114stderr: stderr.join(''),115exitCode: code ?? -1,116});117}118}119});120});121}122123124