async-multi-worker
Version:
Use worker as simple asynchronous function
144 lines (140 loc) • 5 kB
JavaScript
'use strict';
require('worker_threads');
var universalWorker = require('./node/universal-worker.cjs');
require('crypto');
/**
* Manages a pool of Workers, optimizing resource usage by limiting the number of non-busy workers.
* Provides methods to spawn, reuse, terminate, and clean up workers.
*
* ## Usage Example
*
* ```typescript
* // 1. Instantiate the manager with the worker script URL
* const manager = new WorkerManager(new URL('./worker.js', import.meta.url));
*
* // 2. Get an available worker (or spawn a new one)
* const worker = manager.getWorker();
*
* // 3. Mark the worker busy/free as needed
* // worker.busy = true; // when starting a task
* // worker.busy = false; // when done
*
* // 4. Terminate a specific worker
* manager.terminateWorker(worker);
*
* // 5. Cleanup all workers when done
* manager.terminateAllWorkers();
* ```
*
*/
class WorkerManager {
/**
* Creates a WorkerManager instance.
* @param workerURL URL of the worker script.
* @param maxIdleWorkers Maximum number of non-busy workers to keep alive (default: 5).
*/
constructor(workerURL, maxIdleWorkers = 5) {
this.workers = new Map();
/**
* Spawns a new Worker and adds it to the pool.
* @returns The newly created WorkerInfo instance.
*/
this.spawnWorker = () => {
const worker = new universalWorker.UniversalWorker(this.workerURL);
const workerInfo = new WorkerInfo(worker);
this.workers.set(worker, workerInfo);
return workerInfo;
};
/**
* Retrieves an available non-busy Worker from the pool, or spawns a new one if none are available.
* Also removes excess non-busy workers.
* @returns An available WorkerInfo instance.
*/
this.getWorker = () => {
for (const workerInfo of this.workers.values()) {
if (!workerInfo.busy) {
workerInfo.busy = true;
return workerInfo.worker;
}
}
const workerInfo = this.spawnWorker();
workerInfo.busy = true;
return workerInfo.worker;
};
/**
*
* @deprecated invoke idleWorker when the process is done
*
* Removes excess idle workers from the pool, keeping only up to MAX_IDLE_WORKERS.
*/
this.removeIdleWorkers = () => {
const idleWorkers = [];
for (const workerInfo of this.workers.values()) {
if (!workerInfo.busy)
idleWorkers.push(workerInfo);
}
if (idleWorkers.length <= this.MAX_IDLE_WORKERS)
return;
const excessWorkers = idleWorkers.slice(this.MAX_IDLE_WORKERS);
for (const workerInfo of excessWorkers) {
this.workers.delete(workerInfo.worker);
workerInfo.worker.terminate();
}
};
/**
* Terminates a specific Worker and removes it from the pool.
* @param worker The Worker instance to terminate.
*/
this.terminateWorker = (worker) => {
const workerInfo = this.workers.get(worker);
if (workerInfo)
this.workers.delete(worker);
worker.terminate();
};
/**
* Terminates a specific Worker and removes it from the pool when it is idle and existing workers exceed MAX_IDLE_WORKERS .
* @param worker The Worker instance to terminate.
*/
this.idleWorker = (worker) => {
const workerInfo = this.workers.get(worker);
if (!workerInfo)
return worker.terminate();
workerInfo.busy = false;
this.eliminateIfExceedingMaxIdleWorkers(workerInfo);
};
this.eliminateIfExceedingMaxIdleWorkers = (workerInfo) => {
let count = 0;
for (const info of this.workers.values()) {
if (!info.busy)
count++;
if (count > this.MAX_IDLE_WORKERS) {
this.workers.delete(workerInfo.worker);
workerInfo.worker.terminate();
break;
}
}
};
/**
* Terminates all workers and clears the pool.
*
* ! be cautious when using this method, as it will stop all ongoing tasks.
*/
this.terminateAllWorkers = () => {
for (const workerInfo of this.workers.values()) {
workerInfo.worker.terminate();
}
this.workers.clear();
};
this.workerURL = workerURL;
this.MAX_IDLE_WORKERS = maxIdleWorkers;
this.spawnWorker();
}
}
class WorkerInfo {
constructor(worker) {
this.worker = worker;
this.busy = false;
}
}
exports.WorkerInfo = WorkerInfo;
exports.WorkerManager = WorkerManager;