node-apparatus
Version:
A mix of common components needed for awesome node experience
121 lines • 6.2 kB
JavaScript
;
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