UNPKG

@tripod311/leg5

Version:

Zero-dependency concurrent function execution for Node.js using worker threads.

91 lines (90 loc) 3.2 kB
"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();