Skip to content

Commit 2405f98

Browse files
committed
test(project): Cover build server recovery after non-abort errors
Pin the deadlock fix in BuildServer.#processBuildRequests with a regression test: serve application.a in cache=Force mode against an empty cache, fire two requests, and assert both reject with the underlying "no cache found" error and that exactly two "error" events are emitted. Before the fix, the second request hung until AVA's timeout because #activeBuild was never cleared.
1 parent ef60019 commit 2405f98

1 file changed

Lines changed: 34 additions & 0 deletions

File tree

packages/project/test/lib/build/BuildServer.integration.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,40 @@ test.serial("Serve application.a with --cache=Force (2)", async (t) => {
630630
await setTimeout(50);
631631
});
632632

633+
// Regression: a non-abort build error used to leave #activeBuild set, deadlocking the BuildServer
634+
// so subsequent resource requests would hang forever. The fix in #processBuildRequests clears
635+
// #activeBuild in a finally block and emits "error" instead of throwing — verify a second request
636+
// still rejects (with the same root cause) instead of hanging.
637+
test.serial("Build server recovers from non-abort build error (no deadlock)", async (t) => {
638+
const fixtureTester = t.context.fixtureTester = await FixtureTester.create(t, "application.a");
639+
640+
const errorEvents = [];
641+
await fixtureTester.serveProject({config: {cache: Cache.Force}, expectBuildErrors: true});
642+
fixtureTester.buildServer.on("error", (err) => errorEvents.push(err));
643+
644+
// First request triggers a build that fails because cache=Force has no cache
645+
const firstError = await t.throwsAsync(async () => {
646+
await fixtureTester.requestResource({resource: "/test.js"});
647+
});
648+
t.true(
649+
firstError.message.includes(`Cache is in "Force" mode but no cache found for project application.a`),
650+
"First request rejects with the Force-mode cache miss"
651+
);
652+
653+
// Second request must reject again (not hang) — proves #activeBuild was cleared after the failure
654+
const secondError = await t.throwsAsync(async () => {
655+
await fixtureTester.requestResource({resource: "/test.js"});
656+
});
657+
t.true(
658+
secondError.message.includes(`Cache is in "Force" mode but no cache found for project application.a`),
659+
`Second request rejects with the same error instead of deadlocking. Got: ${secondError && secondError.message}`
660+
);
661+
662+
// Each failed build emits exactly one "error" event
663+
await setTimeout(50);
664+
t.is(errorEvents.length, 2, "Two build errors were emitted, one per failed build attempt");
665+
});
666+
633667
// ProjectBuildCache's StageCache must be cleared correctly when a build is aborted.
634668
// A task that completed during an aborted attempt has already called recordTaskResult,
635669
// which adds its stage to the in-memory StageCache. On retry,

0 commit comments

Comments
 (0)