@jonahsnider/benchmark
Version:
A Node.js benchmarking library with support for multithreading and TurboFan optimization isolation.
76 lines • 2.98 kB
JavaScript
import assert from 'node:assert/strict';
import { once } from 'node:events';
import { Worker } from 'node:worker_threads';
import { Suite } from './suite.js';
import { ThreadWorker } from './types/index.js';
import { compatibleImport } from './utils.js';
const WORKER_PATH = new URL('thread-worker.js', import.meta.url);
/**
* Runs a {@link (Suite:class)} in a separate thread.
*/
export class Thread {
static async init(suiteFilepath) {
const suite = await compatibleImport(suiteFilepath);
assert.ok(suite instanceof Suite, new TypeError(`Expected "${suiteFilepath}" to export a Suite instance`));
return new Thread(suite, suiteFilepath);
}
name;
filepath;
#worker;
#workerOptions;
constructor(suite, suitePath) {
this.name = suite.name;
this.filepath = suitePath;
const workerData = {
suitePath,
};
this.#workerOptions = {
workerData,
};
this.#worker = this.#createWorker();
this.#worker.on('exit', this.#onExit.bind(this));
this.#worker.unref();
}
async run(abortSignal) {
const runMessage = { kind: ThreadWorker.Message.Kind.Run };
// Worker must be run before an abort signal is sent
this.#worker.postMessage(runMessage);
const onAbortListener = this.#onAbort.bind(this);
abortSignal?.addEventListener('abort', onAbortListener, { once: true });
const message = once(this.#worker, 'message');
try {
const [response] = (await message);
switch (response.kind) {
case ThreadWorker.Response.Kind.Results: {
return response.results;
}
case ThreadWorker.Response.Kind.Error: {
// Note: Structured clone algorithm has weird behavior with Error instances - see https://github.com/nodejs/help/issues/1558#issuecomment-431142715 and https://github.com/nodejs/node/issues/26692#issuecomment-658010376
throw response.error;
}
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
default: {
throw new RangeError('Unknown response kind');
}
}
}
finally {
abortSignal?.removeEventListener('abort', onAbortListener);
}
}
#createWorker() {
return new Worker(WORKER_PATH, this.#workerOptions);
}
async #onExit(code) {
// This prevents the main thread from hanging when the worker is not `unref`'d
await this.#worker.terminate();
// Create a new worker
this.#worker = this.#createWorker();
throw new Error(`Worker exited with code ${code}`);
}
#onAbort() {
const abortMessage = { kind: ThreadWorker.Message.Kind.Abort };
this.#worker.postMessage(abortMessage);
}
}
//# sourceMappingURL=thread.js.map