go-dev 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/process-manager.js +76 -45
package/package.json
CHANGED
package/src/process-manager.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
const { spawn, spawnSync } = require('child_process');
|
|
1
|
+
const { spawn, spawnSync, execSync } = require('child_process');
|
|
2
2
|
const prefixLines = require("./prefix-lines");
|
|
3
3
|
|
|
4
|
+
const isWindows = process.platform === 'win32';
|
|
5
|
+
|
|
4
6
|
class ProcessManager {
|
|
5
7
|
constructor() {
|
|
6
8
|
this.managedProcesses = new Set();
|
|
@@ -123,13 +125,11 @@ class ProcessManager {
|
|
|
123
125
|
startedProcess.stdout.on('data', (data) => {
|
|
124
126
|
const result = prefixLines(data.toString(), prefix, lastFormatting);
|
|
125
127
|
lastFormatting = result.lastFormatting;
|
|
126
|
-
// process.stdout.write(JSON.stringify(data.toString()) + "\n");
|
|
127
128
|
process.stdout.write(result.prefixedText);
|
|
128
129
|
});
|
|
129
130
|
startedProcess.stderr.on('data', (data) => {
|
|
130
131
|
const result = prefixLines(data.toString(), prefix, lastFormatting);
|
|
131
132
|
lastFormatting = result.lastFormatting;
|
|
132
|
-
// process.stderr.write(JSON.stringify(data.toString()) + "\n");
|
|
133
133
|
process.stderr.write(result.prefixedText);
|
|
134
134
|
});
|
|
135
135
|
|
|
@@ -178,47 +178,65 @@ class ProcessManager {
|
|
|
178
178
|
return processReference;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Kills a single child process.
|
|
183
|
+
* On Windows, uses taskkill to kill the entire process tree.
|
|
184
|
+
* @param {ChildProcess} childProcess - The process to kill.
|
|
185
|
+
* @returns {Promise<void>}
|
|
186
|
+
*/
|
|
187
|
+
killProcess(childProcess) {
|
|
188
|
+
if (childProcess.killed || childProcess.exitCode != null) {
|
|
189
|
+
return Promise.resolve();
|
|
184
190
|
}
|
|
185
191
|
|
|
186
|
-
this.managedProcesses.delete(
|
|
192
|
+
this.managedProcesses.delete(childProcess);
|
|
187
193
|
|
|
188
|
-
|
|
194
|
+
if (isWindows) {
|
|
195
|
+
try {
|
|
196
|
+
execSync(`taskkill /T /F /PID ${childProcess.pid}`, { stdio: 'ignore' });
|
|
197
|
+
} catch {
|
|
198
|
+
// Process may have already been terminated by Ctrl+C signal propagation - this is OK
|
|
199
|
+
}
|
|
200
|
+
return Promise.resolve();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// On Unix, use SIGTERM with timeout fallback to SIGKILL
|
|
204
|
+
return new Promise((resolve) => {
|
|
189
205
|
let exited = false;
|
|
206
|
+
let timeoutId = null;
|
|
207
|
+
|
|
190
208
|
const onExit = () => {
|
|
191
209
|
exited = true;
|
|
210
|
+
if (timeoutId) {
|
|
211
|
+
clearTimeout(timeoutId);
|
|
212
|
+
timeoutId = null;
|
|
213
|
+
}
|
|
192
214
|
resolve();
|
|
193
215
|
};
|
|
194
|
-
|
|
216
|
+
childProcess.on('exit', onExit);
|
|
195
217
|
|
|
196
|
-
setTimeout(() => {
|
|
218
|
+
timeoutId = setTimeout(() => {
|
|
197
219
|
if (exited) {
|
|
198
220
|
return;
|
|
199
221
|
}
|
|
200
222
|
|
|
201
|
-
console.error(`[ProcessManager] Timeout reached for process interruption ${
|
|
223
|
+
console.error(`[ProcessManager] Timeout reached for process interruption ${childProcess.pid}`);
|
|
202
224
|
try {
|
|
203
|
-
|
|
225
|
+
childProcess.kill('SIGKILL');
|
|
204
226
|
} catch (e) {
|
|
205
|
-
console.error(`[ProcessManager] Error force killing process ${
|
|
206
|
-
reject(e);
|
|
227
|
+
console.error(`[ProcessManager] Error force killing process ${childProcess.pid}: ${e.message}`);
|
|
207
228
|
}
|
|
229
|
+
resolve();
|
|
208
230
|
}, 500);
|
|
209
231
|
|
|
210
232
|
try {
|
|
211
|
-
|
|
212
|
-
return;
|
|
213
|
-
} catch (e) {
|
|
214
|
-
console.error(`[ProcessManager] Error signaling process ${process.pid}: ${e.message}`);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
process.kill('SIGKILL');
|
|
233
|
+
childProcess.kill('SIGTERM');
|
|
219
234
|
} catch (e) {
|
|
220
|
-
console.error(`[ProcessManager] Error
|
|
221
|
-
|
|
235
|
+
console.error(`[ProcessManager] Error signaling process ${childProcess.pid}: ${e.message}`);
|
|
236
|
+
if (timeoutId) {
|
|
237
|
+
clearTimeout(timeoutId);
|
|
238
|
+
}
|
|
239
|
+
resolve();
|
|
222
240
|
}
|
|
223
241
|
});
|
|
224
242
|
}
|
|
@@ -231,41 +249,54 @@ class ProcessManager {
|
|
|
231
249
|
console.log('[ProcessManager] Cleanup of managed processes already in progress, skipping.');
|
|
232
250
|
return;
|
|
233
251
|
}
|
|
234
|
-
for (let index = this.managedProcesses.length - 1; index >= 0; index--) {
|
|
235
|
-
const process = this.managedProcesses[index];
|
|
236
|
-
if (process.killed || process.exitCode != null) {
|
|
237
|
-
this.managedProcesses.splice(index, 1);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
252
|
|
|
241
253
|
this.cleanupInProgress = true;
|
|
242
254
|
|
|
243
255
|
console.log('\n[ProcessManager] Initiating cleanup of managed processes...');
|
|
244
256
|
|
|
245
|
-
|
|
257
|
+
// Filter out already-dead processes
|
|
258
|
+
const processesToKill = [...this.managedProcesses].filter(proc => {
|
|
246
259
|
if (proc.killed || proc.exitCode != null) {
|
|
247
|
-
|
|
260
|
+
this.managedProcesses.delete(proc);
|
|
261
|
+
return false;
|
|
248
262
|
}
|
|
249
|
-
|
|
263
|
+
return true;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
for (const proc of processesToKill) {
|
|
250
267
|
console.log(`[ProcessManager] Killing managed process PID: ${proc.pid}`);
|
|
251
268
|
try {
|
|
252
|
-
|
|
269
|
+
if (isWindows) {
|
|
270
|
+
// On Windows, use taskkill to kill the entire process tree
|
|
271
|
+
try {
|
|
272
|
+
execSync(`taskkill /T /F /PID ${proc.pid}`, { stdio: 'ignore' });
|
|
273
|
+
} catch {
|
|
274
|
+
// Process may have already been terminated - this is OK
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
proc.kill('SIGTERM');
|
|
278
|
+
}
|
|
253
279
|
} catch (e) {
|
|
254
280
|
console.error(`[ProcessManager] Error killing process ${proc.pid}: ${e.message}`);
|
|
255
281
|
}
|
|
256
282
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
283
|
+
|
|
284
|
+
// On Unix, wait for processes to exit gracefully before using SIGKILL
|
|
285
|
+
if (processesToKill.length > 0 && !isWindows) {
|
|
286
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
287
|
+
|
|
288
|
+
for (const proc of [...this.managedProcesses]) {
|
|
289
|
+
if (proc.killed || proc.exitCode != null) {
|
|
290
|
+
this.managedProcesses.delete(proc);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
263
293
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
294
|
+
console.warn(`[ProcessManager] Process ${proc.pid} did not exit gracefully, forcing kill.`);
|
|
295
|
+
try {
|
|
296
|
+
proc.kill('SIGKILL');
|
|
297
|
+
} catch (e) {
|
|
298
|
+
console.error(`[ProcessManager] Error forcing kill for process ${proc.pid}: ${e.message}`);
|
|
299
|
+
}
|
|
269
300
|
}
|
|
270
301
|
}
|
|
271
302
|
|