@@ -383,8 +383,27 @@ public async Task StartDownloadsAsync(DownloadDiscoveryResult discoveryResult, E
383383
384384 _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] Buffer space acquired" , partNum ) ;
385385
386- var task = CreateDownloadTaskAsync ( partNum , discoveryResult . ObjectSize , wrappedCallback , internalCts . Token ) ;
387- downloadTasks . Add ( task ) ;
386+ _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] Waiting for HTTP concurrency slot (Available: {1}/{2})" ,
387+ partNum , _httpConcurrencySlots . CurrentCount , _config . ConcurrentServiceRequests ) ;
388+
389+ // Acquire HTTP slot in the loop before creating task
390+ // Loop will block here if all slots are in use
391+ await _httpConcurrencySlots . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
392+
393+ _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] HTTP concurrency slot acquired" , partNum ) ;
394+
395+ try
396+ {
397+ var task = CreateDownloadTaskAsync ( partNum , discoveryResult . ObjectSize , wrappedCallback , internalCts . Token ) ;
398+ downloadTasks . Add ( task ) ;
399+ }
400+ catch
401+ {
402+ // If task creation fails, release the HTTP slot we just acquired
403+ _httpConcurrencySlots . Release ( ) ;
404+ _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] HTTP concurrency slot released due to task creation failure" , partNum ) ;
405+ throw ;
406+ }
388407 }
389408
390409 var expectedTaskCount = downloadTasks . Count ;
@@ -459,15 +478,8 @@ private async Task CreateDownloadTaskAsync(int partNumber, long objectSize, Even
459478
460479 try
461480 {
462- _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] Waiting for HTTP concurrency slot (Available: {1}/{2})" ,
463- partNumber , _httpConcurrencySlots . CurrentCount , _config . ConcurrentServiceRequests ) ;
464-
465- // Limit HTTP concurrency for both network download AND disk write
466- // The semaphore is held until AFTER ProcessPartAsync completes to ensure
467- // ConcurrentServiceRequests controls the entire I/O operation
468- await _httpConcurrencySlots . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
469-
470- _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] HTTP concurrency slot acquired" , partNumber ) ;
481+ // HTTP slot was already acquired in the for loop before this task was created
482+ // We just need to use it and release it when done
471483
472484 try
473485 {
@@ -544,7 +556,7 @@ private async Task CreateDownloadTaskAsync(int partNumber, long objectSize, Even
544556 finally
545557 {
546558 // Release semaphore after BOTH network download AND disk write complete
547- // This ensures ConcurrentServiceRequests limits the entire I/O operation
559+ // Slot was acquired in the for loop before this task was created
548560 _httpConcurrencySlots . Release ( ) ;
549561 _logger . DebugFormat ( "MultipartDownloadManager: [Part {0}] HTTP concurrency slot released (Available: {1}/{2})" ,
550562 partNumber , _httpConcurrencySlots . CurrentCount , _config . ConcurrentServiceRequests ) ;
0 commit comments