UNPKG

@nodesecure/tarball

Version:
127 lines 4.18 kB
// Import Node.js Dependencies import { EventEmitter } from "node:events"; import path from "node:path"; // Import Third-party Dependencies import hyperid from "hyperid"; // Import Internal Dependencies import { PooledWorker } from "./PooledWorker.class.js"; /** * O(1) amortized FIFO queue using a head-pointer to avoid * the O(n) cost of Array.shift(). */ class TaskQueue { #items = []; #head = 0; enqueue(task) { this.#items.push(task); } dequeue() { if (this.#head >= this.#items.length) { return undefined; } const item = this.#items[this.#head++]; if (this.#head > 0 && this.#head >= this.#items.length / 2) { this.#items = this.#items.slice(this.#head); this.#head = 0; } return item; } clear() { this.#items = []; this.#head = 0; } } export class NpmTarballWorkerPool extends EventEmitter { #generateTaskId = hyperid(); #workers = []; #availableWorkers = []; #processingTasks = new Map(); #waitingTasks = new TaskQueue(); #isTerminated = false; constructor(options = {}) { super(); const { workerCount = 4, workerFactory } = options; const workerPath = path.join(import.meta.dirname, "NpmTarballWorkerScript.js"); const factory = workerFactory ?? ((events) => new PooledWorker(workerPath, events)); for (let i = 0; i < workerCount; i++) { const worker = factory({ onComplete: (worker, message) => this.#onWorkerComplete(worker, message), onError: (worker, error, taskId) => this.#onWorkerError(worker, error, taskId) }); this.#workers.push(worker); this.#availableWorkers.push(worker); } } #onWorkerComplete(worker, message) { const handler = this.#processingTasks.get(message.id); if (handler) { this.#processingTasks.delete(message.id); if ("error" in message) { handler.reject(new Error(message.error)); } else { handler.resolve(message.result); } } const nextTask = this.#waitingTasks.dequeue(); if (nextTask) { worker.execute(nextTask); } else { this.#availableWorkers.push(worker); } } #onWorkerError(worker, error, taskId) { if (taskId) { const handler = this.#processingTasks.get(taskId); if (handler) { this.#processingTasks.delete(taskId); handler.reject(error); } } this.emit("error", error); const nextTask = this.#waitingTasks.dequeue(); if (nextTask) { worker.execute(nextTask); } else { this.#availableWorkers.push(worker); } } scan(task) { if (this.#isTerminated) { return Promise.reject(new Error("NpmTarballWorkerPool has been terminated")); } const fullTask = { id: this.#generateTaskId(), ...task }; const { promise, resolve, reject } = Promise.withResolvers(); this.#processingTasks.set(fullTask.id, { resolve, reject }); const availableWorker = this.#availableWorkers.pop() ?? null; if (availableWorker) { availableWorker.execute(fullTask); } else { this.#waitingTasks.enqueue(fullTask); } return promise; } async terminate() { this.#isTerminated = true; const terminationError = new Error("NpmTarballWorkerPool terminated"); for (const handler of this.#processingTasks.values()) { handler.reject(terminationError); } this.#processingTasks.clear(); this.#waitingTasks.clear(); this.#availableWorkers = []; await Promise.all(this.#workers.map((worker) => worker.terminate())); this.#workers = []; } [Symbol.asyncDispose]() { return this.terminate(); } } //# sourceMappingURL=NpmTarballWorkerPool.class.js.map