diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartDownloadManager.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartDownloadManager.cs index 78145dba7bcf..209941c5ab57 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartDownloadManager.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartDownloadManager.cs @@ -440,7 +440,23 @@ public async Task StartDownloadsAsync(DownloadDiscoveryResult discoveryResult, E catch (Exception ex) { _downloadException = ex; - _logger.Error(ex, "MultipartDownloadManager: Background download task failed"); + + + + // Cancel all remaining downloads immediately to prevent cascading timeout errors + // This ensures that when one part fails, other tasks stop gracefully instead of + // continuing until they hit their own timeout/cancellation errors + try + { + internalCts.Cancel(); + _logger.DebugFormat("MultipartDownloadManager: Cancelled all in-flight downloads due to error"); + } + catch (ObjectDisposedException) + { + // CancellationTokenSource was already disposed, ignore + _logger.DebugFormat("MultipartDownloadManager: CancellationTokenSource already disposed during cancellation"); + } + _dataHandler.OnDownloadComplete(ex); throw; } @@ -462,6 +478,18 @@ public async Task StartDownloadsAsync(DownloadDiscoveryResult discoveryResult, E _downloadException = ex; _logger.Error(ex, "MultipartDownloadManager: Download failed"); + // Cancel all remaining downloads immediately to prevent cascading timeout errors + try + { + internalCts.Cancel(); + _logger.DebugFormat("MultipartDownloadManager: Cancelled all in-flight downloads due to error"); + } + catch (ObjectDisposedException) + { + // CancellationTokenSource was already disposed, ignore + _logger.DebugFormat("MultipartDownloadManager: CancellationTokenSource already disposed during cancellation"); + } + _dataHandler.OnDownloadComplete(ex); // Dispose the CancellationTokenSource if background task was never started