Skip to content

Commit 55dd5df

Browse files
committed
test(run-engine): close four coverage holes in the waitpoint store coordinator
Add tests for the winner's own retry in createWithIdempotencyKey, a COMPLETED record round-tripping through createIfAbsent, absorbBlockers reading back a stored delivery envelope rather than its flag, and a new genuine-concurrency suite (real Promise.all races, no mocks) covering complete, registerOrReport, createWithIdempotencyKey, and registerBlocks under contention. Each was proven against its mutant and restored clean.
1 parent e1bc664 commit 55dd5df

1 file changed

Lines changed: 291 additions & 0 deletions

File tree

internal-packages/run-engine/src/engine/waitpointCoordinator/storeCoordinator.test.ts

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,48 @@ describe("createIfAbsent", () => {
164164
}
165165
}
166166
);
167+
168+
redisTest(
169+
"reads a COMPLETED record back through createIfAbsent, with an envelope",
170+
async ({ redisOptions }) => {
171+
const store = coordinator(redisOptions);
172+
try {
173+
await store.createIfAbsent({
174+
record: record("w_a"),
175+
status: "COMPLETED",
176+
completion: completion(),
177+
});
178+
179+
const second = await store.createIfAbsent({ record: record("w_a"), status: "PENDING" });
180+
181+
expect(second.outcome).toBe("exists");
182+
if (second.outcome !== "exists") throw new Error("unreachable");
183+
expect(second.status).toBe("COMPLETED");
184+
expect(second.completion?.output).toEqual({ inline: '{"ok":true}' });
185+
} finally {
186+
await store.quit();
187+
}
188+
}
189+
);
190+
191+
redisTest(
192+
"reads a COMPLETED record back through createIfAbsent, with no envelope",
193+
async ({ redisOptions }) => {
194+
const store = coordinator(redisOptions);
195+
try {
196+
await store.createIfAbsent({ record: record("w_a"), status: "COMPLETED" });
197+
198+
const second = await store.createIfAbsent({ record: record("w_a"), status: "PENDING" });
199+
200+
expect(second.outcome).toBe("exists");
201+
if (second.outcome !== "exists") throw new Error("unreachable");
202+
expect(second.status).toBe("COMPLETED");
203+
expect(second.completion).toBeUndefined();
204+
} finally {
205+
await store.quit();
206+
}
207+
}
208+
);
167209
});
168210

169211
describe("registerOrReport", () => {
@@ -746,6 +788,51 @@ describe("createWithIdempotencyKey", () => {
746788
}
747789
});
748790

791+
redisTest(
792+
"the original creator's own retry does not discard its own record",
793+
async ({ redisOptions }) => {
794+
const store = coordinator(redisOptions);
795+
const probe = createRedisClient(redisOptions);
796+
try {
797+
const withKey = record(idA, {
798+
idempotencyKey: "key-1",
799+
userProvidedIdempotencyKey: true,
800+
});
801+
802+
const first = await store.createWithIdempotencyKey({
803+
record: withKey,
804+
environmentId: ENV_ID,
805+
idempotencyKey: "key-1",
806+
});
807+
expect(first).toEqual({ waitpointId: idA, created: true });
808+
809+
// The SAME caller, retrying with the SAME record id and the SAME key — not a
810+
// different id racing for the same reservation.
811+
const retry = await store.createWithIdempotencyKey({
812+
record: withKey,
813+
environmentId: ENV_ID,
814+
idempotencyKey: "key-1",
815+
});
816+
817+
expect(retry).toEqual({ waitpointId: idA, created: false });
818+
// The record must survive: a wrongly-discarded record would delete this too.
819+
expect(await probe.exists(`wp:{${idA}}`)).toBe(1);
820+
821+
// The real proof: something usable is still there for every later caller that
822+
// blocks on this id.
823+
const registered = await store.registerOrReport({
824+
waitpointId: idA,
825+
runId: "run_1",
826+
createdAt: NOW,
827+
});
828+
expect(registered.outcome).toBe("registered");
829+
} finally {
830+
probe.disconnect();
831+
await store.quit();
832+
}
833+
}
834+
);
835+
749836
redisTest("sets no expiry when the record carries none", async ({ redisOptions }) => {
750837
const store = coordinator(redisOptions);
751838
const probe = createRedisClient(redisOptions);
@@ -918,6 +1005,51 @@ describe("absorbBlockers", () => {
9181005
}
9191006
);
9201007

1008+
redisTest(
1009+
"a later absorb reads back the stored envelope, not a bare flag",
1010+
async ({ redisOptions }) => {
1011+
const store = coordinator(redisOptions);
1012+
try {
1013+
const envelope = completion({ output: { inline: '{"first":true}' } });
1014+
1015+
// Reported once, with an envelope — this write is what's under test.
1016+
await store.absorbBlockers({
1017+
runId: RUN_ID,
1018+
edges: [edge("w_a", { reported: { completion: envelope } })],
1019+
});
1020+
1021+
// Same waitpoint id, arriving unreported this time: takes the "read `done` back"
1022+
// path, exposing whatever the first call actually stored under that id.
1023+
const second = await store.absorbBlockers({ runId: RUN_ID, edges: [edge("w_a")] });
1024+
1025+
expect(second.alreadyDelivered).toHaveLength(1);
1026+
expect(second.alreadyDelivered[0]!.completion).toEqual(envelope);
1027+
} finally {
1028+
await store.quit();
1029+
}
1030+
}
1031+
);
1032+
1033+
redisTest(
1034+
"a later absorb for a no-envelope delivery reads back no completion",
1035+
async ({ redisOptions }) => {
1036+
const store = coordinator(redisOptions);
1037+
try {
1038+
await store.absorbBlockers({
1039+
runId: RUN_ID,
1040+
edges: [edge("w_a", { reported: {} })],
1041+
});
1042+
1043+
const second = await store.absorbBlockers({ runId: RUN_ID, edges: [edge("w_a")] });
1044+
1045+
expect(second.alreadyDelivered).toHaveLength(1);
1046+
expect(second.alreadyDelivered[0]!.completion).toBeUndefined();
1047+
} finally {
1048+
await store.quit();
1049+
}
1050+
}
1051+
);
1052+
9211053
redisTest("reports a repeated already-delivered id once", async ({ redisOptions }) => {
9221054
const store = coordinator(redisOptions);
9231055
try {
@@ -1544,3 +1676,162 @@ describe("the resume cycle drains and can start again", () => {
15441676
}
15451677
});
15461678
});
1679+
1680+
// Every test above is a sequence of awaits. Redis guarantees atomicity WITHIN a script, so
1681+
// those tests can only ever prove single-script invariants. These races drive real
1682+
// concurrent calls (Promise.all over N copies) against the multi-script TypeScript
1683+
// sequences, and assert an invariant that holds regardless of who wins — never a timing.
1684+
describe("genuine concurrency", () => {
1685+
const CONCURRENCY = 8;
1686+
1687+
redisTest(
1688+
"exactly one of N concurrent completers wins, and every caller sees its completion",
1689+
async ({ redisOptions }) => {
1690+
const store = coordinator(redisOptions);
1691+
try {
1692+
await store.createIfAbsent({ record: record("w_a"), status: "PENDING" });
1693+
await store.registerOrReport({ waitpointId: "w_a", runId: "run_1", createdAt: NOW });
1694+
await store.registerOrReport({ waitpointId: "w_a", runId: "run_2", createdAt: NOW });
1695+
1696+
const results = await Promise.all(
1697+
Array.from({ length: CONCURRENCY }, (_, i) =>
1698+
store.complete({
1699+
waitpointId: "w_a",
1700+
completion: completion({ output: { inline: `{"racer":${i}}` } }),
1701+
})
1702+
)
1703+
);
1704+
1705+
const winners = results.filter((r) => r.outcome === "completed");
1706+
const losers = results.filter((r) => r.outcome === "already");
1707+
expect(winners).toHaveLength(1);
1708+
expect(losers).toHaveLength(CONCURRENCY - 1);
1709+
1710+
// Every caller, winner and losers alike, reads back the SAME stored completion.
1711+
const stored = winners[0]!.completion;
1712+
for (const r of results) {
1713+
expect(r.completion).toEqual(stored);
1714+
}
1715+
1716+
// And every caller returns the full watcher list — a race must never truncate it.
1717+
for (const r of results) {
1718+
expect(r.watchers.map((w) => w.runId).sort()).toEqual(["run_1", "run_2"]);
1719+
}
1720+
} finally {
1721+
await store.quit();
1722+
}
1723+
}
1724+
);
1725+
1726+
redisTest(
1727+
"a pre-existing registration survives N concurrent attempts to re-register its field",
1728+
async ({ redisOptions }) => {
1729+
const store = coordinator(redisOptions);
1730+
try {
1731+
await store.createIfAbsent({ record: record("w_a"), status: "PENDING" });
1732+
await store.registerOrReport({
1733+
waitpointId: "w_a",
1734+
runId: "run_1",
1735+
spanIdToComplete: "span_first",
1736+
createdAt: NOW,
1737+
});
1738+
1739+
// Same run, same (absent) batch index as the registration above, so every one of
1740+
// these collides on the exact same watcher field.
1741+
await Promise.all(
1742+
Array.from({ length: CONCURRENCY }, (_, i) =>
1743+
store.registerOrReport({
1744+
waitpointId: "w_a",
1745+
runId: "run_1",
1746+
spanIdToComplete: `span_racer_${i}`,
1747+
createdAt: NOW,
1748+
})
1749+
)
1750+
);
1751+
1752+
const completed = await store.complete({ waitpointId: "w_a", completion: completion() });
1753+
const forRun1 = completed.watchers.filter((w) => w.runId === "run_1");
1754+
expect(forRun1).toHaveLength(1);
1755+
expect(forRun1[0]!.spanIdToComplete).toBe("span_first");
1756+
} finally {
1757+
await store.quit();
1758+
}
1759+
}
1760+
);
1761+
1762+
redisTest(
1763+
"exactly one of N concurrent idempotency-keyed creators wins, and every loser cleans up",
1764+
async ({ redisOptions }) => {
1765+
const store = coordinator(redisOptions);
1766+
const probe = createRedisClient(redisOptions);
1767+
try {
1768+
const ids = Array.from({ length: CONCURRENCY }, () => generateWaitpointId("MANUAL"));
1769+
1770+
const results = await Promise.all(
1771+
ids.map((id) =>
1772+
store.createWithIdempotencyKey({
1773+
record: record(id, { idempotencyKey: "key-1", userProvidedIdempotencyKey: true }),
1774+
environmentId: ENV_ID,
1775+
idempotencyKey: "key-1",
1776+
})
1777+
)
1778+
);
1779+
1780+
const winners = results.filter((r) => r.created);
1781+
expect(winners).toHaveLength(1);
1782+
1783+
const winnerId = winners[0]!.waitpointId;
1784+
for (const r of results) {
1785+
expect(r.waitpointId).toBe(winnerId);
1786+
}
1787+
expect(await probe.exists(`wp:{${winnerId}}`)).toBe(1);
1788+
1789+
for (const id of ids) {
1790+
if (id === winnerId) continue;
1791+
expect(await probe.exists(`wp:{${id}}`)).toBe(0);
1792+
expect(await probe.exists(`wp:{${id}}:w`)).toBe(0);
1793+
}
1794+
} finally {
1795+
probe.disconnect();
1796+
await store.quit();
1797+
}
1798+
}
1799+
);
1800+
1801+
redisTest(
1802+
"registerBlocks racing complete never leaves a waitpoint double-booked or the pending count negative",
1803+
async ({ redisOptions }) => {
1804+
const store = coordinator(redisOptions);
1805+
try {
1806+
for (let i = 0; i < 30; i++) {
1807+
const waitpointId = `w_race_${i}`;
1808+
const runId = `run_race_${i}`;
1809+
await store.createIfAbsent({ record: record(waitpointId), status: "PENDING" });
1810+
1811+
// Two edges for the SAME waitpoint: registerBlocks registers them one at a
1812+
// time, so a concurrent complete() has a real window to land between the two
1813+
// registrations — the exact straddle that makes absorbBlockers' per-group
1814+
// reported/unreported split matter, rather than racing a single all-or-nothing
1815+
// group.
1816+
const [blocked] = await Promise.all([
1817+
store.registerBlocks({
1818+
runId,
1819+
edges: [edge(waitpointId, { batchIndex: 0 }), edge(waitpointId, { batchIndex: 1 })],
1820+
}),
1821+
store.complete({ waitpointId, completion: completion() }),
1822+
]);
1823+
1824+
const state = await store.readBlockState(runId);
1825+
const delivered = state.deliveredIds.includes(waitpointId);
1826+
const pending = state.pendingIds.includes(waitpointId);
1827+
1828+
expect(delivered && pending).toBe(false);
1829+
expect(blocked.storePendingTotal).toBeGreaterThanOrEqual(0);
1830+
expect(blocked.storePendingTotal).toBeLessThanOrEqual(1);
1831+
}
1832+
} finally {
1833+
await store.quit();
1834+
}
1835+
}
1836+
);
1837+
});

0 commit comments

Comments
 (0)