From 03dce1237803e1640e7d6679089b3fecb84b5d6c Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 26 Oct 2026 20:33:23 +0100 Subject: [PATCH] The standing test keeps c failing alone after its ask is dropped, so its only recovery is its retirement: clearing every failure at once raced the loop's read of the new list under load (issue 1126; the hidden failure of try-1793042418286545971, issue 1901) --- test/standing.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/standing.test.ts b/test/standing.test.ts index 107cb49..bfc916a 100644 --- a/test/standing.test.ts +++ b/test/standing.test.ts @@ -34,6 +34,8 @@ interface World { ask(list: { as: string; node?: string; values?: Record }[]): Promise; failing: Error | null; holdsErr: Error | null; + /** When set, holds fails only for these consumers (holdsErr's error). */ + holdsErrFor: Set | null; created: string[]; /** Every event but the first success since start, which has its own test. */ said: Said[]; @@ -44,7 +46,7 @@ interface World { async function world(opts: { classify?: boolean; announce?: false; stableForMs?: number } = {}): Promise { const dir = await mkdtemp(join(tmpdir(), "standing-")); const receives = join(dir, "postgres-database.json"); - const w = { dir, failing: null, holdsErr: null, created: [], said: [], all: [] } as unknown as World; + const w = { dir, failing: null, holdsErr: null, holdsErrFor: null, created: [], said: [], all: [] } as unknown as World; w.ask = async (list) => { const given = []; for (const g of list) { @@ -60,8 +62,8 @@ async function world(opts: { classify?: boolean; announce?: false; stableForMs?: }, async remove() {}, async retire() {}, - async holds() { - if (w.holdsErr) throw w.holdsErr; + async holds(p) { + if (w.holdsErr && (!w.holdsErrFor || w.holdsErrFor.has(p.as))) throw w.holdsErr; return true; }, ...(opts.classify ? { classify: () => "its-own" } : {}), @@ -197,9 +199,11 @@ test("a failing consumer the mesh stopped asking for is said recovered when it i await until(() => w.created.length === 3); w.holdsErr = new Error("boom"); await until(() => w.said.filter((s) => s.event === FAILING_EVENT).length >= 3); - // c is no longer asked for while it is still failing; the others then recover. + // c is no longer asked for while it is still failing; the others then recover. c keeps failing, so its only way + // to be said recovered is its retirement: clearing every failure at once raced the loop's read of the new list + // under load (a pass before that read recovered c as an ordinary recovery, with no why). await w.ask([{ as: "a" }, { as: "b" }]); - w.holdsErr = null; + w.holdsErrFor = new Set(["c"]); await until(() => w.said.some((s) => s.event === RECOVERED_EVENT && s.body.consumer === "c")); const c = w.said.find((s) => s.event === RECOVERED_EVENT && s.body.consumer === "c")!; assert.equal(c.body.why, "retired");