synckit
Version:
Perform async work synchronously in Node.js using `worker_threads`, or `child_process` as fallback, with first-class TypeScript support.
130 lines (123 loc) • 4.28 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var path = require('path');
var worker_threads = require('worker_threads');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
var _a;
const {
SYNCKIT_BUFFER_SIZE,
SYNCKIT_TIMEOUT,
SYNCKIT_TS_ESM,
SYNCKIT_EXEC_ARV
} = process.env;
const TS_USE_ESM = !!SYNCKIT_TS_ESM && ["1", "true"].includes(SYNCKIT_TS_ESM);
const DEFAULT_BUFFER_SIZE = SYNCKIT_BUFFER_SIZE ? +SYNCKIT_BUFFER_SIZE : void 0;
const DEFAULT_TIMEOUT = SYNCKIT_TIMEOUT ? +SYNCKIT_TIMEOUT : void 0;
const DEFAULT_WORKER_BUFFER_SIZE = DEFAULT_BUFFER_SIZE || 1024;
const DEFAULT_EXEC_ARGV = (_a = SYNCKIT_EXEC_ARV == null ? void 0 : SYNCKIT_EXEC_ARV.split(",")) != null ? _a : [];
const syncFnCache = new Map();
function createSyncFn(workerPath, bufferSizeOrOptions, timeout) {
if (!path__default["default"].isAbsolute(workerPath)) {
throw new Error("`workerPath` must be absolute");
}
const cachedSyncFn = syncFnCache.get(workerPath);
if (cachedSyncFn) {
return cachedSyncFn;
}
const syncFn = startWorkerThread(workerPath, typeof bufferSizeOrOptions === "number" ? { bufferSize: bufferSizeOrOptions, timeout } : bufferSizeOrOptions);
syncFnCache.set(workerPath, syncFn);
return syncFn;
}
const throwError = (msg) => {
throw new Error(msg);
};
function startWorkerThread(workerPath, {
bufferSize = DEFAULT_WORKER_BUFFER_SIZE,
timeout = DEFAULT_TIMEOUT,
execArgv = DEFAULT_EXEC_ARGV
} = {}) {
const { port1: mainPort, port2: workerPort } = new worker_threads.MessageChannel();
const isTs = workerPath.endsWith(".ts");
const worker = new worker_threads.Worker(isTs ? TS_USE_ESM ? throwError("Native esm in `.ts` file is not supported yet, please use `.cjs` instead") : `require('ts-node/register');require('${workerPath}')` : workerPath, {
eval: isTs,
workerData: { workerPort },
transferList: [workerPort],
execArgv
});
let nextID = 0;
const syncFn = (...args) => {
const id = nextID++;
const sharedBuffer = new SharedArrayBuffer(bufferSize);
const sharedBufferView = new Int32Array(sharedBuffer);
const msg = { sharedBuffer, id, args };
worker.postMessage(msg);
const status = Atomics.wait(sharedBufferView, 0, 0, timeout);
if (!["ok", "not-equal"].includes(status)) {
throw new Error("Internal error: Atomics.wait() failed: " + status);
}
const {
id: id2,
result,
error
} = worker_threads.receiveMessageOnPort(mainPort).message;
if (id !== id2) {
throw new Error(`Internal error: Expected id ${id} but got id ${id2}`);
}
if (error) {
throw error;
}
return result;
};
worker.unref();
return syncFn;
}
function runAsWorker(fn) {
if (!worker_threads.workerData) {
return;
}
const { workerPort } = worker_threads.workerData;
worker_threads.parentPort.on("message", ({ sharedBuffer, id, args }) => {
(() => __async(this, null, function* () {
const sharedBufferView = new Int32Array(sharedBuffer);
let msg;
try {
msg = { id, result: yield fn(...args) };
} catch (error) {
msg = {
id,
error
};
}
workerPort.postMessage(msg);
Atomics.add(sharedBufferView, 0, 1);
Atomics.notify(sharedBufferView, 0);
}))();
});
}
exports.DEFAULT_BUFFER_SIZE = DEFAULT_BUFFER_SIZE;
exports.DEFAULT_EXEC_ARGV = DEFAULT_EXEC_ARGV;
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
exports.DEFAULT_WORKER_BUFFER_SIZE = DEFAULT_WORKER_BUFFER_SIZE;
exports.createSyncFn = createSyncFn;
exports.runAsWorker = runAsWorker;