generated from ztimson/template
51 lines
1.7 KiB
JavaScript
51 lines
1.7 KiB
JavaScript
'use strict';
|
|
|
|
import {Worker} from 'node:worker_threads';
|
|
import path from 'node:path';
|
|
import os from 'node:os';
|
|
import {fileURLToPath} from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
/** Round-robin pool of worker threads for off-main-thread cluster decompression. */
|
|
class DecompressPool {
|
|
#workers = [];
|
|
#next = 0;
|
|
#pending = new Map(); // id -> {resolve, reject}
|
|
#nextId = 0;
|
|
#active = 0;
|
|
|
|
constructor(size = Math.max(1, os.cpus().length - 1)) {
|
|
for (let i = 0; i < size; i++) this.#spawn();
|
|
}
|
|
|
|
#spawn() {
|
|
const worker = new Worker(path.join(__dirname, 'decompress-worker.js'));
|
|
worker.on('message', ({id, data, error}) => {
|
|
const task = this.#pending.get(id);
|
|
if (!task) return;
|
|
this.#pending.delete(id);
|
|
// No work left in flight -> safe to let the process exit again.
|
|
if (--this.#active === 0) for (const w of this.#workers) w.unref();
|
|
error ? task.reject(new Error(error)) : task.resolve(Buffer.from(data));
|
|
});
|
|
worker.unref(); // idle workers must never keep a short script/server alive
|
|
this.#workers.push(worker);
|
|
}
|
|
|
|
/** Decompresses `{compType, body}` on the next worker in rotation. `body` must be a Uint8Array/Buffer view. */
|
|
run({compType, body}) {
|
|
const worker = this.#workers[this.#next];
|
|
this.#next = (this.#next + 1) % this.#workers.length;
|
|
const id = this.#nextId++;
|
|
const owned = new Uint8Array(body);
|
|
if (this.#active++ === 0) for (const w of this.#workers) w.ref();
|
|
return new Promise((resolve, reject) => {
|
|
this.#pending.set(id, {resolve, reject});
|
|
worker.postMessage({id, compType, body: owned}, [owned.buffer]);
|
|
});
|
|
}
|
|
}
|
|
|
|
export const decompressPool = new DecompressPool();
|