prisma-client-extensions
prisma-client-extensions copied to clipboard
Callback Free Interactive Transactions -- exhausting connection pools in Prisma 5
We've been using (a variant of) the callback-free-itx extension with Prisma 4 in production for a while now, it's been rock solid. We're exploring the upgrade to Prisma 5 and finding that periodically we're getting "unable to start a transaction in the given time" P2028 errors. We've narrowed it down to the callback free interactive transaction code. If we don't use transactions at all then we're stable.
I swapped over our implementation to be the exact one in this repository and we're seeing the same behavior. I don't have a small reproduction case yet, I'm opening this ticket for visibility and in the hope that somebody else has seen this on Prisma 5 and perhaps can point me in the right direction.
Incase this is helpful for anyone finding this from a search, I'm seeing good progress by not using a proxy object and instead returning a brand new object in $begin. For us that looks like:
return {
$commit: async () => {
commit();
await prismaTranactionResult;
},
$rollback: async () => {
rollback();
await prismaTranactionResult.catch((err) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (err.message !== PRISMA_ROLLBACK_MSG) {
console.log(`Rollback txn, cause: ${err}`);
}
});
},
...capturedPrismaTxClient,
$executeRaw: capturedPrismaTxClient.$executeRaw,
$executeRawUnsafe: capturedPrismaTxClient.$executeRawUnsafe,
// @ts-expect-error we're copying an internal method
$executeRawInternal: capturedPrismaTxClient.$executeRawInternal,
// @ts-expect-error we're copying an internal method
_createPrismaPromise: capturedPrismaTxClient._createPrismaPromise,
// @ts-expect-error we're copying an internal method
_request: capturedPrismaTxClient._request,
// @ts-expect-error we're copying an internal method
_executeRequest: capturedPrismaTxClient._executeRequest,
};
we got similar issue can you please share your implementation or point me what needs to be fixed here?
{
code: 'P2028',
clientVersion: '5.4.2',
meta: {
error: "Transaction not found. Transaction ID is invalid, refers to an old closed transaction Prisma doesn't have information about anymore, or was obtained before disconnecting."
}
}
this is our implementation
public readonly prisma = new PrismaClient({ log: ['query'] }).$extends({
client: {
async $begin() {
const prisma = Prisma.getExtensionContext(this);
let setTxClient: (txClient: Prisma.TransactionClient) => void;
let commit: () => void;
let rollback: () => void;
// a promise for getting the tx inner client
const txClient = new Promise<Prisma.TransactionClient>((res) => {
setTxClient = (txClient) => res(txClient);
});
// a promise for controlling the transaction
const txPromise = new Promise((_res, _rej) => {
commit = () => _res(undefined);
rollback = () => _rej(this.ROLLBACK);
});
// opening a transaction to control externally
if (
'$transaction' in prisma &&
typeof prisma.$transaction === 'function'
) {
const tx = prisma.$transaction((txClient) => {
setTxClient(txClient as unknown as Prisma.TransactionClient);
return txPromise.catch((e) => {
if (e === this.ROLLBACK) return;
throw e;
});
});
// return a proxy TransactionClient with `$commit` and `$rollback` methods
return new Proxy(await txClient, {
get(target, prop) {
if (prop === '$commit') {
return () => {
commit();
return tx;
};
}
if (prop === '$rollback') {
return () => {
rollback();
return tx;
};
}
return target[prop as keyof typeof target];
},
}) as FlatTransactionClient;
}
throw new Error('Transactions are not supported by this client');
},
},
});