batch-cluster
Version:
Manage a cluster of child processes
137 lines • 7.75 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _ProcessTerminator_instances, _ProcessTerminator_logger, _ProcessTerminator_waitForTaskCompletion, _ProcessTerminator_removeErrorListeners, _ProcessTerminator_sendExitCommand, _ProcessTerminator_destroyStreams, _ProcessTerminator_handleGracefulShutdown, _ProcessTerminator_forceKillIfRunning, _ProcessTerminator_awaitNotRunning;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProcessTerminator = void 0;
const Async_1 = require("./Async");
const Pids_1 = require("./Pids");
const Stream_1 = require("./Stream");
const String_1 = require("./String");
const Timeout_1 = require("./Timeout");
/**
* Utility class for managing process termination lifecycle
*/
class ProcessTerminator {
constructor(opts) {
_ProcessTerminator_instances.add(this);
this.opts = opts;
_ProcessTerminator_logger.set(this, void 0);
__classPrivateFieldSet(this, _ProcessTerminator_logger, opts.logger, "f");
}
/**
* Terminates a child process gracefully or forcefully
*
* @param proc The child process to terminate
* @param processName Name for logging purposes
* @param pid Process ID
* @param lastTask Current task being processed
* @param startupTaskId ID of the startup task
* @param gracefully Whether to wait for current task completion
* @param reason Reason for termination
* @param isExited Whether the process has already exited
* @param isRunning Function to check if process is still running
* @returns Promise that resolves when termination is complete
*/
async terminate(proc, processName, lastTask, startupTaskId, gracefully, isExited, isRunning) {
var _a;
// Wait for current task to complete if graceful termination requested
await __classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_waitForTaskCompletion).call(this, lastTask, startupTaskId, gracefully);
// Remove error listeners to prevent EPIPE errors during termination
__classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_removeErrorListeners).call(this, proc);
// Send exit command to process
__classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_sendExitCommand).call(this, proc);
// Destroy streams
__classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_destroyStreams).call(this, proc);
// Handle graceful shutdown with timeouts
await __classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_handleGracefulShutdown).call(this, proc, gracefully, isExited, isRunning);
// Force kill if still running
__classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_forceKillIfRunning).call(this, proc, processName, isRunning);
// Final cleanup
try {
(_a = proc.disconnect) === null || _a === void 0 ? void 0 : _a.call(proc);
}
catch {
// Ignore disconnect errors
}
// Note: Caller should emit childEnd event with proper BatchProcess instance
}
}
exports.ProcessTerminator = ProcessTerminator;
_ProcessTerminator_logger = new WeakMap(), _ProcessTerminator_instances = new WeakSet(), _ProcessTerminator_waitForTaskCompletion = async function _ProcessTerminator_waitForTaskCompletion(lastTask, startupTaskId, gracefully) {
// Don't wait for startup tasks or if no task is running
if (lastTask == null || lastTask.taskId === startupTaskId) {
return;
}
try {
// Wait for the process to complete and streams to flush
await (0, Timeout_1.thenOrTimeout)(lastTask.promise, gracefully ? 2000 : 250);
}
catch {
// Ignore errors during task completion wait
}
// Reject task if still pending
if (lastTask.pending) {
lastTask.reject(new Error(`Process terminated before task completed (${JSON.stringify({
gracefully,
lastTask,
})})`));
}
}, _ProcessTerminator_removeErrorListeners = function _ProcessTerminator_removeErrorListeners(proc) {
// Remove error listeners to prevent EPIPE errors during termination
// See https://github.com/nodejs/node/issues/26828
for (const stream of [proc, proc.stdin, proc.stdout, proc.stderr]) {
stream === null || stream === void 0 ? void 0 : stream.removeAllListeners("error");
}
}, _ProcessTerminator_sendExitCommand = function _ProcessTerminator_sendExitCommand(proc) {
var _a;
if (((_a = proc.stdin) === null || _a === void 0 ? void 0 : _a.writable) !== true) {
return;
}
const exitCmd = this.opts.exitCommand == null
? null
: (0, String_1.ensureSuffix)(this.opts.exitCommand, "\n");
try {
proc.stdin.end(exitCmd);
}
catch {
// Ignore errors when sending exit command
}
}, _ProcessTerminator_destroyStreams = function _ProcessTerminator_destroyStreams(proc) {
// Destroy all streams to ensure cleanup
(0, Stream_1.destroy)(proc.stdin);
(0, Stream_1.destroy)(proc.stdout);
(0, Stream_1.destroy)(proc.stderr);
}, _ProcessTerminator_handleGracefulShutdown = async function _ProcessTerminator_handleGracefulShutdown(proc, gracefully, isExited, isRunning) {
if (!this.opts.cleanupChildProcs ||
!gracefully ||
this.opts.endGracefulWaitTimeMillis <= 0 ||
isExited) {
return;
}
// Wait for the exit command to take effect
await __classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_awaitNotRunning).call(this, this.opts.endGracefulWaitTimeMillis / 2, isRunning);
// If still running, send kill signal
if (isRunning() && proc.pid != null) {
proc.kill();
}
// Wait for the signal handler to work
await __classPrivateFieldGet(this, _ProcessTerminator_instances, "m", _ProcessTerminator_awaitNotRunning).call(this, this.opts.endGracefulWaitTimeMillis / 2, isRunning);
}, _ProcessTerminator_forceKillIfRunning = function _ProcessTerminator_forceKillIfRunning(proc, processName, isRunning) {
if (this.opts.cleanupChildProcs && proc.pid != null && isRunning()) {
__classPrivateFieldGet(this, _ProcessTerminator_logger, "f").call(this).warn(`${processName}.terminate(): force-killing still-running child.`);
(0, Pids_1.kill)(proc.pid, true);
}
}, _ProcessTerminator_awaitNotRunning = async function _ProcessTerminator_awaitNotRunning(timeout, isRunning) {
await (0, Async_1.until)(() => !isRunning(), timeout);
};
//# sourceMappingURL=ProcessTerminator.js.map
;