microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
connor/add-network-inspector-manager-tests

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/node/childProcess.ts

231lines · 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 async execFileToString(
92 command: string,
93 args: string[] = [],
94 options: IExecOptions = {},
95 ): Promise<string> {
96 return new Promise<string>((resolve, reject) => {
97 this.childProcess.execFile(
98 command,
99 args,
100 options,
101 (
102 error: nodeChildProcess.ExecException | null,
103 stdout: string | Buffer,
104 stderr: string | Buffer,
105 ) => {
106 if (error) {
107 reject(
108 ErrorHelper.getNestedError(
109 error,
110 InternalErrorCode.CommandFailed,
111 command,
112 ),
113 );
114 } else {
115 resolve(stdout.toString());
116 }
117 },
118 );
119 });
120 }
121
122 public execFileSync(
123 command: string,
124 args: string[] = [],
125 options: IExecOptions = {},
126 ): Buffer | string {
127 return this.childProcess.execFileSync(command, args, options);
128 }
129
130 public spawn(
131 command: string,
132 args: string[] = [],
133 options: ISpawnOptions = {},
134 showStdOutputsOnError: boolean = false,
135 ): ISpawnResult {
136 const spawnedProcess = this.childProcess.spawn(
137 command,
138 args,
139 Object.assign({}, options, { shell: true }),
140 );
141 const outcome: Promise<void> = new Promise((resolve, reject) => {
142 spawnedProcess.once("error", (error: any) => {
143 reject(error);
144 });
145
146 const stderrChunks: string[] = [];
147 const stdoutChunks: string[] = [];
148
149 spawnedProcess.stderr.on("data", data => {
150 stderrChunks.push(data.toString());
151 });
152
153 spawnedProcess.stdout.on("data", data => {
154 stdoutChunks.push(data.toString());
155 });
156
157 spawnedProcess.once("exit", (code: number) => {
158 if (code === 0) {
159 resolve();
160 } else {
161 const commandWithArgs = `${command} ${args.join(" ")}`;
162 if (showStdOutputsOnError) {
163 let details = "";
164 if (stdoutChunks.length > 0) {
165 details = details.concat(
166 `\n\tSTDOUT: ${stdoutChunks[stdoutChunks.length - 1]}`,
167 );
168 }
169 if (stderrChunks.length > 0) {
170 details = details.concat(`\n\tSTDERR: ${stderrChunks.join("\n\t")}`);
171 }
172 if (details === "") {
173 details = "STDOUT and STDERR are empty!";
174 }
175 reject(
176 ErrorHelper.getInternalError(
177 InternalErrorCode.CommandFailedWithDetails,
178 commandWithArgs,
179 details,
180 ),
181 );
182 } else {
183 reject(
184 ErrorHelper.getInternalError(
185 InternalErrorCode.CommandFailed,
186 commandWithArgs,
187 code,
188 ),
189 );
190 }
191 }
192 });
193 });
194 return {
195 spawnedProcess,
196 stdin: spawnedProcess.stdin,
197 stdout: spawnedProcess.stdout,
198 stderr: spawnedProcess.stderr,
199 outcome,
200 };
201 }
202
203 // Kills any orphaned Instruments processes belonging to the user.
204 //
205 // In some cases, we've seen interactions between Instruments and the iOS
206 // simulator that cause hung instruments and DTServiceHub processes. If
207 // enough instances pile up, the host machine eventually becomes
208 // unresponsive. Until the underlying issue is resolved, manually kill any
209 // orphaned instances (where the parent process has died and PPID is 1)
210 // before launching another instruments run.
211 public async killOrphanedInstrumentsProcesses(): Promise<void> {
212 const result = await this.execToString("ps -e -o user,ppid,pid,comm");
213 if (result) {
214 result
215 .split("\n")
216 .filter(notNullOrUndefined)
217 .map(a => /^(\S+)\s+1\s+(\d+)\s+(.+)$/.exec(a))
218 .filter(notNullOrUndefined)
219 .filter(m => m[1] === process.env.USER)
220 .filter(
221 m =>
222 m[3] && ["/instruments", "/DTServiceHub"].some(name => m[3].endsWith(name)),
223 )
224 .forEach(m => {
225 const pid = m[2];
226 console.debug(`Killing orphaned Instruments process: ${pid}`);
227 kill(parseInt(pid, 10), "SIGKILL");
228 });
229 }
230 }
231}
232