From 99dbe525d9b221bf12778ed899c94ef103a77c45 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Tue, 22 Aug 2023 22:27:41 -0700 Subject: [PATCH] fix: properly lock across source and content refresh by sharing a mutex --- quartz/bootstrap-cli.mjs | 12 ++++++------ quartz/build.ts | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs index 6a975ca4..47c58ab0 100755 --- a/quartz/bootstrap-cli.mjs +++ b/quartz/bootstrap-cli.mjs @@ -394,12 +394,12 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. const buildMutex = new Mutex() const timeoutIds = new Set() - let firstBuild = true + let cleanupBuild = null const build = async (clientRefresh) => { const release = await buildMutex.acquire() - if (firstBuild) { - firstBuild = false - } else { + + if (cleanupBuild) { + await cleanupBuild() console.log(chalk.yellow("Detected a source code change, doing a hard rebuild...")) } @@ -408,6 +408,7 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. console.log(`Reason: ${chalk.grey(err)}`) process.exit(1) }) + release() if (argv.bundleInfo) { const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs" @@ -423,9 +424,8 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. // bypass module cache // https://github.com/nodejs/modules/issues/307 const { default: buildQuartz } = await import(cacheFile + `?update=${randomUUID()}`) - await buildQuartz(argv, clientRefresh) + cleanupBuild = await buildQuartz(argv, buildMutex, clientRefresh) clientRefresh() - release() } const rebuild = (clientRefresh) => { diff --git a/quartz/build.ts b/quartz/build.ts index 0af39d00..8b1d3183 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -18,7 +18,7 @@ import { trace } from "./util/trace" import { options } from "./util/sourcemap" import { Mutex } from "async-mutex" -async function buildQuartz(argv: Argv, clientRefresh: () => void) { +async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { const ctx: BuildCtx = { argv, cfg, @@ -38,6 +38,7 @@ async function buildQuartz(argv: Argv, clientRefresh: () => void) { console.log(` Emitters: ${pluginNames("emitters").join(", ")}`) } + const release = await mut.acquire() perf.addEvent("clean") await rimraf(output) console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`) @@ -56,15 +57,17 @@ async function buildQuartz(argv: Argv, clientRefresh: () => void) { const filteredContent = filterContent(ctx, parsedFiles) await emitContent(ctx, filteredContent) console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`)) + release() if (argv.serve) { - return startServing(ctx, parsedFiles, clientRefresh) + return startServing(ctx, mut, parsedFiles, clientRefresh) } } // setup watcher for rebuilds async function startServing( ctx: BuildCtx, + mut: Mutex, initialContent: ProcessedContent[], clientRefresh: () => void, ) { @@ -78,7 +81,6 @@ async function startServing( } const initialSlugs = ctx.allSlugs - const buildMutex = new Mutex() const timeoutIds: Set> = new Set() const toRebuild: Set = new Set() const toRemove: Set = new Set() @@ -111,7 +113,7 @@ async function startServing( // debounce rebuilds every 250ms timeoutIds.add( setTimeout(async () => { - const release = await buildMutex.acquire() + const release = await mut.acquire() timeoutIds.forEach((id) => clearTimeout(id)) timeoutIds.clear() @@ -164,11 +166,16 @@ async function startServing( .on("add", (fp) => rebuild(fp, "add")) .on("change", (fp) => rebuild(fp, "change")) .on("unlink", (fp) => rebuild(fp, "delete")) + + return async () => { + timeoutIds.forEach((id) => clearTimeout(id)) + await watcher.close() + } } -export default async (argv: Argv, clientRefresh: () => void) => { +export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => { try { - return await buildQuartz(argv, clientRefresh) + return await buildQuartz(argv, mut, clientRefresh) } catch (err) { trace("\nExiting Quartz due to a fatal error", err as Error) }