fix race condition issue

This commit is contained in:
2026-05-20 13:16:25 +07:00
parent 43ea725107
commit da217c2946
3 changed files with 445 additions and 421 deletions
+47
View File
@@ -0,0 +1,47 @@
/**
* Helper transaksi Serializable + retry. Single source of truth supaya semua
* operasi yang rawan race (join/cancel trip, refund, payment) konsisten:
* - Isolation level Serializable → Postgres SSI mendeteksi konflik.
* - Retry otomatis saat serialization failure (Prisma error code `P2034`).
*/
import { Prisma } from "@/app/generated/prisma/client";
import { prisma } from "@/lib/prisma";
const SERIAL_TX_ATTEMPTS = 6;
export function isSerializationConflict(err: unknown): boolean {
return (
typeof err === "object" &&
err !== null &&
"code" in err &&
(err as { code: string }).code === "P2034"
);
}
/**
* Jalankan `fn` dalam transaksi Serializable. Kalau gagal karena konflik
* serialisasi, ulang sampai `SERIAL_TX_ATTEMPTS` kali. Error bisnis (non-P2034)
* langsung dilempar tanpa retry.
*/
export async function runSerializable<T>(
fn: (tx: Prisma.TransactionClient) => Promise<T>,
fallbackMessage = "Operasi sedang ramai. Coba lagi sebentar."
): Promise<T> {
let lastErr: unknown;
for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) {
try {
return await prisma.$transaction(fn, {
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
maxWait: 5000,
timeout: 15000,
});
} catch (e) {
lastErr = e;
if (isSerializationConflict(e) && attempt < SERIAL_TX_ATTEMPTS - 1) {
continue;
}
throw e;
}
}
throw lastErr instanceof Error ? lastErr : new Error(fallbackMessage);
}