UNPKG

@xylabs/threads

Version:

Web workers & worker threads as simple as a function call

1,026 lines (1,008 loc) 34.7 kB
var __getOwnPropNames = Object.getOwnPropertyNames; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __commonJS = (cb, mod) => function __require2() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; // ../../node_modules/.store/tiny-worker-npm-2.3.0-38c7100e1d/package/lib/index.js var require_lib = __commonJS({ "../../node_modules/.store/tiny-worker-npm-2.3.0-38c7100e1d/package/lib/index.js"(exports, module) { "use strict"; var _createClass = /* @__PURE__ */ (function() { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var path2 = __require("path"); var fork = __require("child_process").fork; var worker = path2.join(__dirname, "worker.js"); var events = /^(error|message)$/; var defaultPorts = { inspect: 9229, debug: 5858 }; var range = { min: 1, max: 300 }; var Worker3 = (function() { function Worker4(arg) { var _this = this; var args = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : []; var options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : { cwd: process.cwd() }; _classCallCheck(this, Worker4); var isfn = typeof arg === "function", input = isfn ? arg.toString() : arg; if (!options.cwd) { options.cwd = process.cwd(); } var debugVars = process.execArgv.filter(function(execArg) { return /(debug|inspect)/.test(execArg); }); if (debugVars.length > 0 && !options.noDebugRedirection) { if (!options.execArgv) { debugVars = Array.from(process.execArgv); options.execArgv = []; } var inspectIndex = debugVars.findIndex(function(debugArg) { return /^--inspect(-brk)?(=\d+)?$/.test(debugArg); }); var debugIndex = debugVars.findIndex(function(debugArg) { return /^--debug(-brk)?(=\d+)?$/.test(debugArg); }); var portIndex = inspectIndex >= 0 ? inspectIndex : debugIndex; if (portIndex >= 0) { var match = /^--(debug|inspect)(?:-brk)?(?:=(\d+))?$/.exec(debugVars[portIndex]); var port = defaultPorts[match[1]]; if (match[2]) { port = parseInt(match[2]); } debugVars[portIndex] = "--" + match[1] + "=" + (port + range.min + Math.floor(Math.random() * (range.max - range.min))); if (debugIndex >= 0 && debugIndex !== portIndex) { match = /^(--debug)(?:-brk)?(.*)/.exec(debugVars[debugIndex]); debugVars[debugIndex] = match[1] + (match[2] ? match[2] : ""); } } options.execArgv = options.execArgv.concat(debugVars); } delete options.noDebugRedirection; this.child = fork(worker, args, options); this.onerror = void 0; this.onmessage = void 0; this.child.on("error", function(e) { if (_this.onerror) { _this.onerror.call(_this, e); } }); this.child.on("message", function(msg) { var message = JSON.parse(msg); var error = void 0; if (!message.error && _this.onmessage) { _this.onmessage.call(_this, message); } if (message.error && _this.onerror) { error = new Error(message.error); error.stack = message.stack; _this.onerror.call(_this, error); } }); this.child.send({ input, isfn, cwd: options.cwd, esm: options.esm }); } _createClass(Worker4, [{ key: "addEventListener", value: function addEventListener(event, fn) { if (events.test(event)) { this["on" + event] = fn; } } }, { key: "postMessage", value: function postMessage(msg) { this.child.send(JSON.stringify({ data: msg }, null, 0)); } }, { key: "terminate", value: function terminate() { this.child.kill("SIGINT"); } }], [{ key: "setRange", value: function setRange(min, max) { if (min >= max) { return false; } range.min = min; range.max = max; return true; } }]); return Worker4; })(); module.exports = Worker3; } }); // src/serializers.ts function extendSerializer(extend, implementation2) { const fallbackDeserializer = extend.deserialize.bind(extend); const fallbackSerializer = extend.serialize.bind(extend); return { deserialize(message) { return implementation2.deserialize(message, fallbackDeserializer); }, serialize(input) { return implementation2.serialize(input, fallbackSerializer); } }; } var DefaultErrorSerializer = { deserialize(message) { return Object.assign(new Error(message.message), { name: message.name, stack: message.stack }); }, serialize(error) { return { __error_marker: "$$error", message: error.message, name: error.name, stack: error.stack }; } }; var isSerializedError = (thing) => thing && typeof thing === "object" && "__error_marker" in thing && thing.__error_marker === "$$error"; var DefaultSerializer = { deserialize(message) { return isSerializedError(message) ? DefaultErrorSerializer.deserialize(message) : message; }, serialize(input) { return input instanceof Error ? DefaultErrorSerializer.serialize(input) : input; } }; // src/common.ts globalThis.registeredSerializer = globalThis.registeredSerializer ?? DefaultSerializer; function registerSerializer(serializer) { globalThis.registeredSerializer = extendSerializer(globalThis.registeredSerializer, serializer); } function deserialize(message) { return globalThis.registeredSerializer.deserialize(message); } function serialize(input) { return globalThis.registeredSerializer.serialize(input); } // src/master/implementation.node.ts import { EventEmitter } from "events"; import { cpus } from "os"; import path from "path"; import { cwd } from "process"; import { Worker as NativeWorker } from "worker_threads"; var defaultPoolSize = cpus().length; function resolveScriptPath(scriptPath, baseURL) { const makeAbsolute = (filePath) => { return path.isAbsolute(filePath) ? filePath : path.join(baseURL ?? cwd(), filePath); }; const absolutePath = makeAbsolute(scriptPath); return absolutePath; } function initWorkerThreadsWorker() { let allWorkers = []; class Worker3 extends NativeWorker { mappedEventListeners; constructor(scriptPath, options) { const resolvedScriptPath = options && options.fromSource ? null : resolveScriptPath(scriptPath, (options ?? {})._baseURL); if (resolvedScriptPath) { super(resolvedScriptPath, options); } else { const sourceCode = scriptPath; super(sourceCode, { ...options, eval: true }); } this.mappedEventListeners = /* @__PURE__ */ new WeakMap(); allWorkers.push(this); } addEventListener(eventName, rawListener) { const listener = (message) => { rawListener({ data: message }); }; this.mappedEventListeners.set(rawListener, listener); this.on(eventName, listener); } removeEventListener(eventName, rawListener) { const listener = this.mappedEventListeners.get(rawListener) || rawListener; this.off(eventName, listener); } } const terminateWorkersAndMaster = () => { Promise.all(allWorkers.map((worker) => worker.terminate())).then( () => process.exit(0), () => process.exit(1) ); allWorkers = []; }; process.on("SIGINT", () => terminateWorkersAndMaster()); process.on("SIGTERM", () => terminateWorkersAndMaster()); class BlobWorker2 extends Worker3 { constructor(blob, options) { super(Buffer.from(blob).toString("utf-8"), { ...options, fromSource: true }); } static fromText(source, options) { return new Worker3(source, { ...options, fromSource: true }); } } return { blob: BlobWorker2, default: Worker3 }; } function initTinyWorker() { const TinyWorker = require_lib(); let allWorkers = []; class Worker3 extends TinyWorker { emitter; constructor(scriptPath, options) { const resolvedScriptPath = options && options.fromSource ? null : process.platform === "win32" ? `file:///${resolveScriptPath(scriptPath).replaceAll("\\", "/")}` : resolveScriptPath(scriptPath); if (resolvedScriptPath) { super(resolvedScriptPath, [], { esm: true }); } else { const sourceCode = scriptPath; super(new Function(sourceCode), [], { esm: true }); } allWorkers.push(this); this.emitter = new EventEmitter(); this.onerror = (error) => this.emitter.emit("error", error); this.onmessage = (message) => this.emitter.emit("message", message); } addEventListener(eventName, listener) { this.emitter.addListener(eventName, listener); } removeEventListener(eventName, listener) { this.emitter.removeListener(eventName, listener); } terminate() { allWorkers = allWorkers.filter((worker) => worker !== this); return super.terminate(); } } const terminateWorkersAndMaster = () => { Promise.all(allWorkers.map((worker) => worker.terminate())).then( () => process.exit(0), () => process.exit(1) ); allWorkers = []; }; process.on("SIGINT", () => terminateWorkersAndMaster()); process.on("SIGTERM", () => terminateWorkersAndMaster()); class BlobWorker2 extends Worker3 { constructor(blob, options) { super(Buffer.from(blob).toString("utf-8"), { ...options, fromSource: true }); } static fromText(source, options) { return new Worker3(source, { ...options, fromSource: true }); } } return { blob: BlobWorker2, default: Worker3 }; } var implementation; var isTinyWorker; function selectWorkerImplementation() { try { isTinyWorker = false; return initWorkerThreadsWorker(); } catch (ex) { console.error(ex); console.debug("Node worker_threads not available. Trying to fall back to tiny-worker polyfill..."); isTinyWorker = true; return initTinyWorker(); } } function getWorkerImplementation() { if (!implementation) { implementation = selectWorkerImplementation(); } return implementation; } function isWorkerRuntime() { if (isTinyWorker) { return globalThis !== void 0 && self["postMessage"] ? true : false; } else { const isMainThread = typeof __non_webpack_require__ === "function" ? __non_webpack_require__("worker_threads").isMainThread : eval("require")("worker_threads").isMainThread; return !isMainThread; } } // src/master/pool-browser.ts import DebugLogger from "debug"; import { multicast, Observable, Subject } from "observable-fns"; // src/master/implementation.browser.ts var defaultPoolSize2 = typeof navigator !== "undefined" && navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 4; // src/master/pool-types.ts var PoolEventType = /* @__PURE__ */ ((PoolEventType2) => { PoolEventType2["initialized"] = "initialized"; PoolEventType2["taskCanceled"] = "taskCanceled"; PoolEventType2["taskCompleted"] = "taskCompleted"; PoolEventType2["taskFailed"] = "taskFailed"; PoolEventType2["taskQueued"] = "taskQueued"; PoolEventType2["taskQueueDrained"] = "taskQueueDrained"; PoolEventType2["taskStart"] = "taskStart"; PoolEventType2["terminated"] = "terminated"; return PoolEventType2; })(PoolEventType || {}); // src/symbols.ts var $errors = /* @__PURE__ */ Symbol("thread.errors"); var $events = /* @__PURE__ */ Symbol("thread.events"); var $terminate = /* @__PURE__ */ Symbol("thread.terminate"); var $transferable = /* @__PURE__ */ Symbol("thread.transferable"); var $worker = /* @__PURE__ */ Symbol("thread.worker"); // src/master/thread.ts function fail(message) { throw new Error(message); } var Thread = { /** Return an observable that can be used to subscribe to all errors happening in the thread. */ errors(thread) { return thread[$errors] || fail("Error observable not found. Make sure to pass a thread instance as returned by the spawn() promise."); }, /** Return an observable that can be used to subscribe to internal events happening in the thread. Useful for debugging. */ events(thread) { return thread[$events] || fail("Events observable not found. Make sure to pass a thread instance as returned by the spawn() promise."); }, /** Terminate a thread. Remember to terminate every thread when you are done using it. */ terminate(thread) { return thread[$terminate](); } }; // src/master/pool-browser.ts var nextPoolID = 1; function createArray(size) { const array = []; for (let index = 0; index < size; index++) { array.push(index); } return array; } function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function flatMap(array, mapper) { return array.reduce((flattened, element) => [...flattened, ...mapper(element)], []); } function slugify(text) { return text.replaceAll(/\W/g, " ").trim().replaceAll(/\s+/g, "-"); } function spawnWorkers(spawnWorker, count) { return createArray(count).map( () => ({ init: spawnWorker(), runningTasks: [] }) ); } var WorkerPool = class { static EventType = PoolEventType; debug; eventObservable; options; workers; eventSubject = new Subject(); initErrors = []; isClosing = false; nextTaskID = 1; taskQueue = []; constructor(spawnWorker, optionsOrSize) { const options = typeof optionsOrSize === "number" ? { size: optionsOrSize } : optionsOrSize || {}; const { size = defaultPoolSize2 } = options; this.debug = DebugLogger(`threads:pool:${slugify(options.name || String(nextPoolID++))}`); this.options = options; this.workers = spawnWorkers(spawnWorker, size); this.eventObservable = multicast(Observable.from(this.eventSubject)); Promise.all(this.workers.map((worker) => worker.init)).then( () => this.eventSubject.next({ size: this.workers.length, type: "initialized" /* initialized */ }), (error) => { this.debug("Error while initializing pool worker:", error); this.eventSubject.error(error); this.initErrors.push(error); } ); } findIdlingWorker() { const { concurrency = 1 } = this.options; return this.workers.find((worker) => worker.runningTasks.length < concurrency); } async runPoolTask(worker, task) { const workerID = this.workers.indexOf(worker) + 1; this.debug(`Running task #${task.id} on worker #${workerID}...`); this.eventSubject.next({ taskID: task.id, type: "taskStart" /* taskStart */, workerID }); try { const returnValue = await task.run(await worker.init); this.debug(`Task #${task.id} completed successfully`); this.eventSubject.next({ returnValue, taskID: task.id, type: "taskCompleted" /* taskCompleted */, workerID }); } catch (ex) { const error = ex; this.debug(`Task #${task.id} failed`); this.eventSubject.next({ error, taskID: task.id, type: "taskFailed" /* taskFailed */, workerID }); } } run(worker, task) { const runPromise = (async () => { const removeTaskFromWorkersRunningTasks = () => { worker.runningTasks = worker.runningTasks.filter((someRunPromise) => someRunPromise !== runPromise); }; await delay(0); try { await this.runPoolTask(worker, task); } finally { removeTaskFromWorkersRunningTasks(); if (!this.isClosing) { this.scheduleWork(); } } })(); worker.runningTasks.push(runPromise); } scheduleWork() { this.debug("Attempt de-queueing a task in order to run it..."); const availableWorker = this.findIdlingWorker(); if (!availableWorker) return; const nextTask = this.taskQueue.shift(); if (!nextTask) { this.debug("Task queue is empty"); this.eventSubject.next({ type: "taskQueueDrained" /* taskQueueDrained */ }); return; } this.run(availableWorker, nextTask); } taskCompletion(taskID) { return new Promise((resolve, reject) => { const eventSubscription = this.events().subscribe((event) => { if (event.type === "taskCompleted" /* taskCompleted */ && event.taskID === taskID) { eventSubscription.unsubscribe(); resolve(event.returnValue); } else if (event.type === "taskFailed" /* taskFailed */ && event.taskID === taskID) { eventSubscription.unsubscribe(); reject(event.error); } else if (event.type === "terminated" /* terminated */) { eventSubscription.unsubscribe(); reject(new Error("Pool has been terminated before task was run.")); } }); }); } async settled(allowResolvingImmediately = false) { const getCurrentlyRunningTasks = () => flatMap(this.workers, (worker) => worker.runningTasks); const taskFailures = []; const failureSubscription = this.eventObservable.subscribe((event) => { if (event.type === "taskFailed" /* taskFailed */) { taskFailures.push(event.error); } }); if (this.initErrors.length > 0) { throw this.initErrors[0]; } if (allowResolvingImmediately && this.taskQueue.length === 0) { await Promise.allSettled(getCurrentlyRunningTasks()); return taskFailures; } await new Promise((resolve, reject) => { const subscription = this.eventObservable.subscribe({ error: reject, next(event) { if (event.type === "taskQueueDrained" /* taskQueueDrained */) { subscription.unsubscribe(); resolve(void 0); } } // make a pool-wide error reject the completed() result promise }); }); await Promise.allSettled(getCurrentlyRunningTasks()); failureSubscription.unsubscribe(); return taskFailures; } async completed(allowResolvingImmediately = false) { const settlementPromise = this.settled(allowResolvingImmediately); const earlyExitPromise = new Promise((resolve, reject) => { const subscription = this.eventObservable.subscribe({ error: reject, next(event) { if (event.type === "taskQueueDrained" /* taskQueueDrained */) { subscription.unsubscribe(); resolve(settlementPromise); } else if (event.type === "taskFailed" /* taskFailed */) { subscription.unsubscribe(); reject(event.error); } } // make a pool-wide error reject the completed() result promise }); }); const errors = await Promise.race([settlementPromise, earlyExitPromise]); if (errors.length > 0) { throw errors[0]; } } events() { return this.eventObservable; } queue(taskFunction) { const { maxQueuedJobs = Number.POSITIVE_INFINITY } = this.options; if (this.isClosing) { throw new Error("Cannot schedule pool tasks after terminate() has been called."); } if (this.initErrors.length > 0) { throw this.initErrors[0]; } const taskID = this.nextTaskID++; const taskCompletion = this.taskCompletion(taskID); taskCompletion.catch((error) => { this.debug(`Task #${taskID} errored:`, error); }); const task = { cancel: () => { if (!this.taskQueue.includes(task)) return; this.taskQueue = this.taskQueue.filter((someTask) => someTask !== task); this.eventSubject.next({ taskID: task.id, type: "taskCanceled" /* taskCanceled */ }); }, id: taskID, run: taskFunction, then: taskCompletion.then.bind(taskCompletion) }; if (this.taskQueue.length >= maxQueuedJobs) { throw new Error( "Maximum number of pool tasks queued. Refusing to queue another one.\nThis usually happens for one of two reasons: We are either at peak workload right now or some tasks just won't finish, thus blocking the pool." ); } this.debug(`Queueing task #${task.id}...`); this.taskQueue.push(task); this.eventSubject.next({ taskID: task.id, type: "taskQueued" /* taskQueued */ }); this.scheduleWork(); return task; } async terminate(force) { this.isClosing = true; if (!force) { await this.completed(true); } this.eventSubject.next({ remainingQueue: [...this.taskQueue], type: "terminated" /* terminated */ }); this.eventSubject.complete(); await Promise.all(this.workers.map(async (worker) => Thread.terminate(await worker.init))); } }; function PoolConstructor(spawnWorker, optionsOrSize) { return new WorkerPool(spawnWorker, optionsOrSize); } PoolConstructor.EventType = PoolEventType; var Pool = PoolConstructor; // src/master/spawn.ts import DebugLogger3 from "debug"; import { Observable as Observable4 } from "observable-fns"; // src/promise.ts var doNothing = () => void 0; function createPromiseWithResolver() { let alreadyResolved = false; let resolvedTo; let resolver = doNothing; const promise = new Promise((resolve) => { if (alreadyResolved) { resolve(resolvedTo); } else { resolver = resolve; } }); const exposedResolver = (value) => { alreadyResolved = true; resolvedTo = value; resolver(resolvedTo); }; return [promise, exposedResolver]; } // src/master/invocation-proxy.ts import DebugLogger2 from "debug"; import { multicast as multicast2, Observable as Observable3 } from "observable-fns"; // src/observable-promise.ts import { Observable as Observable2 } from "observable-fns"; var doNothing2 = () => { }; var returnInput = (input) => input; var runDeferred = (fn) => Promise.resolve().then(fn); function fail2(error) { throw error; } function isThenable(thing) { return thing && typeof thing.then === "function"; } var ObservablePromise = class _ObservablePromise extends Observable2 { [Symbol.toStringTag] = "[object ObservablePromise]"; initHasRun = false; fulfillmentCallbacks = []; rejectionCallbacks = []; firstValue; firstValueSet = false; rejection; state = "pending"; constructor(init) { super((originalObserver) => { const self2 = this; const observer = { ...originalObserver, complete() { originalObserver.complete(); self2.onCompletion(); }, error(error) { originalObserver.error(error); self2.onError(error); }, next(value) { originalObserver.next(value); self2.onNext(value); } }; try { this.initHasRun = true; return init(observer); } catch (error) { observer.error(error); } }); } onNext(value) { if (!this.firstValueSet) { this.firstValue = value; this.firstValueSet = true; } } onError(error) { this.state = "rejected"; this.rejection = error; for (const onRejected of this.rejectionCallbacks) { runDeferred(() => onRejected(error)); } } onCompletion() { this.state = "fulfilled"; for (const onFulfilled of this.fulfillmentCallbacks) { runDeferred(() => onFulfilled(this.firstValue)); } } then(onFulfilledRaw, onRejectedRaw) { const onFulfilled = onFulfilledRaw || returnInput; const onRejected = onRejectedRaw || fail2; let onRejectedCalled = false; return new Promise((resolve, reject) => { const rejectionCallback = (error) => { if (onRejectedCalled) return; onRejectedCalled = true; try { resolve(onRejected(error)); } catch (anotherError) { reject(anotherError); } }; const fulfillmentCallback = (value) => { try { resolve(onFulfilled(value)); } catch (ex) { const error = ex; rejectionCallback(error); } }; if (!this.initHasRun) { this.subscribe({ error: rejectionCallback }); } if (this.state === "fulfilled") { return resolve(onFulfilled(this.firstValue)); } if (this.state === "rejected") { onRejectedCalled = true; return resolve(onRejected(this.rejection)); } this.fulfillmentCallbacks.push(fulfillmentCallback); this.rejectionCallbacks.push(rejectionCallback); }); } catch(onRejected) { return this.then(void 0, onRejected); } finally(onCompleted) { const handler = onCompleted || doNothing2; return this.then( (value) => { handler(); return value; }, () => handler() ); } static from(thing) { return isThenable(thing) ? new _ObservablePromise((observer) => { const onFulfilled = (value) => { observer.next(value); observer.complete(); }; const onRejected = (error) => { observer.error(error); }; thing.then(onFulfilled, onRejected); }) : super.from(thing); } }; // src/transferable.ts function isTransferable(thing) { if (!thing || typeof thing !== "object") return false; return true; } function isTransferDescriptor(thing) { return thing && typeof thing === "object" && thing[$transferable]; } function Transfer(payload, transferables) { console.log("Transfer"); if (!transferables) { if (!isTransferable(payload)) throw new Error("Not transferable"); transferables = [payload]; } return { [$transferable]: true, send: payload, transferables }; } // src/master/invocation-proxy.ts var debugMessages = DebugLogger2("threads:master:messages"); var nextJobUID = 1; var dedupe = (array) => [...new Set(array)]; var isJobErrorMessage = (data) => data && data.type === "error" /* error */; var isJobResultMessage = (data) => data && data.type === "result" /* result */; var isJobStartMessage = (data) => data && data.type === "running" /* running */; function createObservableForJob(worker, jobUID) { return new Observable3((observer) => { let asyncType; const messageHandler = ((event) => { debugMessages("Message from worker:", event.data); if (!event.data || event.data.uid !== jobUID) return; if (isJobStartMessage(event.data)) { asyncType = event.data.resultType; } else if (isJobResultMessage(event.data)) { if (asyncType === "promise") { if (event.data.payload !== void 0) { observer.next(deserialize(event.data.payload)); } observer.complete(); worker.removeEventListener("message", messageHandler); } else { if (event.data.payload) { observer.next(deserialize(event.data.payload)); } if (event.data.complete) { observer.complete(); worker.removeEventListener("message", messageHandler); } } } else if (isJobErrorMessage(event.data)) { const error = deserialize(event.data.error); if (asyncType === "promise" || !asyncType) { observer.error(error); } else { observer.error(error); } worker.removeEventListener("message", messageHandler); } }); worker.addEventListener("message", messageHandler); return () => { if (asyncType === "observable" || !asyncType) { const cancelMessage = { type: "cancel" /* cancel */, uid: jobUID }; worker.postMessage(cancelMessage); } worker.removeEventListener("message", messageHandler); }; }); } function prepareArguments(rawArgs) { if (rawArgs.length === 0) { return { args: [], transferables: [] }; } const args = []; const transferables = []; for (const arg of rawArgs) { if (isTransferDescriptor(arg)) { args.push(serialize(arg.send)); transferables.push(...arg.transferables); } else { args.push(serialize(arg)); } } return { args, transferables: transferables.length === 0 ? transferables : dedupe(transferables) }; } function createProxyFunction(worker, method) { return ((...rawArgs) => { const uid = nextJobUID++; const { args, transferables } = prepareArguments(rawArgs); const runMessage = { args, method, type: "run" /* run */, uid }; debugMessages("Sending command to run function to worker:", runMessage); try { worker.postMessage(runMessage, transferables); } catch (error) { return ObservablePromise.from(Promise.reject(error)); } return ObservablePromise.from(multicast2(createObservableForJob(worker, uid))); }); } function createProxyModule(worker, methodNames) { const proxy = {}; for (const methodName of methodNames) { proxy[methodName] = createProxyFunction(worker, methodName); } return proxy; } // src/master/spawn.ts var debugMessages2 = DebugLogger3("threads:master:messages"); var debugSpawn = DebugLogger3("threads:master:spawn"); var debugThreadUtils = DebugLogger3("threads:master:thread-utils"); var isInitMessage = (data) => data && data.type === "init"; var isUncaughtErrorMessage = (data) => data && data.type === "uncaughtError"; var initMessageTimeout = typeof process !== "undefined" && process.env !== void 0 && process.env.THREADS_WORKER_INIT_TIMEOUT ? Number.parseInt(process.env.THREADS_WORKER_INIT_TIMEOUT, 10) : 1e4; async function withTimeout(promise, timeoutInMs, errorMessage) { let timeoutHandle; const timeout = new Promise((resolve, reject) => { timeoutHandle = setTimeout(() => reject(new Error(errorMessage)), timeoutInMs); }); const result = await Promise.race([promise, timeout]); clearTimeout(timeoutHandle); return result; } function receiveInitMessage(worker) { return new Promise((resolve, reject) => { const messageHandler = ((event) => { debugMessages2("Message from worker before finishing initialization:", event.data); if (isInitMessage(event.data)) { worker.removeEventListener("message", messageHandler); resolve(event.data); } else if (isUncaughtErrorMessage(event.data)) { worker.removeEventListener("message", messageHandler); reject(deserialize(event.data.error)); } }); worker.addEventListener("message", messageHandler); }); } function createEventObservable(worker, workerTermination) { return new Observable4((observer) => { const messageHandler = ((messageEvent) => { const workerEvent = { data: messageEvent.data, type: "message" /* message */ }; observer.next(workerEvent); }); const rejectionHandler = ((errorEvent) => { debugThreadUtils("Unhandled promise rejection event in thread:", errorEvent); const workerEvent = { error: new Error(errorEvent.reason), type: "internalError" /* internalError */ }; observer.next(workerEvent); }); worker.addEventListener("message", messageHandler); worker.addEventListener("unhandledrejection", rejectionHandler); workerTermination.then(() => { const terminationEvent = { type: "termination" /* termination */ }; worker.removeEventListener("message", messageHandler); worker.removeEventListener("unhandledrejection", rejectionHandler); observer.next(terminationEvent); observer.complete(); }); }); } function createTerminator(worker) { const [termination, resolver] = createPromiseWithResolver(); const terminate = async () => { debugThreadUtils("Terminating worker"); await worker.terminate(); resolver(); }; return { terminate, termination }; } function setPrivateThreadProps(raw, worker, workerEvents, terminate) { const workerErrors = workerEvents.filter((event) => event.type === "internalError" /* internalError */).map((errorEvent) => errorEvent.error); return Object.assign(raw, { [$errors]: workerErrors, [$events]: workerEvents, [$terminate]: terminate, [$worker]: worker }); } async function spawn(worker, options) { debugSpawn("Initializing new thread"); const timeout = options && options.timeout ? options.timeout : initMessageTimeout; const initMessage = await withTimeout( receiveInitMessage(worker), timeout, `Timeout: Did not receive an init message from worker after ${timeout}ms. Make sure the worker calls expose().` ); const exposed = initMessage.exposed; const { termination, terminate } = createTerminator(worker); const events = createEventObservable(worker, termination); if (exposed.type === "function") { const proxy = createProxyFunction(worker); return setPrivateThreadProps(proxy, worker, events, terminate); } else if (exposed.type === "module") { const proxy = createProxyModule(worker, exposed.methods); return setPrivateThreadProps(proxy, worker, events, terminate); } else { const type = exposed.type; throw new Error(`Worker init message states unexpected type of expose(): ${type}`); } } // src/master/index-node.ts var BlobWorker = getWorkerImplementation().blob; var Worker2 = getWorkerImplementation().default; export { BlobWorker, DefaultSerializer, Pool, Thread, Transfer, Worker2 as Worker, isWorkerRuntime, registerSerializer, spawn }; //# sourceMappingURL=index-node.mjs.map