astro-ghostcms/.pnpm-store/v3/files/b0/5db4e154e5d3ea20123a2cd120a...

100 lines
2.6 KiB
Plaintext

import { dirname, resolve } from "path";
import { runAsWorker } from "synckit";
import { fileURLToPath } from "url";
import { Worker } from "worker_threads";
// When some error occurs in @astrojs/compiler, the worker may be terminated.
// So calling @astrojs/compiler directly on this worker may hang calls via `synckit`.
// This worker creates a child worker to process @astrojs/compiler, and if an error occurs in the child worker,
// it terminates the child worker and recreates it to prevent hangs.
// Related to https://github.com/ota-meshi/eslint-plugin-astro/issues/99
const __dirname = dirname(fileURLToPath(import.meta.url));
const workerPath = resolve(__dirname, "./astrojs-compiler-worker.mjs");
/**
* @typedef {Object} Process
* @property {(v: any) => void} resolve
* @property {(v: any) => void} reject
*/
/** @type {Worker | undefined} */
let worker;
let seq = 0;
/** @type {Map<number, Process>} */
const processMap = new Map();
/** @type {Set<()=>boolean>} */
const terminateRemainingProcessList = new Set();
function onMessage({ id, result, error, properties }) {
const proc = processMap.get(id);
processMap.delete(id);
if (error) {
proc?.reject(Object.assign(error, properties));
terminateWorker();
} else {
proc?.resolve(result);
terminateRemainingProcesses();
}
}
function terminateWorker() {
const remaining = new Set(processMap.keys());
const targetWorker = worker;
worker = null;
terminateRemainingProcessList.add(tryTerminate);
terminateRemainingProcesses();
function tryTerminate() {
if (!targetWorker) {
return true;
}
for (const id of [...remaining]) {
if (!processMap.has(id)) {
remaining.delete(id);
}
}
if (remaining.size === 0) {
targetWorker.terminate();
return true;
}
return false;
}
}
function terminateRemainingProcesses() {
for (const proc of [...terminateRemainingProcessList]) {
if (proc()) {
terminateRemainingProcessList.delete(proc);
}
}
}
runAsWorker((method, ...args) => {
if (!worker) {
worker = new Worker(workerPath, {});
worker.on("message", onMessage);
worker.on("exit", terminateWorker);
worker.on("error", terminateWorker);
worker.unref();
}
return new Promise((resolve, reject) => {
const id = seq++;
processMap.set(id, { resolve, reject });
setTimeout(() => {
if (processMap.delete(id)) {
terminateWorker();
reject(
new Error(
"Timeout: More than 10 seconds passed for parsing. Possible unexpected error.",
),
);
}
}, 10000);
worker.postMessage({ id, method, args });
});
});