microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c78564627d05eb04bd03fb8d3978871935d0c077

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/node/childProcess.ts

191lines · 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 { ErrorHelper } from "../error/errorHelper";
6import { InternalErrorCode } from "../error/internalErrorCode";
7import { notNullOrUndefined } from "../utils";
8import { kill } from "process";
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}
43
44export class ChildProcess {
45 public static ERROR_TIMEOUT_MILLISECONDS = 300;
46 private childProcess: typeof nodeChildProcess;
47
48 constructor({ childProcess = nodeChildProcess } = {}) {
49 this.childProcess = childProcess;
50 }
51
52 public exec(command: string, options: IExecOptions = {}): Promise<IExecResult> {
53 let outcome: Promise<string>;
54 let process: nodeChildProcess.ChildProcess;
55 return new Promise<IExecResult>(resolveRes => {
56 outcome = new Promise<string>((resolve, reject) => {
57 process = this.childProcess.exec(
58 command,
59 options,
60 // eslint-disable-next-line @typescript-eslint/no-unused-vars
61 (error: Error, stdout: string, stderr: string) => {
62 if (error) {
63 reject(
64 ErrorHelper.getNestedError(
65 error,
66 InternalErrorCode.CommandFailed,
67 command,
68 ),
69 );
70 } else {
71 resolve(stdout);
72 }
73 },
74 );
75 });
76 resolveRes({ process: process, outcome: outcome });
77 });
78 }
79
80 public async execToString(command: string, options: IExecOptions = {}): Promise<string> {
81 const execResult = await this.exec(command, options);
82 const stdout = await execResult.outcome;
83 return stdout.toString();
84 }
85
86 public execFileSync(
87 command: string,
88 args: string[] = [],
89 options: IExecOptions = {},
90 ): Buffer | string {
91 return this.childProcess.execFileSync(command, args, options);
92 }
93
94 public spawn(
95 command: string,
96 args: string[] = [],
97 options: ISpawnOptions = {},
98 showStdOutputsOnError: boolean = false,
99 ): ISpawnResult {
100 const spawnedProcess = this.childProcess.spawn(command, args, options);
101 let outcome: Promise<void> = new Promise((resolve, reject) => {
102 spawnedProcess.once("error", (error: any) => {
103 reject(error);
104 });
105
106 const stderrChunks: string[] = [];
107 const stdoutChunks: string[] = [];
108
109 spawnedProcess.stderr.on("data", data => {
110 stderrChunks.push(data.toString());
111 });
112
113 spawnedProcess.stdout.on("data", data => {
114 stdoutChunks.push(data.toString());
115 });
116
117 spawnedProcess.once("exit", (code: number) => {
118 if (code === 0) {
119 resolve();
120 } else {
121 const commandWithArgs = command + " " + args.join(" ");
122 if (showStdOutputsOnError) {
123 let details = "";
124 if (stdoutChunks.length > 0) {
125 details = details.concat(
126 `\n\tSTDOUT: ${stdoutChunks[stdoutChunks.length - 1]}`,
127 );
128 }
129 if (stderrChunks.length > 0) {
130 details = details.concat(`\n\tSTDERR: ${stderrChunks.join("\n\t")}`);
131 }
132 if (details === "") {
133 details = "STDOUT and STDERR are empty!";
134 }
135 reject(
136 ErrorHelper.getInternalError(
137 InternalErrorCode.CommandFailedWithDetails,
138 commandWithArgs,
139 details,
140 ),
141 );
142 } else {
143 reject(
144 ErrorHelper.getInternalError(
145 InternalErrorCode.CommandFailed,
146 commandWithArgs,
147 code,
148 ),
149 );
150 }
151 }
152 });
153 });
154 return {
155 spawnedProcess: spawnedProcess,
156 stdin: spawnedProcess.stdin,
157 stdout: spawnedProcess.stdout,
158 stderr: spawnedProcess.stderr,
159 outcome: outcome,
160 };
161 }
162
163 // Kills any orphaned Instruments processes belonging to the user.
164 //
165 // In some cases, we've seen interactions between Instruments and the iOS
166 // simulator that cause hung instruments and DTServiceHub processes. If
167 // enough instances pile up, the host machine eventually becomes
168 // unresponsive. Until the underlying issue is resolved, manually kill any
169 // orphaned instances (where the parent process has died and PPID is 1)
170 // before launching another instruments run.
171 public async killOrphanedInstrumentsProcesses(): Promise<void> {
172 const result = await this.execToString("ps -e -o user,ppid,pid,comm");
173 if (result) {
174 result
175 .split("\n")
176 .filter(notNullOrUndefined)
177 .map(a => /^(\S+)\s+1\s+(\d+)\s+(.+)$/.exec(a))
178 .filter(notNullOrUndefined)
179 .filter(m => m[1] === process.env.USER)
180 .filter(
181 m =>
182 m[3] && ["/instruments", "/DTServiceHub"].some(name => m[3].endsWith(name)),
183 )
184 .forEach(m => {
185 const pid = m[2];
186 console.debug(`Killing orphaned Instruments process: ${pid}`);
187 kill(parseInt(pid, 10), "SIGKILL");
188 });
189 }
190 }
191}
192