Skip to content

Commit

Permalink
Fix hang issue (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Sep 3, 2022
1 parent d8a57f7 commit e7d223e
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 20 deletions.
57 changes: 57 additions & 0 deletions lib/astrojs-compiler-worker-service.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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");
let worker;
let seq = 0;
let processMap = new Map();

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);
}
}

function terminateWorker() {
if (worker) {
worker.terminate();
worker = null;
}
}

runAsWorker((method, ...args) => {
const id = seq++;
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) => {
processMap.set(id, { resolve, reject });
setTimeout(() => {
if (processMap.has(id)) {
processMap.delete(id);
terminateWorker();
reject(new Error("Timeout"));
}
}, 3000);
worker.postMessage({ id, method, args });
});
});
9 changes: 0 additions & 9 deletions lib/astrojs-compiler-worker.cjs

This file was deleted.

36 changes: 31 additions & 5 deletions lib/astrojs-compiler-worker.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import { runAsWorker } from "synckit";
import * as compiler from "@astrojs/compiler";
import { parentPort } from "worker_threads";

runAsWorker((method, ...args) => {
const result = compiler[method](...args);
return result;
});
try {
parentPort.on("message", async ({ id, method, args }) => {
let msg;
try {
msg = { id, result: await compiler[method](...args) };
} catch (error) {
msg = { id, error, properties: extractProperties(error) };
}
parentPort.postMessage(msg);
});
} catch (error) {
parentPort.on("message", ({ id }) => {
parentPort.postMessage({
id,
error,
properties: extractProperties(error),
});
});
}

function extractProperties(object) {
if (object && typeof object === "object") {
const properties = {};
for (const key in object) {
properties[key] = object[key];
}
return properties;
}
return object;
}
2 changes: 1 addition & 1 deletion lib/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { resolve } = require("path");
const { createSyncFn } = require("synckit");

const compilerSync = createSyncFn(
resolve(__dirname, "./astrojs-compiler-worker.cjs")
resolve(__dirname, "./astrojs-compiler-worker-service.mjs")
);

module.exports = {
Expand Down
2 changes: 1 addition & 1 deletion lib/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fileURLToPath } from "url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const compilerSync = createSyncFn(
resolve(__dirname, "./astrojs-compiler-worker.mjs")
resolve(__dirname, "./astrojs-compiler-worker-service.mjs")
);

export function parse(...args) {
Expand Down
32 changes: 28 additions & 4 deletions tests/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import assert from "assert";

const modules = { mjs, cjs };

for (const [nm, mo] of Object.entries(modules)) {
describe("parse test", () => {
describe("parse test", () => {
for (const [nm, mo] of Object.entries(modules)) {
it(nm, () => {
const result = mo.parse(`---
fn()
Expand Down Expand Up @@ -43,5 +43,29 @@ fn()
},
});
});
});
}

it(`${nm} with error`, () => {
const code = `---
const foo = true
---
<!-- notice the tag is not closed properly -->
<style is:inline set:html={""}>`;

let fail = false;
try {
mo.parse(code);
} catch (_e) {
fail = true;
}
if (!fail) assert.fail("Expected error");
fail = false;
try {
mo.parse(code);
} catch (_e) {
fail = true;
}
if (!fail) assert.fail("Expected error");
});
}
});

0 comments on commit e7d223e

Please sign in to comment.