UNPKG

@chainsafe/threads

Version:

Web workers & worker threads as simple as a function call

122 lines (121 loc) 4.54 kB
/* * This source file contains the code for proxying calls in the master thread to calls in the workers * by `.postMessage()`-ing. * * Keep in mind that this code can make or break the program's performance! Need to optimize more… */ import DebugLogger from "debug"; import { multicast, Observable } from "observable-fns"; import { deserialize, serialize } from "../common"; import { ObservablePromise } from "../observable-promise"; import { isTransferDescriptor } from "../transferable"; import { MasterMessageType, WorkerMessageType } from "../types/messages"; const debugMessages = DebugLogger("threads:master:messages"); let nextJobUID = 1; const dedupe = (array) => Array.from(new Set(array)); const isJobErrorMessage = (data) => data && data.type === WorkerMessageType.error; const isJobResultMessage = (data) => data && data.type === WorkerMessageType.result; const isJobStartMessage = (data) => data && data.type === WorkerMessageType.running; function createObservableForJob(worker, jobUID) { return new Observable(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 (typeof event.data.payload !== "undefined") { 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: MasterMessageType.cancel, uid: jobUID }; worker.postMessage(cancelMessage); } worker.removeEventListener("message", messageHandler); }; }); } function prepareArguments(rawArgs) { if (rawArgs.length === 0) { // Exit early if possible 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) }; } export function createProxyFunction(worker, method) { return ((...rawArgs) => { const uid = nextJobUID++; const { args, transferables } = prepareArguments(rawArgs); const runMessage = { type: MasterMessageType.run, uid, method, args }; 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(multicast(createObservableForJob(worker, uid))); }); } export function createProxyModule(worker, methodNames) { const proxy = {}; for (const methodName of methodNames) { proxy[methodName] = createProxyFunction(worker, methodName); } return proxy; }