threads
Version:
Web workers & worker threads as simple as a function call
138 lines (137 loc) • 6.34 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import DebugLogger from "debug";
import { Observable } from "observable-fns";
import { deserialize } from "../common";
import { createPromiseWithResolver } from "../promise";
import { $errors, $events, $terminate, $worker } from "../symbols";
import { WorkerEventType } from "../types/master";
import { createProxyFunction, createProxyModule } from "./invocation-proxy";
const debugMessages = DebugLogger("threads:master:messages");
const debugSpawn = DebugLogger("threads:master:spawn");
const debugThreadUtils = DebugLogger("threads:master:thread-utils");
const isInitMessage = (data) => data && data.type === "init";
const isUncaughtErrorMessage = (data) => data && data.type === "uncaughtError";
const initMessageTimeout = typeof process !== "undefined" && process.env.THREADS_WORKER_INIT_TIMEOUT
? Number.parseInt(process.env.THREADS_WORKER_INIT_TIMEOUT, 10)
: 10000;
function withTimeout(promise, timeoutInMs, errorMessage) {
return __awaiter(this, void 0, void 0, function* () {
let timeoutHandle;
const timeout = new Promise((resolve, reject) => {
timeoutHandle = setTimeout(() => reject(Error(errorMessage)), timeoutInMs);
});
const result = yield Promise.race([
promise,
timeout
]);
clearTimeout(timeoutHandle);
return result;
});
}
function receiveInitMessage(worker) {
return new Promise((resolve, reject) => {
const messageHandler = ((event) => {
debugMessages("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 Observable(observer => {
const messageHandler = ((messageEvent) => {
const workerEvent = {
type: WorkerEventType.message,
data: messageEvent.data
};
observer.next(workerEvent);
});
const rejectionHandler = ((errorEvent) => {
debugThreadUtils("Unhandled promise rejection event in thread:", errorEvent);
const workerEvent = {
type: WorkerEventType.internalError,
error: Error(errorEvent.reason)
};
observer.next(workerEvent);
});
worker.addEventListener("message", messageHandler);
worker.addEventListener("unhandledrejection", rejectionHandler);
workerTermination.then(() => {
const terminationEvent = {
type: WorkerEventType.termination
};
worker.removeEventListener("message", messageHandler);
worker.removeEventListener("unhandledrejection", rejectionHandler);
observer.next(terminationEvent);
observer.complete();
});
});
}
function createTerminator(worker) {
const [termination, resolver] = createPromiseWithResolver();
const terminate = () => __awaiter(this, void 0, void 0, function* () {
debugThreadUtils("Terminating worker");
// Newer versions of worker_threads workers return a promise
yield worker.terminate();
resolver();
});
return { terminate, termination };
}
function setPrivateThreadProps(raw, worker, workerEvents, terminate) {
const workerErrors = workerEvents
.filter(event => event.type === WorkerEventType.internalError)
.map(errorEvent => errorEvent.error);
// tslint:disable-next-line prefer-object-spread
return Object.assign(raw, {
[$errors]: workerErrors,
[$events]: workerEvents,
[$terminate]: terminate,
[$worker]: worker
});
}
/**
* Spawn a new thread. Takes a fresh worker instance, wraps it in a thin
* abstraction layer to provide the transparent API and verifies that
* the worker has initialized successfully.
*
* @param worker Instance of `Worker`. Either a web worker, `worker_threads` worker or `tiny-worker` worker.
* @param [options]
* @param [options.timeout] Init message timeout. Default: 10000 or set by environment variable.
*/
export function spawn(worker, options) {
return __awaiter(this, void 0, void 0, function* () {
debugSpawn("Initializing new thread");
const timeout = options && options.timeout ? options.timeout : initMessageTimeout;
const initMessage = yield 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 Error(`Worker init message states unexpected type of expose(): ${type}`);
}
});
}