mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
49 lines
2.3 KiB
JavaScript
49 lines
2.3 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name: withTransactionRetry.util.js
|
|
* Type of Program: Utility
|
|
* Description: Runs a Sequelize transaction, automatically retrying on serialization_failure
|
|
* (SQLSTATE 40001) — the transient "restart transaction" error CockroachDB (and
|
|
* Postgres SERIALIZABLE) throw when concurrent transactions can't be safely
|
|
* ordered. Without a retry, these surface as unhandled 500s on otherwise-correct
|
|
* code the moment two requests touch the same row close together.
|
|
* Author: Kenneth Obsequio (@lash0000)
|
|
* Date Created: Jul. 17, 2026
|
|
***********************************************************************************************************************************************************************
|
|
* HOW TO USE:
|
|
* const { withTransactionRetry } = require('../utils/withTransactionRetry.util');
|
|
* const result = await withTransactionRetry(sequelize, async (t) => {
|
|
* await Model.upsert({...}, { transaction: t });
|
|
* return something;
|
|
* });
|
|
***********************************************************************************************************************************************************************/
|
|
'use strict';
|
|
|
|
const SERIALIZATION_FAILURE = '40001';
|
|
const MAX_RETRIES = 5;
|
|
const BASE_DELAY_MS = 50;
|
|
|
|
function isSerializationFailure(err) {
|
|
return err?.parent?.code === SERIALIZATION_FAILURE || err?.original?.code === SERIALIZATION_FAILURE;
|
|
}
|
|
|
|
async function withTransactionRetry(sequelize, fn, { maxRetries = MAX_RETRIES } = {}) {
|
|
for (let attempt = 0; ; attempt++) {
|
|
const t = await sequelize.transaction();
|
|
try {
|
|
const result = await fn(t);
|
|
await t.commit();
|
|
return result;
|
|
} catch (err) {
|
|
await t.rollback();
|
|
if (isSerializationFailure(err) && attempt < maxRetries) {
|
|
const delay = BASE_DELAY_MS * 2 ** attempt + Math.random() * BASE_DELAY_MS;
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
continue;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = { withTransactionRetry, isSerializationFailure };
|