microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
add-signproj-for-microbuild1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/common/node/childProcess.ts

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