@@ -414,7 +414,9 @@ private async Task StartBackgroundDownloadsAsync(DownloadResult downloadResult,
414414 _logger . DebugFormat ( "MultipartDownloadManager: Background task waiting for {0} download tasks" , expectedTaskCount ) ;
415415
416416 // Wait for all downloads to complete (fails fast on first exception)
417- await TaskHelpers . WhenAllOrFirstExceptionAsync ( downloadTasks , internalCts . Token ) . ConfigureAwait ( false ) ;
417+ // Use fault-priority variant to ensure original exceptions propagate instead of
418+ // OperationCanceledException when cancellation occurs due to a task failure
419+ await TaskHelpers . WhenAllOrFirstExceptionWithFaultPriorityAsync ( downloadTasks , internalCts . Token ) . ConfigureAwait ( false ) ;
418420
419421 _logger . DebugFormat ( "MultipartDownloadManager: All download tasks completed successfully" ) ;
420422
@@ -451,13 +453,21 @@ private async Task CreateDownloadTasksAsync(DownloadResult downloadResult, Event
451453 // Pre-acquire capacity in sequential order to prevent race condition deadlock
452454 // This ensures Part 2 gets capacity before Part 3, etc., preventing out-of-order
453455 // parts from consuming all buffer slots and blocking the next expected part
454- for ( int partNum = 2 ; partNum <= downloadResult . TotalParts ; partNum ++ )
456+ for ( int partNum = 2 ; partNum <= downloadResult . TotalParts && ! internalCts . IsCancellationRequested ; partNum ++ )
455457 {
456458 _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] Waiting for buffer space" , partNum ) ;
457459
458460 // Acquire capacity sequentially - guarantees Part 2 before Part 3, etc.
459461 await _dataHandler . WaitForCapacityAsync ( internalCts . Token ) . ConfigureAwait ( false ) ;
460462
463+ // Check cancellation after acquiring capacity - a task may have failed while waiting
464+ if ( internalCts . IsCancellationRequested )
465+ {
466+ _logger . InfoFormat ( "MultipartDownloadManager: [Part {0}] Stopping early - cancellation requested after capacity acquired" , partNum ) ;
467+ _dataHandler . ReleaseCapacity ( ) ;
468+ break ;
469+ }
470+
461471 _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] Buffer space acquired" , partNum ) ;
462472
463473 _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] Waiting for HTTP concurrency slot (Available: {1}/{2})" ,
@@ -466,22 +476,58 @@ private async Task CreateDownloadTasksAsync(DownloadResult downloadResult, Event
466476 // Acquire HTTP slot in the loop before creating task
467477 // Loop will block here if all slots are in use
468478 await _httpConcurrencySlots . WaitAsync ( internalCts . Token ) . ConfigureAwait ( false ) ;
479+
480+ // Check cancellation after acquiring HTTP slot - a task may have failed while waiting
481+ if ( internalCts . IsCancellationRequested )
482+ {
483+ _logger . InfoFormat ( "MultipartDownloadManager: [Part {0}] Stopping early - cancellation requested after HTTP slot acquired" , partNum ) ;
484+ _httpConcurrencySlots . Release ( ) ;
485+ _dataHandler . ReleaseCapacity ( ) ;
486+ break ;
487+ }
469488
470489 _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] HTTP concurrency slot acquired" , partNum ) ;
471490
472491 try
473492 {
474493 var task = CreateDownloadTaskAsync ( partNum , downloadResult . ObjectSize , wrappedCallback , internalCts . Token ) ;
494+
495+ // Add failure detection to immediately cancel internal token on first error
496+ // This prevents the for loop from queuing additional parts after a failure
497+ _ = task . ContinueWith ( t =>
498+ {
499+ if ( t . IsFaulted && ! internalCts . IsCancellationRequested )
500+ {
501+ // Then cancel to stop queuing more parts
502+ // Note: The original exception will be propagated by WhenAllOrFirstExceptionAsync
503+ try
504+ {
505+ internalCts . Cancel ( ) ;
506+ _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] Cancelled internal token to stop queuing" , partNum ) ;
507+ }
508+ catch ( ObjectDisposedException )
509+ {
510+ _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] CancellationTokenSource already disposed during cancellation" , partNum ) ;
511+ }
512+ }
513+ } , CancellationToken . None , TaskContinuationOptions . ExecuteSynchronously , TaskScheduler . Default ) ;
514+
475515 downloadTasks . Add ( task ) ;
476516 }
477517 catch ( Exception ex )
478518 {
479519 // If task creation fails, release the HTTP slot we just acquired
480520 _httpConcurrencySlots . Release ( ) ;
521+ _dataHandler . ReleaseCapacity ( ) ;
481522 _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] HTTP concurrency slot released due to task creation failure: {1}" , partNum , ex ) ;
482523 throw ;
483524 }
484525 }
526+
527+ if ( internalCts . IsCancellationRequested && downloadTasks . Count < downloadResult . TotalParts - 1 )
528+ {
529+ _logger . InfoFormat ( "MultipartDownloadManager: Stopped queuing early at {0} parts due to cancellation" , downloadTasks . Count ) ;
530+ }
485531 }
486532
487533 /// <summary>
0 commit comments