@@ -9,8 +9,15 @@ import { Worker } from '../../../lib/worker';
99import { DatabaseReadWriteError , NonCriticalError } from '../../../lib/workerErrors' ;
1010import * as pkg from '../package.json' ;
1111import { ReleaseWorkerTask , ReleaseWorkerAddReleasePayload , CommitDataUnparsed } from '../types' ;
12- import { Collection , MongoClient } from 'mongodb' ;
12+ import { Collection , MongoClient , MongoError } from 'mongodb' ;
1313import { SourceMapDataExtended , SourceMapFileChunk , CommitData , SourcemapCollectedData , ReleaseDBScheme } from '@hawk.so/types' ;
14+
15+ /**
16+ * Error code of MongoDB key duplication error
17+ */
18+ /* eslint-disable @typescript-eslint/no-magic-numbers */
19+ const DB_DUPLICATE_KEY_ERROR = '11000' ;
20+
1421/**
1522 * Worker to save releases
1623 */
@@ -142,105 +149,104 @@ export default class ReleaseWorker extends Worker {
142149 * @param payload - source map data
143150 */
144151 private async saveSourceMap ( projectId : string , payload : ReleaseWorkerAddReleasePayload ) : Promise < void > {
152+ const files : SourceMapDataExtended [ ] = this . extendReleaseInfo ( payload . files ) ;
153+
145154 /**
146- * Start transaction to avoid race condition
155+ * Use same transaction for read and related write operations
147156 */
148- const session = await this . client . startSession ( ) ;
149-
150- try {
151- const files : SourceMapDataExtended [ ] = this . extendReleaseInfo ( payload . files ) ;
157+ const existedRelease = await this . releasesCollection . findOne ( {
158+ projectId : projectId ,
159+ release : payload . release ,
160+ } ) ;
152161
162+ /**
163+ * Iterate all maps of the new release and save only new
164+ */
165+ const savedFiles = await Promise . all ( files . map ( async ( map : SourceMapDataExtended ) => {
153166 /**
154- * Use same transaction for read and related write operations
167+ * Skip already saved maps
155168 */
156- await session . withTransaction ( async ( ) => {
157- const existedRelease = await this . releasesCollection . findOne ( {
158- projectId : projectId ,
159- release : payload . release ,
160- } , { session } ) ;
161-
162- /**
163- * Iterate all maps of the new release and save only new
164- */
165- const savedFiles = await Promise . all ( files . map ( async ( map : SourceMapDataExtended ) => {
166- /**
167- * Skip already saved maps
168- */
169-
170- const alreadySaved = existedRelease && existedRelease . files && existedRelease . files . find ( ( savedFile ) => {
171- return savedFile . mapFileName === map . mapFileName ;
172- } ) ;
169+ const alreadySaved = existedRelease && existedRelease . files && ! ! existedRelease . files . find ( ( savedFile ) => {
170+ return savedFile . mapFileName === map . mapFileName ;
171+ } ) ;
173172
174- if ( alreadySaved ) {
175- return ;
176- }
173+ if ( alreadySaved ) {
174+ return ;
175+ }
177176
178- try {
179- const fileInfo = await this . saveFile ( map ) ;
180-
181- /**
182- * Save id of saved file instead
183- */
184- return {
185- ...map ,
186- _id : fileInfo . _id ,
187- } ;
188- } catch ( error ) {
189- this . logger . error ( `Map ${ map . mapFileName } was not saved: ${ error } ` ) ;
190- }
191- } ) ) ;
177+ try {
178+ const fileInfo = await this . saveFile ( map ) ;
192179
193180 /**
194- * Filter undefined files and then prepare files that would be saved to releases table
195- * we do not need their content since it would be stored in gridFS
181+ * Save id of saved file instead
196182 */
197- const savedFilesWithoutContent : Omit < SourceMapDataExtended , 'content' > [ ] = savedFiles . filter ( file => {
198- return file !== undefined ;
199- } ) . map ( ( { content, ...rest } ) => {
200- return rest ;
201- } ) ;
183+ return {
184+ ...map ,
185+ _id : fileInfo . _id ,
186+ } ;
187+ } catch ( error ) {
188+ this . logger . error ( `Map ${ map . mapFileName } was not saved: ${ error } ` ) ;
189+ }
190+ } ) ) ;
202191
203- /**
204- * Nothing to save: maps was previously saved
205- */
206- if ( savedFilesWithoutContent . length === 0 ) {
207- return ;
208- }
192+ /**
193+ * Filter undefined files and then prepare files that would be saved to releases table
194+ * we do not need their content since it would be stored in gridFS
195+ */
196+ const savedFilesWithoutContent : Omit < SourceMapDataExtended , 'content' > [ ] = savedFiles . filter ( file => {
197+ return file !== undefined ;
198+ } ) . map ( ( { content, ...rest } ) => {
199+ return rest ;
200+ } ) ;
209201
210- /**
211- * - insert new record with saved maps
212- * or
213- * - update previous record with adding new saved maps
214- */
215- if ( ! existedRelease ) {
216- this . logger . info ( 'inserted new release' ) ;
202+ /**
203+ * Nothing to save: maps was previously saved
204+ */
205+ if ( savedFilesWithoutContent . length === 0 ) {
206+ return ;
207+ }
208+
209+ try {
210+ /**
211+ * - insert new record with saved maps
212+ * or
213+ * - update previous record with adding new saved maps
214+ */
215+ if ( ! existedRelease ) {
216+ this . logger . info ( 'trying insert new release' ) ;
217+
218+ try {
217219 await this . releasesCollection . insertOne ( {
218220 projectId : projectId ,
219221 release : payload . release ,
220222 files : savedFilesWithoutContent ,
221- } as ReleaseDBScheme , { session } ) ;
223+ } as ReleaseDBScheme ) ;
224+ this . logger . info ( 'inserted new release' ) ;
225+ } catch ( err ) {
226+ if ( ( err as MongoError ) . code . toString ( ) === DB_DUPLICATE_KEY_ERROR ) {
227+ this . logger . warn ( `Duplicate key on insert, retrying update after small delay` ) ;
228+ /* eslint-disable @typescript-eslint/no-magic-numbers */
229+ await new Promise ( resolve => setTimeout ( resolve , 200 ) ) ;
230+ } else {
231+ throw err ;
232+ }
222233 }
234+ }
223235
224- await this . releasesCollection . findOneAndUpdate ( {
225- projectId : projectId ,
226- release : payload . release ,
227- } , {
228- $push : {
229- files : {
230- $each : savedFilesWithoutContent ,
231- } ,
236+ await this . releasesCollection . findOneAndUpdate ( {
237+ projectId : projectId ,
238+ release : payload . release ,
239+ } , {
240+ $push : {
241+ files : {
242+ $each : savedFilesWithoutContent ,
232243 } ,
233- } , { session } ) ;
244+ } ,
234245 } ) ;
235246 } catch ( error ) {
236247 this . logger . error ( `Can't extract release info:\n${ JSON . stringify ( error ) } ` ) ;
237248
238249 throw new NonCriticalError ( 'Can\'t parse source-map file' ) ;
239- } finally {
240- /**
241- * End transaction
242- */
243- await session . endSession ( ) ;
244250 }
245251 }
246252
0 commit comments