microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/upgradeTypeScriptTo7

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/common/node/childProcess.ts

200lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as nodeChildProcess from "child_process";
5import { kill } from "process";
6import { ErrorHelper } from "../error/errorHelper";
7import { InternalErrorCode } from "../error/internalErrorCode";
8import { notNullOrUndefined } from "../utils";
9
10// Uncomment the following lines to record all spawned processes executions
11// import {Recorder} from "../../../test/resources/processExecution/recorder";
12// Recorder.installGlobalRecorder();
13
14export interface IExecResult {
15 process: nodeChildProcess.ChildProcess;
16 outcome: Promise<string>;
17}
18
19export interface ISpawnResult {
20 spawnedProcess: nodeChildProcess.ChildProcess;
21 stdin: NodeJS.WritableStream;
22 stdout: NodeJS.ReadableStream;
23 stderr: NodeJS.ReadableStream;
24 outcome: Promise<void>;
25}
26
27interface IExecOptions {
28 cwd?: string;
29 stdio?: any;
30 env?: any;
31 encoding?: string;
32 timeout?: number;
33 maxBuffer?: number;
34 killSignal?: string;
35}
36
37interface ISpawnOptions {
38 cwd?: string;
39 stdio?: any;
40 env?: any;
41 detached?: boolean;
42 shell?: boolean;
43}
44
45export class ChildProcess {
46 public static ERROR_TIMEOUT_MILLISECONDS = 300;
47 private childProcess: typeof nodeChildProcess;
48
49 constructor({ childProcess = nodeChildProcess } = {}) {
50 this.childProcess = childProcess;
51 }
52
53 public exec(command: string, options: IExecOptions = {}): Promise<IExecResult> {
54 let outcome: Promise<string>;
55 let process: nodeChildProcess.ChildProcess;
56 return new Promise<IExecResult>(resolveRes => {
57 outcome = new Promise<string>((resolve, reject) => {
58 process = this.childProcess.exec(
59 command,
60 options,
61 // eslint-disable-next-line @typescript-eslint/no-unused-vars
62 (
63 error: nodeChildProcess.ExecException | null,
64 stdout: string | Buffer,
65 stderr: string | Buffer,
66 ) => {
67 if (error) {
68 reject(
69 ErrorHelper.getNestedError(
70 error,
71 InternalErrorCode.CommandFailed,
72 command,
73 ),
74 );
75 } else {
76 resolve(stdout.toString());
77 }
78 },
79 );
80 });
81 resolveRes({ process, outcome });
82 });
83 }
84
85 public async execToString(command: string, options: IExecOptions = {}): Promise<string> {
86 const execResult = await this.exec(command, options);
87 const stdout = await execResult.outcome;
88 return stdout.toString();
89 }
90
91 public execFileSync(
92 command: string,
93 args: string[] = [],
94 options: IExecOptions = {},
95 ): Buffer | string {
96 return this.childProcess.execFileSync(command, args, options);
97 }
98
99 public spawn(
100 command: string,
101 args: string[] = [],
102 options: ISpawnOptions = {},
103 showStdOutputsOnError: boolean = false,
104 ): ISpawnResult {
105 const spawnedProcess = this.childProcess.spawn(
106 command,
107 args,
108 Object.assign({}, options, { shell: true }),
109 );
110 const outcome: Promise<void> = new Promise((resolve, reject) => {
111 spawnedProcess.once("error", (error: any) => {
112 reject(error);
113 });
114
115 const stderrChunks: string[] = [];
116 const stdoutChunks: string[] = [];
117
118 spawnedProcess.stderr.on("data", data => {
119 stderrChunks.push(data.toString());
120 });
121
122 spawnedProcess.stdout.on("data", data => {
123 stdoutChunks.push(data.toString());
124 });
125
126 spawnedProcess.once("exit", (code: number) => {
127 if (code === 0) {
128 resolve();
129 } else {
130 const commandWithArgs = `${command} ${args.join(" ")}`;
131 if (showStdOutputsOnError) {
132 let details = "";
133 if (stdoutChunks.length > 0) {
134 details = details.concat(
135 `\n\tSTDOUT: ${stdoutChunks[stdoutChunks.length - 1]}`,
136 );
137 }
138 if (stderrChunks.length > 0) {
139 details = details.concat(`\n\tSTDERR: ${stderrChunks.join("\n\t")}`);
140 }
141 if (details === "") {
142 details = "STDOUT and STDERR are empty!";
143 }
144 reject(
145 ErrorHelper.getInternalError(
146 InternalErrorCode.CommandFailedWithDetails,
147 commandWithArgs,
148 details,
149 ),
150 );
151 } else {
152 reject(
153 ErrorHelper.getInternalError(
154 InternalErrorCode.CommandFailed,
155 commandWithArgs,
156 code,
157 ),
158 );
159 }
160 }
161 });
162 });
163 return {
164 spawnedProcess,
165 stdin: spawnedProcess.stdin,
166 stdout: spawnedProcess.stdout,
167 stderr: spawnedProcess.stderr,
168 outcome,
169 };
170 }
171
172 // Kills any orphaned Instruments processes belonging to the user.
173 //
174 // In some cases, we've seen interactions between Instruments and the iOS
175 // simulator that cause hung instruments and DTServiceHub processes. If
176 // enough instances pile up, the host machine eventually becomes
177 // unresponsive. Until the underlying issue is resolved, manually kill any
178 // orphaned instances (where the parent process has died and PPID is 1)
179 // before launching another instruments run.
180 public async killOrphanedInstrumentsProcesses(): Promise<void> {
181 const result = await this.execToString("ps -e -o user,ppid,pid,comm");
182 if (result) {
183 result
184 .split("\n")
185 .filter(notNullOrUndefined)
186 .map(a => /^(\S+)\s+1\s+(\d+)\s+(.+)$/.exec(a))
187 .filter(notNullOrUndefined)
188 .filter(m => m[1] === process.env.USER)
189 .filter(
190 m =>
191 m[3] && ["/instruments", "/DTServiceHub"].some(name => m[3].endsWith(name)),
192 )
193 .forEach(m => {
194 const pid = m[2];
195 console.debug(`Killing orphaned Instruments process: ${pid}`);
196 kill(parseInt(pid, 10), "SIGKILL");
197 });
198 }
199 }
200}