@chainsafe/threads
Version:
Web workers & worker threads as simple as a function call
122 lines (121 loc) • 4.54 kB
JavaScript
/*
* 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;
}