fix: properly lock across source and content refresh by sharing a mutex

This commit is contained in:
Jacky Zhao 2023-08-22 22:27:41 -07:00
parent 8b63ff882a
commit 99dbe525d9
2 changed files with 19 additions and 12 deletions

View file

@ -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) => {

View file

@ -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<ReturnType<typeof setTimeout>> = new Set()
const toRebuild: Set<FilePath> = new Set()
const toRemove: Set<FilePath> = 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)
}