@@ -44,6 +44,7 @@ internal class MultipartDownloadManager : IDownloadManager
4444 private readonly DownloadManagerConfiguration _config ;
4545 private readonly IPartDataHandler _dataHandler ;
4646 private readonly SemaphoreSlim _httpConcurrencySlots ;
47+ private readonly bool _ownsHttpThrottler ;
4748 private readonly RequestEventHandler _requestEventHandler ;
4849
4950 private Exception _downloadException ;
@@ -79,23 +80,82 @@ private Logger Logger
7980 public Task DownloadCompletionTask => _downloadCompletionTask ?? Task . CompletedTask ;
8081
8182 /// <summary>
82- /// Initializes a new instance of the <see cref="MultipartDownloadManager"/> class.
83+ /// Initializes a new instance of the <see cref="MultipartDownloadManager"/> for single file downloads.
84+ /// This constructor creates and owns its own HTTP concurrency throttler based on the configuration.
8385 /// </summary>
84- /// <param name="s3Client">The <see cref="IAmazonS3"/> client for making S3 requests.</param>
85- /// <param name="request">The <see cref="BaseDownloadRequest"/> containing download parameters.</param>
86- /// <param name="config">The <see cref="DownloadManagerConfiguration"/> with download settings.</param>
87- /// <param name="dataHandler">The <see cref="IPartDataHandler"/> for processing downloaded parts.</param>
88- /// <param name="requestEventHandler">Optional <see cref="RequestEventHandler"/> for user agent tracking.</param>
89- /// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
86+ /// <param name="s3Client">The <see cref="IAmazonS3"/> client used to make GetObject requests to S3.</param>
87+ /// <param name="request">The <see cref="BaseDownloadRequest"/> containing bucket, key, version, and download strategy configuration.</param>
88+ /// <param name="config">The <see cref="DownloadManagerConfiguration"/> specifying concurrency limits and part size settings.</param>
89+ /// <param name="dataHandler">The <see cref="IPartDataHandler"/> responsible for buffering and processing downloaded part data.</param>
90+ /// <param name="requestEventHandler">Optional request event handler for adding custom headers or tracking requests. May be null.</param>
91+ /// <exception cref="ArgumentNullException">
92+ /// Thrown when <paramref name="s3Client"/>, <paramref name="request"/>, <paramref name="config"/>, or <paramref name="dataHandler"/> is null.
93+ /// </exception>
94+ /// <remarks>
95+ /// This constructor is used for single file downloads where each download manages its own HTTP concurrency.
96+ /// The created <see cref="SemaphoreSlim"/> throttler will be disposed when this instance is disposed.
97+ /// For directory downloads with shared concurrency management, use the overload that accepts a shared throttler.
98+ /// </remarks>
99+ /// <seealso cref="DownloadManagerConfiguration"/>
100+ /// <seealso cref="IPartDataHandler"/>
101+ /// <seealso cref="MultipartDownloadType"/>
90102 public MultipartDownloadManager ( IAmazonS3 s3Client , BaseDownloadRequest request , DownloadManagerConfiguration config , IPartDataHandler dataHandler , RequestEventHandler requestEventHandler = null )
103+ : this ( s3Client , request , config , dataHandler , requestEventHandler , null )
104+ {
105+ }
106+
107+ /// <summary>
108+ /// Initializes a new instance of the <see cref="MultipartDownloadManager"/> for directory downloads or scenarios requiring shared concurrency control.
109+ /// This constructor allows using a shared HTTP concurrency throttler across multiple concurrent file downloads.
110+ /// </summary>
111+ /// <param name="s3Client">The <see cref="IAmazonS3"/> client used to make GetObject requests to S3.</param>
112+ /// <param name="request">The <see cref="BaseDownloadRequest"/> containing bucket, key, version, and download strategy configuration.</param>
113+ /// <param name="config">The <see cref="DownloadManagerConfiguration"/> specifying concurrency limits and part size settings.</param>
114+ /// <param name="dataHandler">The <see cref="IPartDataHandler"/> responsible for buffering and processing downloaded part data.</param>
115+ /// <param name="requestEventHandler">Optional request event handler for adding custom headers or tracking requests. May be null.</param>
116+ /// <param name="sharedHttpThrottler">
117+ /// Optional shared <see cref="SemaphoreSlim"/> for coordinating HTTP concurrency across multiple downloads.
118+ /// If null, a new throttler will be created and owned by this instance.
119+ /// If provided, the caller retains ownership and responsibility for disposal.
120+ /// </param>
121+ /// <exception cref="ArgumentNullException">
122+ /// Thrown when <paramref name="s3Client"/>, <paramref name="request"/>, <paramref name="config"/>, or <paramref name="dataHandler"/> is null.
123+ /// </exception>
124+ /// <remarks>
125+ /// <para>
126+ /// This constructor is typically used by directory download operations where multiple files are being downloaded
127+ /// concurrently and need to share a global HTTP concurrency limit.
128+ /// </para>
129+ /// <para>
130+ /// <strong>Resource Ownership:</strong>
131+ /// If <paramref name="sharedHttpThrottler"/> is provided, this instance does NOT take ownership and will NOT dispose it.
132+ /// If <paramref name="sharedHttpThrottler"/> is null, this instance creates and owns the throttler and will dispose it.
133+ /// </para>
134+ /// </remarks>
135+ /// <seealso cref="DownloadManagerConfiguration"/>
136+ /// <seealso cref="IPartDataHandler"/>
137+ /// <seealso cref="MultipartDownloadType"/>
138+ /// <seealso cref="DiscoverDownloadStrategyAsync"/>
139+ /// <seealso cref="StartDownloadsAsync"/>
140+ public MultipartDownloadManager ( IAmazonS3 s3Client , BaseDownloadRequest request , DownloadManagerConfiguration config , IPartDataHandler dataHandler , RequestEventHandler requestEventHandler , SemaphoreSlim sharedHttpThrottler )
91141 {
92142 _s3Client = s3Client ?? throw new ArgumentNullException ( nameof ( s3Client ) ) ;
93143 _request = request ?? throw new ArgumentNullException ( nameof ( request ) ) ;
94144 _config = config ?? throw new ArgumentNullException ( nameof ( config ) ) ;
95145 _dataHandler = dataHandler ?? throw new ArgumentNullException ( nameof ( dataHandler ) ) ;
96146 _requestEventHandler = requestEventHandler ;
97147
98- _httpConcurrencySlots = new SemaphoreSlim ( _config . ConcurrentServiceRequests ) ;
148+ // Use shared throttler if provided, otherwise create our own
149+ if ( sharedHttpThrottler != null )
150+ {
151+ _httpConcurrencySlots = sharedHttpThrottler ;
152+ _ownsHttpThrottler = false ; // Don't dispose - directory command owns it
153+ }
154+ else
155+ {
156+ _httpConcurrencySlots = new SemaphoreSlim ( _config . ConcurrentServiceRequests ) ;
157+ _ownsHttpThrottler = true ; // We own it, so we dispose it
158+ }
99159 }
100160
101161 /// <inheritdoc/>
@@ -654,7 +714,11 @@ public void Dispose()
654714 {
655715 try
656716 {
657- _httpConcurrencySlots ? . Dispose ( ) ;
717+ // Only dispose HTTP throttler if we own it
718+ if ( _ownsHttpThrottler )
719+ {
720+ _httpConcurrencySlots ? . Dispose ( ) ;
721+ }
658722 _dataHandler ? . Dispose ( ) ;
659723 }
660724 catch ( Exception )
0 commit comments