mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
test(integration): retry a truncate that lost a deadlock
The suite wipes the database between files with one TRUNCATE, which needs the strictest lock on every table at the same time. The server writes in thirty-nine places without awaiting the result, so one of those can land after the test that caused it has returned, and Postgres then picks the truncate as the deadlock victim. It failed an unrelated file twice in two days and passed on the rerun both times. The truncate is retried up to four times, on that one error and no other, with a short back-off. Three cases hold it there: a deadlock that clears, an error that is not a deadlock and must not be retried at all, and a deadlock that never clears and must still fail. All three go red without the retry.
This commit is contained in:
1 parent
655eafa757
commit
42b9aefdfd
2 files changed
+86
-3
No files matched your search
@@ -101,9 +101,29 @@ export async function truncateAllTables(client: PrismaClient): Promise<void> {
|
||||
}
|
||||
|
||||
const quoted = tables.map((t) => `"${t}"`).join(", ");
|
||||
await client.$executeRawUnsafe(
|
||||
`TRUNCATE TABLE ${quoted} RESTART IDENTITY CASCADE;`,
|
||||
);
|
||||
// A truncate takes the strictest lock there is on every table at once, so it
|
||||
// loses to anything still holding a row lock. Thirty-nine places in the
|
||||
// server write without awaiting the result (a last-used stamp, a delivery
|
||||
// record, an audit row), and one of those can land after the test that
|
||||
// triggered it has returned. Postgres then reports a deadlock and kills the
|
||||
// truncate, which fails an unrelated file and passes on the next run.
|
||||
// Retrying is the honest answer: a deadlock means somebody else got there
|
||||
// first, and by the next attempt the stray write has finished. Only 40P01 is
|
||||
// retried, so a lock held for good still fails as it should.
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
try {
|
||||
await client.$executeRawUnsafe(
|
||||
`TRUNCATE TABLE ${quoted} RESTART IDENTITY CASCADE;`,
|
||||
);
|
||||
break;
|
||||
} catch (error) {
|
||||
const code = (error as { code?: string }).code;
|
||||
const deadlock =
|
||||
code === "40P01" || /deadlock detected/i.test(String(error));
|
||||
if (!deadlock || attempt >= 4) throw error;
|
||||
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
// Categories first — the tag rows FK into them.
|
||||
if (categorySnapshot.length > 0) {
|
||||
|
||||
Reference in new issue
Block a user