Skip to content

Commit dd41264

Browse files
committed
fix(database,webapp): address review — exclude acquisition errors from serialization retry, clamp maxWait, narrow release note
1 parent 451773f commit dd41264

4 files changed

Lines changed: 30 additions & 2 deletions

File tree

.server-changes/transaction-resilience-during-db-interruptions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: improvement
44
---
55

6-
Triggering tasks is now more resilient to brief database interruptions, so short stalls are far less likely to surface as errors.
6+
Triggering tasks is now more resilient to brief, transient service interruptions, so short stalls are less likely to surface as errors.

apps/webapp/app/db.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function resolveTransactionResilience(
8888
overrides.budgetPerSec ?? env.DATABASE_TRANSACTION_START_RETRY_BUDGET_PER_SEC;
8989
const budgetBurst = overrides.budgetBurst ?? env.DATABASE_TRANSACTION_START_RETRY_BUDGET_BURST;
9090
return {
91-
maxWait: overrides.maxWaitMs ?? env.DATABASE_TRANSACTION_MAX_WAIT_MS,
91+
maxWait: Math.max(0, overrides.maxWaitMs ?? env.DATABASE_TRANSACTION_MAX_WAIT_MS),
9292
startRetry: {
9393
options: {
9494
enabled: overrides.enabled ?? env.DATABASE_TRANSACTION_START_RETRY_ENABLED,

internal-packages/database/src/transaction.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,31 @@ describe("$transaction startRetry wiring", () => {
186186
it("UNLIMITED_RETRY_BUDGET always consumes", () => {
187187
expect(UNLIMITED_RETRY_BUDGET.tryConsume()).toBe(true);
188188
});
189+
190+
it("does not let maxRetries retry an acquisition error beyond the startRetry budget", async () => {
191+
const prisma = { $transaction: vi.fn().mockRejectedValue(acquisitionError()) } as any;
192+
await expect(
193+
$transaction(
194+
prisma,
195+
async () => "x",
196+
() => {},
197+
{ startRetry: config({ maxAttempts: 2 }), maxRetries: 3 }
198+
)
199+
).rejects.toMatchObject({ code: "P2028" });
200+
expect(prisma.$transaction).toHaveBeenCalledTimes(2);
201+
});
202+
203+
it("still lets maxRetries retry a serialization error (P2034)", async () => {
204+
const serializationError = { code: "P2034", message: "write conflict / deadlock" };
205+
const prisma = { $transaction: vi.fn().mockRejectedValue(serializationError) } as any;
206+
await expect(
207+
$transaction(
208+
prisma,
209+
async () => "x",
210+
() => {},
211+
{ maxRetries: 2 }
212+
)
213+
).rejects.toMatchObject({ code: "P2034" });
214+
expect(prisma.$transaction).toHaveBeenCalledTimes(3);
215+
});
189216
});

internal-packages/database/src/transaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ export async function $transaction<R>(
239239
} catch (error) {
240240
if (
241241
isPrismaRetriableError(error) &&
242+
!isTransactionAcquisitionError(error) &&
242243
typeof options?.maxRetries === "number" &&
243244
attempt < options.maxRetries
244245
) {

0 commit comments

Comments
 (0)