batch-cluster
Version:
Manage a cluster of child processes
305 lines • 17.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _BatchCluster_instances, _BatchCluster_logger, _BatchCluster_processPool, _BatchCluster_taskQueue, _BatchCluster_eventCoordinator, _BatchCluster_onIdleRequested, _BatchCluster_onIdleInterval, _BatchCluster_endPromise, _BatchCluster_beforeExitListener, _BatchCluster_exitListener, _BatchCluster_onIdleLater, _BatchCluster_onIdle, _BatchCluster_execNextTask, _BatchCluster_maybeSpawnProcs;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BatchCluster = exports.Task = exports.Rate = exports.ProcpsMissingError = exports.pids = exports.pidExists = exports.kill = exports.SimpleParser = exports.Deferred = exports.BatchProcess = exports.BatchClusterOptions = void 0;
const node_events_1 = __importDefault(require("node:events"));
const node_process_1 = __importDefault(require("node:process"));
const node_timers_1 = __importDefault(require("node:timers"));
const Deferred_1 = require("./Deferred");
const OptionsVerifier_1 = require("./OptionsVerifier");
const BatchClusterEventCoordinator_1 = require("./BatchClusterEventCoordinator");
const ProcessPoolManager_1 = require("./ProcessPoolManager");
const ProcpsChecker_1 = require("./ProcpsChecker");
const TaskQueueManager_1 = require("./TaskQueueManager");
var BatchClusterOptions_1 = require("./BatchClusterOptions");
Object.defineProperty(exports, "BatchClusterOptions", { enumerable: true, get: function () { return BatchClusterOptions_1.BatchClusterOptions; } });
var BatchProcess_1 = require("./BatchProcess");
Object.defineProperty(exports, "BatchProcess", { enumerable: true, get: function () { return BatchProcess_1.BatchProcess; } });
var Deferred_2 = require("./Deferred");
Object.defineProperty(exports, "Deferred", { enumerable: true, get: function () { return Deferred_2.Deferred; } });
__exportStar(require("./Logger"), exports);
var Parser_1 = require("./Parser");
Object.defineProperty(exports, "SimpleParser", { enumerable: true, get: function () { return Parser_1.SimpleParser; } });
var Pids_1 = require("./Pids");
Object.defineProperty(exports, "kill", { enumerable: true, get: function () { return Pids_1.kill; } });
Object.defineProperty(exports, "pidExists", { enumerable: true, get: function () { return Pids_1.pidExists; } });
Object.defineProperty(exports, "pids", { enumerable: true, get: function () { return Pids_1.pids; } });
var ProcpsChecker_2 = require("./ProcpsChecker");
Object.defineProperty(exports, "ProcpsMissingError", { enumerable: true, get: function () { return ProcpsChecker_2.ProcpsMissingError; } });
var Rate_1 = require("./Rate");
Object.defineProperty(exports, "Rate", { enumerable: true, get: function () { return Rate_1.Rate; } });
var Task_1 = require("./Task");
Object.defineProperty(exports, "Task", { enumerable: true, get: function () { return Task_1.Task; } });
/**
* BatchCluster instances manage 0 or more homogeneous child processes, and
* provide the main interface for enqueuing `Task`s via `enqueueTask`.
*
* Given the large number of configuration options, the constructor
* receives a single options hash. The most important of these are the
* `ChildProcessFactory`, which specifies the factory that creates
* ChildProcess instances, and `BatchProcessOptions`, which specifies how
* child tasks can be verified and shut down.
*/
class BatchCluster {
constructor(opts) {
_BatchCluster_instances.add(this);
_BatchCluster_logger.set(this, void 0);
_BatchCluster_processPool.set(this, void 0);
_BatchCluster_taskQueue.set(this, void 0);
_BatchCluster_eventCoordinator.set(this, void 0);
_BatchCluster_onIdleRequested.set(this, false);
_BatchCluster_onIdleInterval.set(this, void 0);
_BatchCluster_endPromise.set(this, void 0);
this.emitter = new node_events_1.default.EventEmitter();
/**
* @see BatchClusterEvents
*/
this.on = this.emitter.on.bind(this.emitter);
/**
* @see BatchClusterEvents
* @since v9.0.0
*/
this.off = this.emitter.off.bind(this.emitter);
_BatchCluster_beforeExitListener.set(this, () => {
void this.end(true);
});
_BatchCluster_exitListener.set(this, () => {
void this.end(false);
});
_BatchCluster_onIdleLater.set(this, () => {
if (!__classPrivateFieldGet(this, _BatchCluster_onIdleRequested, "f")) {
__classPrivateFieldSet(this, _BatchCluster_onIdleRequested, true, "f");
node_timers_1.default.setTimeout(() => __classPrivateFieldGet(this, _BatchCluster_instances, "m", _BatchCluster_onIdle).call(this), 1);
}
}
// NOT ASYNC: updates internal state:
);
// Validate that required process listing commands are available
(0, ProcpsChecker_1.validateProcpsAvailable)();
this.options = (0, OptionsVerifier_1.verifyOptions)({ ...opts, observer: this.emitter });
__classPrivateFieldSet(this, _BatchCluster_logger, this.options.logger, "f");
// Initialize the managers
__classPrivateFieldSet(this, _BatchCluster_processPool, new ProcessPoolManager_1.ProcessPoolManager(this.options, this.emitter, () => __classPrivateFieldGet(this, _BatchCluster_onIdleLater, "f").call(this)), "f");
__classPrivateFieldSet(this, _BatchCluster_taskQueue, new TaskQueueManager_1.TaskQueueManager(__classPrivateFieldGet(this, _BatchCluster_logger, "f"), this.emitter), "f");
// Initialize event coordinator to handle all event processing
__classPrivateFieldSet(this, _BatchCluster_eventCoordinator, new BatchClusterEventCoordinator_1.BatchClusterEventCoordinator(this.emitter, {
streamFlushMillis: this.options.streamFlushMillis,
maxReasonableProcessFailuresPerMinute: this.options.maxReasonableProcessFailuresPerMinute,
logger: __classPrivateFieldGet(this, _BatchCluster_logger, "f"),
}, () => __classPrivateFieldGet(this, _BatchCluster_onIdleLater, "f").call(this), () => void this.end()), "f");
if (this.options.onIdleIntervalMillis > 0) {
__classPrivateFieldSet(this, _BatchCluster_onIdleInterval, node_timers_1.default.setInterval(() => __classPrivateFieldGet(this, _BatchCluster_onIdleLater, "f").call(this), this.options.onIdleIntervalMillis), "f");
__classPrivateFieldGet(this, _BatchCluster_onIdleInterval, "f").unref(); // < don't prevent node from exiting
}
__classPrivateFieldSet(this, _BatchCluster_logger, this.options.logger, "f");
node_process_1.default.once("beforeExit", __classPrivateFieldGet(this, _BatchCluster_beforeExitListener, "f"));
node_process_1.default.once("exit", __classPrivateFieldGet(this, _BatchCluster_exitListener, "f"));
}
get ended() {
return __classPrivateFieldGet(this, _BatchCluster_endPromise, "f") != null;
}
/**
* Shut down this instance, and all child processes.
* @param gracefully should an attempt be made to finish in-flight tasks, or
* should we force-kill child PIDs.
*/
// NOT ASYNC so state transition happens immediately
end(gracefully = true) {
__classPrivateFieldGet(this, _BatchCluster_logger, "f").call(this).info("BatchCluster.end()", { gracefully });
if (__classPrivateFieldGet(this, _BatchCluster_endPromise, "f") == null) {
this.emitter.emit("beforeEnd");
if (__classPrivateFieldGet(this, _BatchCluster_onIdleInterval, "f") != null)
node_timers_1.default.clearInterval(__classPrivateFieldGet(this, _BatchCluster_onIdleInterval, "f"));
__classPrivateFieldSet(this, _BatchCluster_onIdleInterval, undefined, "f");
node_process_1.default.removeListener("beforeExit", __classPrivateFieldGet(this, _BatchCluster_beforeExitListener, "f"));
node_process_1.default.removeListener("exit", __classPrivateFieldGet(this, _BatchCluster_exitListener, "f"));
__classPrivateFieldSet(this, _BatchCluster_endPromise, new Deferred_1.Deferred().observe(this.closeChildProcesses(gracefully).then(() => {
this.emitter.emit("end");
})), "f");
}
return __classPrivateFieldGet(this, _BatchCluster_endPromise, "f");
}
/**
* Submits `task` for processing by a `BatchProcess` instance
*
* @return a Promise that is resolved or rejected once the task has been
* attempted on an idle BatchProcess
*/
enqueueTask(task) {
if (this.ended) {
task.reject(new Error("BatchCluster has ended, cannot enqueue " + task.command));
}
__classPrivateFieldGet(this, _BatchCluster_taskQueue, "f").enqueue(task);
// Run #onIdle now (not later), to make sure the task gets enqueued asap if
// possible
__classPrivateFieldGet(this, _BatchCluster_onIdleLater, "f").call(this);
// (BatchProcess will call our #onIdleLater when tasks settle or when they
// exit)
return task.promise;
}
/**
* @return true if all previously-enqueued tasks have settled
*/
get isIdle() {
return this.pendingTaskCount === 0 && this.busyProcCount === 0;
}
/**
* @return the number of pending tasks
*/
get pendingTaskCount() {
return __classPrivateFieldGet(this, _BatchCluster_taskQueue, "f").pendingTaskCount;
}
/**
* @returns {number} the mean number of tasks completed by child processes
*/
get meanTasksPerProc() {
return __classPrivateFieldGet(this, _BatchCluster_eventCoordinator, "f").meanTasksPerProc;
}
/**
* @return the total number of child processes created by this instance
*/
get spawnedProcCount() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").spawnedProcCount;
}
/**
* @return the current number of spawned child processes. Some (or all) may be idle.
*/
get procCount() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").processCount;
}
/**
* @return the current number of child processes currently servicing tasks
*/
get busyProcCount() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").busyProcCount;
}
get startingProcCount() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").startingProcCount;
}
/**
* @return the current pending Tasks (mostly for testing)
*/
get pendingTasks() {
return __classPrivateFieldGet(this, _BatchCluster_taskQueue, "f").pendingTasks;
}
/**
* @return the current running Tasks (mostly for testing)
*/
get currentTasks() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").currentTasks();
}
/**
* For integration tests:
*/
get internalErrorCount() {
return __classPrivateFieldGet(this, _BatchCluster_eventCoordinator, "f").internalErrorCount;
}
/**
* Verify that each BatchProcess PID is actually alive.
*
* @return the spawned PIDs that are still in the process table.
*/
pids() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").pids();
}
/**
* For diagnostics. Contents may change.
*/
stats() {
var _a;
return {
pendingTaskCount: this.pendingTaskCount,
currentProcCount: this.procCount,
readyProcCount: __classPrivateFieldGet(this, _BatchCluster_processPool, "f").readyProcCount,
maxProcCount: this.options.maxProcs,
internalErrorCount: __classPrivateFieldGet(this, _BatchCluster_eventCoordinator, "f").internalErrorCount,
startErrorRatePerMinute: __classPrivateFieldGet(this, _BatchCluster_eventCoordinator, "f").startErrorRatePerMinute,
msBeforeNextSpawn: __classPrivateFieldGet(this, _BatchCluster_processPool, "f").msBeforeNextSpawn,
spawnedProcCount: this.spawnedProcCount,
childEndCounts: this.childEndCounts,
ending: __classPrivateFieldGet(this, _BatchCluster_endPromise, "f") != null,
ended: false === ((_a = __classPrivateFieldGet(this, _BatchCluster_endPromise, "f")) === null || _a === void 0 ? void 0 : _a.pending),
};
}
/**
* Get ended process counts (used for tests)
*/
countEndedChildProcs(why) {
return __classPrivateFieldGet(this, _BatchCluster_eventCoordinator, "f").countEndedChildProcs(why);
}
get childEndCounts() {
return __classPrivateFieldGet(this, _BatchCluster_eventCoordinator, "f").childEndCounts;
}
/**
* Shut down any currently-running child processes. New child processes will
* be started automatically to handle new tasks.
*/
async closeChildProcesses(gracefully = true) {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").closeChildProcesses(gracefully);
}
/**
* Reset the maximum number of active child processes to `maxProcs`. Note that
* this is handled gracefully: child processes are only reduced as tasks are
* completed.
*/
setMaxProcs(maxProcs) {
__classPrivateFieldGet(this, _BatchCluster_processPool, "f").setMaxProcs(maxProcs);
// we may now be able to handle an enqueued task. Vacuum pids and see:
__classPrivateFieldGet(this, _BatchCluster_onIdleLater, "f").call(this);
}
/**
* Run maintenance on currently spawned child processes. This method is
* normally invoked automatically as tasks are enqueued and processed.
*
* Only public for tests.
*/
// NOT ASYNC: updates internal state. only exported for tests.
vacuumProcs() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").vacuumProcs();
}
}
exports.BatchCluster = BatchCluster;
_BatchCluster_logger = new WeakMap(), _BatchCluster_processPool = new WeakMap(), _BatchCluster_taskQueue = new WeakMap(), _BatchCluster_eventCoordinator = new WeakMap(), _BatchCluster_onIdleRequested = new WeakMap(), _BatchCluster_onIdleInterval = new WeakMap(), _BatchCluster_endPromise = new WeakMap(), _BatchCluster_beforeExitListener = new WeakMap(), _BatchCluster_exitListener = new WeakMap(), _BatchCluster_onIdleLater = new WeakMap(), _BatchCluster_instances = new WeakSet(), _BatchCluster_onIdle = function _BatchCluster_onIdle() {
__classPrivateFieldSet(this, _BatchCluster_onIdleRequested, false, "f");
void this.vacuumProcs();
while (__classPrivateFieldGet(this, _BatchCluster_instances, "m", _BatchCluster_execNextTask).call(this)) {
//
}
void __classPrivateFieldGet(this, _BatchCluster_instances, "m", _BatchCluster_maybeSpawnProcs).call(this);
}, _BatchCluster_execNextTask = function _BatchCluster_execNextTask(retries = 1) {
if (this.ended)
return false;
const readyProc = __classPrivateFieldGet(this, _BatchCluster_processPool, "f").findReadyProcess();
return __classPrivateFieldGet(this, _BatchCluster_taskQueue, "f").tryAssignNextTask(readyProc, retries);
}, _BatchCluster_maybeSpawnProcs = async function _BatchCluster_maybeSpawnProcs() {
return __classPrivateFieldGet(this, _BatchCluster_processPool, "f").maybeSpawnProcs(__classPrivateFieldGet(this, _BatchCluster_taskQueue, "f").pendingTaskCount, this.ended);
};
//# sourceMappingURL=BatchCluster.js.map