@tripod311/leg5
Version:
Zero-dependency concurrent function execution for Node.js using worker threads.
91 lines (90 loc) • 3.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const worker_threads_1 = require("worker_threads");
const tools_1 = require("./tools");
const abortContext_1 = __importDefault(require("./abortContext"));
const CheckDeadTimeout = 1000;
class Executor {
constructor() {
this.task_cache = new Map();
this.terminated = false;
this.abortContext = new abortContext_1.default();
worker_threads_1.parentPort?.on("message", this.handleMessage.bind(this));
setTimeout(this.loop.bind(this), CheckDeadTimeout);
}
handleMessage(raw) {
const message = raw;
switch (message.command) {
case "terminate":
this.terminated = true;
break;
case "execute":
this.execute(message.name, message.args, message.timeout, message.script, message.argsList);
break;
case "abort":
this.abortContext.controller.abort(new Error("Aborted"));
break;
}
}
compile(name, script, argsList) {
if (!this.task_cache.has(name) || script !== undefined) {
this.task_cache.set(name, {
fn: eval(`(async function (AbortContext, ${argsList.join(',')}) {
${script}
})`),
argsList: argsList
});
}
return this.task_cache.get(name);
}
serialize_error(e) {
if (e instanceof Error) {
return { message: e.message, stack: e.stack, name: e.name };
}
return { message: String(e) };
}
async execute(name, args, timeout, script, argsList) {
try {
const cached = this.compile(name, script, argsList);
if (timeout > 0) {
this.timeout = setTimeout(() => {
this.abortContext.controller.abort(new Error("Timeout"));
}, timeout);
}
const orderedArgs = cached.argsList.map(argName => {
return args[argName];
});
const result = await cached.fn(this.abortContext, ...orderedArgs);
if (this.abortContext.controller.signal.aborted) {
throw this.abortContext.controller.signal.reason ?? new Error("Aborted");
}
if (this.terminated)
return;
const transferlist = (0, tools_1.get_transferlist)(result);
worker_threads_1.parentPort?.postMessage({
command: "finished",
payload: result
}, transferlist);
}
catch (e) {
if (this.terminated)
return;
worker_threads_1.parentPort?.postMessage({
command: "failed",
error: this.serialize_error(e)
});
}
finally {
this.abortContext.reset();
}
}
loop() {
if (!this.terminated) {
setTimeout(this.loop.bind(this), CheckDeadTimeout);
}
}
}
new Executor();