@@ -320,15 +320,31 @@ const getAWSConfig = ({ accessKeyId, secretAccessKey, sessionToken }: AWSAccount
320320 sessionToken,
321321 } ,
322322 ...( region ? { region } : { } ) ,
323- maxRetries : 10 ,
323+ maxRetries : 5 , // Reduced from 10 to prevent excessive retries
324+ retryDelayOptions : {
325+ customBackoff : ( retryCount : number ) => Math . pow ( 2 , retryCount ) * 1000 , // Exponential backoff
326+ } ,
327+ requestHandler : {
328+ connectionTimeout : 30000 ,
329+ socketTimeout : 30000 ,
330+ maxSockets : 25 , // Reduced from default 50 to prevent socket exhaustion
331+ } ,
324332} ) ;
325333
326334// Client cache to reuse clients and reduce memory usage
327335const clientCache = new Map < string , any > ( ) ;
336+ const MAX_CACHE_SIZE = 50 ; // Limit cache size to prevent memory issues
328337
329338const getClient = < T > ( ClientClass : new ( config : any ) => T , account : AWSAccountInfo , region ?: string ) : T => {
330339 const key = `${ account . accessKeyId } -${ region || 'global' } -${ ClientClass . name } ` ;
331340 if ( ! clientCache . has ( key ) ) {
341+ // Clear cache if it gets too large
342+ if ( clientCache . size >= MAX_CACHE_SIZE ) {
343+ clientCache . clear ( ) ;
344+ if ( global . gc ) {
345+ global . gc ( ) ;
346+ }
347+ }
332348 clientCache . set ( key , new ClientClass ( getAWSConfig ( account , region ) ) ) ;
333349 }
334350 return clientCache . get ( key ) ;
@@ -340,12 +356,16 @@ const getClient = <T>(ClientClass: new (config: any) => T, account: AWSAccountIn
340356const deleteS3Bucket = async ( bucket : string , providedS3Client : S3Client | undefined = undefined ) => {
341357 const s3 = providedS3Client || new S3Client ( { } ) ;
342358 let continuationToken : string | undefined = undefined ;
343- const objectKeyAndVersion : { Key : string ; VersionId ?: string } [ ] = [ ] ;
344359 let truncated = true ;
360+
345361 while ( truncated ) {
362+ const objectKeyAndVersion : { Key : string ; VersionId ?: string } [ ] = [ ] ;
363+
364+ // Process in smaller batches to reduce memory usage
346365 const results = await s3 . send (
347366 new ListObjectVersionsCommand ( {
348367 Bucket : bucket ,
368+ MaxKeys : 500 , // Reduced from default to limit memory usage
349369 ...( continuationToken ? { KeyMarker : continuationToken } : { } ) ,
350370 } ) ,
351371 ) ;
@@ -362,20 +382,42 @@ const deleteS3Bucket = async (bucket: string, providedS3Client: S3Client | undef
362382 }
363383 } ) ;
364384
385+ // Delete objects in smaller chunks with rate limiting
386+ if ( objectKeyAndVersion . length > 0 ) {
387+ const chunkedResult = _ . chunk ( objectKeyAndVersion , 500 ) ; // Reduced chunk size
388+ for ( const chunk of chunkedResult ) {
389+ try {
390+ await s3 . send (
391+ new DeleteObjectsCommand ( {
392+ Bucket : bucket ,
393+ Delete : {
394+ Objects : chunk ,
395+ Quiet : true ,
396+ } ,
397+ } ) ,
398+ ) ;
399+ // Add small delay to prevent rate limiting
400+ await sleep ( 100 ) ;
401+ } catch ( error : any ) {
402+ if ( error . message ?. includes ( 'Please reduce your request rate' ) ) {
403+ console . log ( `Rate limited while deleting objects from ${ bucket } , waiting...` ) ;
404+ await sleep ( 5000 ) ;
405+ } else {
406+ throw error ;
407+ }
408+ }
409+ }
410+ }
411+
365412 continuationToken = results . NextKeyMarker ;
366413 truncated = ! ! results . IsTruncated ;
414+
415+ // Force garbage collection periodically
416+ if ( global . gc && Math . random ( ) < 0.1 ) {
417+ global . gc ( ) ;
418+ }
367419 }
368- const chunkedResult = _ . chunk ( objectKeyAndVersion , 1000 ) ;
369- const deleteReq = chunkedResult
370- . map ( ( r ) => ( {
371- Bucket : bucket ,
372- Delete : {
373- Objects : r ,
374- Quiet : true ,
375- } ,
376- } ) )
377- . map ( ( delParams ) => s3 . send ( new DeleteObjectsCommand ( delParams ) ) ) ;
378- await Promise . all ( deleteReq ) ;
420+
379421 await s3 . send (
380422 new DeleteBucketCommand ( {
381423 Bucket : bucket ,
@@ -848,7 +890,13 @@ const deleteIamRolePolicy = async (account: AWSAccountInfo, accountIndex: number
848890} ;
849891
850892const deleteBuckets = async ( account : AWSAccountInfo , accountIndex : number , buckets : S3BucketInfo [ ] ) : Promise < void > => {
851- await Promise . all ( buckets . slice ( 0 , DELETE_LIMITS . PER_BATCH . OTHER ) . map ( ( bucket ) => deleteBucket ( account , accountIndex , bucket ) ) ) ;
893+ // Process buckets sequentially to avoid memory issues
894+ const bucketsToDelete = buckets . slice ( 0 , DELETE_LIMITS . PER_BATCH . OTHER ) ;
895+ for ( const bucket of bucketsToDelete ) {
896+ await deleteBucket ( account , accountIndex , bucket ) ;
897+ // Small delay between bucket deletions to prevent rate limiting
898+ await sleep ( 1000 ) ;
899+ }
852900} ;
853901
854902const deleteBucket = async ( account : AWSAccountInfo , accountIndex : number , bucket : S3BucketInfo ) : Promise < void > => {
@@ -862,11 +910,18 @@ const deleteBucket = async (account: AWSAccountInfo, accountIndex: number, bucke
862910 const bucketRegion = locationResponse . LocationConstraint || 'us-east-1' ;
863911 const regionalS3Client = new S3Client ( getAWSConfig ( account , bucketRegion ) ) ;
864912 await deleteS3Bucket ( name , regionalS3Client ) ;
865- } catch ( e ) {
866- console . log ( `[ACCOUNT ${ accountIndex } ] Deleting bucket ${ name } failed with error ${ e . message } ` ) ;
867- if ( e . name === 'ExpiredTokenException' ) {
913+ } catch ( e : unknown ) {
914+ const errorMessage = e instanceof Error ? e . message : String ( e ) ;
915+ const errorName = e instanceof Error ? e . name : 'UnknownError' ;
916+
917+ console . log ( `[ACCOUNT ${ accountIndex } ] Deleting bucket ${ name } failed with error ${ errorMessage } ` ) ;
918+ if ( errorName === 'ExpiredTokenException' ) {
868919 handleExpiredTokenException ( ) ;
869920 }
921+ if ( errorMessage . includes ( 'Please reduce your request rate' ) ) {
922+ console . log ( `Rate limited while deleting bucket ${ name } , will retry later` ) ;
923+ return ;
924+ }
870925 }
871926 }
872927} ;
0 commit comments