From ce382a4bf9643109f90ca4a8bcaa15c87d2d33c2 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 24 Nov 2023 11:11:26 +0100 Subject: [PATCH] fix: prevent concurrent queries from running out of transaction (#5412) --- .../export-import-service.ts | 3 +- src/lib/util/allSettledWithRejection.test.ts | 47 +++++++++++++++++++ src/lib/util/allSettledWithRejection.ts | 16 +++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/lib/util/allSettledWithRejection.test.ts create mode 100644 src/lib/util/allSettledWithRejection.ts diff --git a/src/lib/features/export-import-toggles/export-import-service.ts b/src/lib/features/export-import-toggles/export-import-service.ts index 0857849dfb..483855afce 100644 --- a/src/lib/features/export-import-toggles/export-import-service.ts +++ b/src/lib/features/export-import-toggles/export-import-service.ts @@ -52,6 +52,7 @@ import { FeatureNameCheckResultWithFeaturePattern } from '../feature-toggle/feat import { IDependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model-type'; import groupBy from 'lodash.groupby'; import { ISegmentService } from '../../segments/segment-service-interface'; +import { allSettledWithRejection } from '../../util/allSettledWithRejection'; export type IImportService = { validate( @@ -252,7 +253,7 @@ export default class ExportImportService user: IUser, mode = 'regular' as Mode, ): Promise { - await Promise.all([ + await allSettledWithRejection([ this.verifyStrategies(dto), this.verifyContextFields(dto), this.importPermissionsService.verifyPermissions(dto, user, mode), diff --git a/src/lib/util/allSettledWithRejection.test.ts b/src/lib/util/allSettledWithRejection.test.ts new file mode 100644 index 0000000000..6ec44a4352 --- /dev/null +++ b/src/lib/util/allSettledWithRejection.test.ts @@ -0,0 +1,47 @@ +import { allSettledWithRejection } from './allSettledWithRejection'; + +describe('allSettledWithRejection', () => { + it('should resolve if all promises resolve', async () => { + const promises = [ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ]; + await expect(allSettledWithRejection(promises)).resolves.toEqual([ + 1, 2, 3, + ]); + }); + + it('should reject with the reason of the first rejected promise', async () => { + const error = new Error('First rejection'); + const promises = [ + Promise.reject(error), + Promise.resolve(1), + Promise.resolve(2), + ]; + await expect(allSettledWithRejection(promises)).rejects.toEqual(error); + }); + + it('should reject with the reason of the first rejected promise, even with multiple rejections', async () => { + const firstError = new Error('First rejection'); + const secondError = new Error('Second rejection'); + const promises = [ + Promise.reject(firstError), + Promise.reject(secondError), + Promise.resolve(1), + ]; + await expect(allSettledWithRejection(promises)).rejects.toEqual( + firstError, + ); + }); + + it('should reject with the reason of the first rejected promise in mixed scenarios', async () => { + const error = new Error('Rejection'); + const promises = [ + Promise.resolve(1), + Promise.reject(error), + Promise.resolve(2), + ]; + await expect(allSettledWithRejection(promises)).rejects.toEqual(error); + }); +}); diff --git a/src/lib/util/allSettledWithRejection.ts b/src/lib/util/allSettledWithRejection.ts new file mode 100644 index 0000000000..5953efe423 --- /dev/null +++ b/src/lib/util/allSettledWithRejection.ts @@ -0,0 +1,16 @@ +export const allSettledWithRejection = ( + promises: Promise[], +): Promise => + new Promise((resolve, reject) => { + Promise.allSettled(promises).then((results) => { + for (const result of results) { + if (result.status === 'rejected') { + reject(result.reason); + return; + } + } + resolve( + results.map((r) => (r as PromiseFulfilledResult).value), + ); + }); + });