UNPKG

node-apparatus

Version:

A mix of common components needed for awesome node experience

121 lines 6.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StatefulProxyManager = void 0; const node_os_1 = require("node:os"); const i_proxy_method_js_1 = require("./i-proxy-method.js"); const node_worker_threads_1 = require("node:worker_threads"); const sequential_invocation_queue_js_1 = require("../sequential-invocation-queue/sequential-invocation-queue.js"); const spin_wait_lock_js_1 = require("../spin-wait-lock/spin-wait-lock.js"); /** * Manages a pool of stateful worker threads to handle method invocations. * * This class is responsible for initializing worker threads, invoking methods on them, * and managing their lifecycle. It supports both local and remote method invocations. * * @class StatefulProxyManager */ class StatefulProxyManager { /** * Creates an instance of StatefulProxyManager. * * @param {number} workerCount - The number of worker threads to create, Pass zero to run in main thread. * @param {string} workerFilePath - The path to the worker script file which has exported instance of StatefulRecipient. * @param {number} [invocationQueueSizePerWorker=Number.MAX_SAFE_INTEGER] - The maximum number of method invocations to queue per worker. * @param {number} [spinWaitTime=100] - The time to wait between attempts to acquire the lock in milliseconds. */ constructor(workerCount, workerFilePath, invocationQueueSizePerWorker = Number.MAX_SAFE_INTEGER, spinWaitTime = 100) { this.workerCount = workerCount; this.workerFilePath = workerFilePath; this.invocationQueueSizePerWorker = invocationQueueSizePerWorker; this.spinWaitTime = spinWaitTime; this.workerSequentialQueue = new Map(); this.workers = new Array(); } /** * Initializes the worker threads, must be called before invoking any methods. */ async initialize() { const parsedWorkerCount = Math.min((0, node_os_1.cpus)().length, Math.max(0, this.workerCount)); for (let index = 0; index < parsedWorkerCount; index++) { //TODO:Have Handshake with threads that they are ready to accept commands Ping-Pong. this.workers.push(new node_worker_threads_1.Worker(this.workerFilePath, { workerData: null })); this.workerSequentialQueue.set(index, new sequential_invocation_queue_js_1.SequentialInvocationQueue(new spin_wait_lock_js_1.SpinWaitLock(), this.invokeRemoteMethod.bind(this), this.invocationQueueSizePerWorker)); } if (this.workers.length === 0) { const module = await import(`file://${this.workerFilePath}`); this.selfWorker = module.default; this.WorkerCount = 1; } else { this.WorkerCount = this.workers.length; } } /** * Invokes a method on a worker thread. * * @template T - The return type of the method being invoked. * @param {string} methodName - The name of the method to invoke. * @param {any[]} methodArguments - The arguments to pass to the method. * @param {number} [workerIndex=0] - The index of the worker thread to invoke the method on. * @param {number} [methodInvocationId=Number.NaN] - The unique identifier for the method invocation. * @returns {Promise<T>} - A promise that resolves with the return value of the method * or rejects with an error if the method invocation fails. */ async invokeMethod(methodName, methodArguments = [], workerIndex = 0, methodInvocationId = Number.NaN) { const workerIdx = Math.max(0, Math.min(workerIndex, this.workers.length - 1)); if (workerIdx === 0 && this.selfWorker !== undefined) { return await this.selfWorker[methodName](...methodArguments); } else { const q = this.workerSequentialQueue.get(workerIdx); const result = await q.invoke([methodName, methodArguments, workerIdx, methodInvocationId], this.spinWaitTime); if (result.state === "success") { return result.result; } else if (result.state === "error:queue-full") { throw new Error(`Worker ${workerIdx} reported:${result.state}`); } else { return undefined; } } } async invokeRemoteMethod(methodParameters) { const [methodName, methodArguments, workerIndex, methodInvocationId = Number.NaN] = methodParameters; return await new Promise((resolve, reject) => { const worker = this.workers[workerIndex]; const workerErrorHandler = (error) => { worker.off('message', workerMessageHandler); reject(error); }; const workerMessageHandler = (message) => { worker.off('error', workerErrorHandler); const returnValue = (0, i_proxy_method_js_1.deserialize)(message); if (returnValue.error !== undefined) { reject(new Error(returnValue.error)); return; } resolve(returnValue.returnValue); }; worker.once('error', workerErrorHandler); worker.once('message', workerMessageHandler); const methodInvocationPayload = { workerId: workerIndex, invocationId: methodInvocationId, methodName, methodArguments, returnValue: null }; worker.postMessage((0, i_proxy_method_js_1.serialize)(methodInvocationPayload)); }); } async [Symbol.asyncDispose]() { if (this.selfWorker !== undefined) { await this.selfWorker[Symbol.asyncDispose](); } for (const [workerIndex, q] of this.workerSequentialQueue.entries()) { await q[Symbol.asyncDispose](); } for (const worker of this.workers) { worker.postMessage((0, i_proxy_method_js_1.serialize)(i_proxy_method_js_1.DisposeMethodPayload)); } this.workers.length = 0; this.workerSequentialQueue.clear(); } } exports.StatefulProxyManager = StatefulProxyManager; //# sourceMappingURL=stateful-proxy-manager.js.map