@@ -536,35 +536,60 @@ function completeMultipartUpload(authInfo, request, log, callback) {
536536 function deletePartsMetadata ( mpuBucket , keysToDelete , aggregateETag ,
537537 extraPartLocations , destinationBucket , generatedVersionId , droppedMPUSize , next ) {
538538 services . batchDeleteObjectMetadata ( mpuBucket . getName ( ) ,
539- keysToDelete , log , err => next ( err , extraPartLocations ,
540- destinationBucket , aggregateETag , generatedVersionId , droppedMPUSize ) ) ;
539+ keysToDelete , log , err => {
540+ if ( err ) {
541+ // Handle specific error cases according to retry strategy
542+ if ( err . is ?. DeleteConflict ) {
543+ // DeleteConflict should trigger automatic retry
544+ // Convert to InternalError to make it retryable
545+ return next ( errors . InternalError , extraPartLocations ,
546+ destinationBucket , aggregateETag , generatedVersionId , droppedMPUSize ) ;
547+ }
548+
549+ // For NoSuchKey and other errors, return them as-is
550+ // NoSuchKey is non-retryable, InternalError and others are retryable
551+ return next ( err , extraPartLocations ,
552+ destinationBucket , aggregateETag , generatedVersionId , droppedMPUSize ) ;
553+ }
554+ return next ( null , extraPartLocations ,
555+ destinationBucket , aggregateETag , generatedVersionId , droppedMPUSize ) ;
556+ } ) ;
541557 } ,
542558 function batchDeleteExtraParts ( extraPartLocations , destinationBucket ,
543559 aggregateETag , generatedVersionId , droppedMPUSize , next ) {
544560 if ( extraPartLocations && extraPartLocations . length > 0 ) {
545561 return data . batchDelete ( extraPartLocations , request . method , null , log , err => {
546562 if ( err ) {
547- return next ( err ) ;
563+ // Extra part deletion failure should not fail the operation
564+ // The S3 object was created successfully and MPU metadata was cleaned up
565+ // Orphaned extra parts are acceptable since the main operation succeeded
566+ log . warn ( 'failed to delete extra parts, keeping orphan but returning success' , {
567+ method : 'completeMultipartUpload' ,
568+ extraPartLocationsCount : extraPartLocations . length ,
569+ error : err ,
570+ } ) ;
548571 }
549-
550- return validateQuotas ( request , destinationBucket , request . accountQuotas ,
551- [ 'objectDelete' ] , 'objectDelete' , - droppedMPUSize , false , log , err => {
552- if ( err ) {
553- // Ignore error, as the data has been deleted already: only inflight count
554- // has not been updated, and will be eventually consistent anyway
555- log . warn ( 'failed to update inflights' , {
556- method : 'completeMultipartUpload' ,
557- extraPartLocations,
558- error : err ,
559- } ) ;
560- }
561- return next ( null , destinationBucket , aggregateETag ,
562- generatedVersionId ) ;
563- } ) ;
572+ return next ( null , destinationBucket , aggregateETag ,
573+ generatedVersionId , droppedMPUSize ) ;
564574 } ) ;
565575 }
566576 return next ( null , destinationBucket , aggregateETag ,
567- generatedVersionId ) ;
577+ generatedVersionId , droppedMPUSize ) ;
578+ } ,
579+ function updateQuotas ( destinationBucket , aggregateETag , generatedVersionId , droppedMPUSize , next ) {
580+ return validateQuotas ( request , destinationBucket , request . accountQuotas ,
581+ [ 'objectDelete' ] , 'objectDelete' , - droppedMPUSize , false , log , err => {
582+ if ( err ) {
583+ // Ignore error, as the data has been deleted already: only inflight count
584+ // has not been updated, and will be eventually consistent anyway
585+ log . warn ( 'failed to update inflights' , {
586+ method : 'completeMultipartUpload' ,
587+ error : err ,
588+ } ) ;
589+ }
590+ return next ( null , destinationBucket , aggregateETag ,
591+ generatedVersionId ) ;
592+ } ) ;
568593 } ,
569594 ] , ( err , destinationBucket , aggregateETag , generatedVersionId ) => {
570595 const corsHeaders =
0 commit comments