UNPKG

batch-cluster

Version:
305 lines 17.3 kB
"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