@@ -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 ;
@@ -70,23 +71,82 @@ private Logger Logger
7071 }
7172
7273 /// <summary>
73- /// Initializes a new instance of the <see cref="MultipartDownloadManager"/> class.
74+ /// Initializes a new instance of the <see cref="MultipartDownloadManager"/> for single file downloads.
75+ /// This constructor creates and owns its own HTTP concurrency throttler based on the configuration.
7476 /// </summary>
75- /// <param name="s3Client">The <see cref="IAmazonS3"/> client for making S3 requests.</param>
76- /// <param name="request">The <see cref="BaseDownloadRequest"/> containing download parameters.</param>
77- /// <param name="config">The <see cref="DownloadManagerConfiguration"/> with download settings.</param>
78- /// <param name="dataHandler">The <see cref="IPartDataHandler"/> for processing downloaded parts.</param>
79- /// <param name="requestEventHandler">Optional <see cref="RequestEventHandler"/> for user agent tracking.</param>
80- /// <exception cref="ArgumentNullException">Thrown when any required parameter is null.</exception>
77+ /// <param name="s3Client">The <see cref="IAmazonS3"/> client used to make GetObject requests to S3.</param>
78+ /// <param name="request">The <see cref="BaseDownloadRequest"/> containing bucket, key, version, and download strategy configuration.</param>
79+ /// <param name="config">The <see cref="DownloadManagerConfiguration"/> specifying concurrency limits and part size settings.</param>
80+ /// <param name="dataHandler">The <see cref="IPartDataHandler"/> responsible for buffering and processing downloaded part data.</param>
81+ /// <param name="requestEventHandler">Optional request event handler for adding custom headers or tracking requests. May be null.</param>
82+ /// <exception cref="ArgumentNullException">
83+ /// Thrown when <paramref name="s3Client"/>, <paramref name="request"/>, <paramref name="config"/>, or <paramref name="dataHandler"/> is null.
84+ /// </exception>
85+ /// <remarks>
86+ /// This constructor is used for single file downloads where each download manages its own HTTP concurrency.
87+ /// The created <see cref="SemaphoreSlim"/> throttler will be disposed when this instance is disposed.
88+ /// For directory downloads with shared concurrency management, use the overload that accepts a shared throttler.
89+ /// </remarks>
90+ /// <seealso cref="DownloadManagerConfiguration"/>
91+ /// <seealso cref="IPartDataHandler"/>
92+ /// <seealso cref="MultipartDownloadType"/>
8193 public MultipartDownloadManager ( IAmazonS3 s3Client , BaseDownloadRequest request , DownloadManagerConfiguration config , IPartDataHandler dataHandler , RequestEventHandler requestEventHandler = null )
94+ : this ( s3Client , request , config , dataHandler , requestEventHandler , null )
95+ {
96+ }
97+
98+ /// <summary>
99+ /// Initializes a new instance of the <see cref="MultipartDownloadManager"/> for directory downloads or scenarios requiring shared concurrency control.
100+ /// This constructor allows using a shared HTTP concurrency throttler across multiple concurrent file downloads.
101+ /// </summary>
102+ /// <param name="s3Client">The <see cref="IAmazonS3"/> client used to make GetObject requests to S3.</param>
103+ /// <param name="request">The <see cref="BaseDownloadRequest"/> containing bucket, key, version, and download strategy configuration.</param>
104+ /// <param name="config">The <see cref="DownloadManagerConfiguration"/> specifying concurrency limits and part size settings.</param>
105+ /// <param name="dataHandler">The <see cref="IPartDataHandler"/> responsible for buffering and processing downloaded part data.</param>
106+ /// <param name="requestEventHandler">Optional request event handler for adding custom headers or tracking requests. May be null.</param>
107+ /// <param name="sharedHttpThrottler">
108+ /// Optional shared <see cref="SemaphoreSlim"/> for coordinating HTTP concurrency across multiple downloads.
109+ /// If null, a new throttler will be created and owned by this instance.
110+ /// If provided, the caller retains ownership and responsibility for disposal.
111+ /// </param>
112+ /// <exception cref="ArgumentNullException">
113+ /// Thrown when <paramref name="s3Client"/>, <paramref name="request"/>, <paramref name="config"/>, or <paramref name="dataHandler"/> is null.
114+ /// </exception>
115+ /// <remarks>
116+ /// <para>
117+ /// This constructor is typically used by directory download operations where multiple files are being downloaded
118+ /// concurrently and need to share a global HTTP concurrency limit.
119+ /// </para>
120+ /// <para>
121+ /// <strong>Resource Ownership:</strong>
122+ /// If <paramref name="sharedHttpThrottler"/> is provided, this instance does NOT take ownership and will NOT dispose it.
123+ /// If <paramref name="sharedHttpThrottler"/> is null, this instance creates and owns the throttler and will dispose it.
124+ /// </para>
125+ /// </remarks>
126+ /// <seealso cref="DownloadManagerConfiguration"/>
127+ /// <seealso cref="IPartDataHandler"/>
128+ /// <seealso cref="MultipartDownloadType"/>
129+ /// <seealso cref="DiscoverDownloadStrategyAsync"/>
130+ /// <seealso cref="StartDownloadsAsync"/>
131+ public MultipartDownloadManager ( IAmazonS3 s3Client , BaseDownloadRequest request , DownloadManagerConfiguration config , IPartDataHandler dataHandler , RequestEventHandler requestEventHandler , SemaphoreSlim sharedHttpThrottler )
82132 {
83133 _s3Client = s3Client ?? throw new ArgumentNullException ( nameof ( s3Client ) ) ;
84134 _request = request ?? throw new ArgumentNullException ( nameof ( request ) ) ;
85135 _config = config ?? throw new ArgumentNullException ( nameof ( config ) ) ;
86136 _dataHandler = dataHandler ?? throw new ArgumentNullException ( nameof ( dataHandler ) ) ;
87137 _requestEventHandler = requestEventHandler ;
88138
89- _httpConcurrencySlots = new SemaphoreSlim ( _config . ConcurrentServiceRequests ) ;
139+ // Use shared throttler if provided, otherwise create our own
140+ if ( sharedHttpThrottler != null )
141+ {
142+ _httpConcurrencySlots = sharedHttpThrottler ;
143+ _ownsHttpThrottler = false ; // Don't dispose - directory command owns it
144+ }
145+ else
146+ {
147+ _httpConcurrencySlots = new SemaphoreSlim ( _config . ConcurrentServiceRequests ) ;
148+ _ownsHttpThrottler = true ; // We own it, so we dispose it
149+ }
90150 }
91151
92152 /// <inheritdoc/>
@@ -616,7 +676,11 @@ public void Dispose()
616676 {
617677 try
618678 {
619- _httpConcurrencySlots ? . Dispose ( ) ;
679+ // Only dispose HTTP throttler if we own it
680+ if ( _ownsHttpThrottler )
681+ {
682+ _httpConcurrencySlots ? . Dispose ( ) ;
683+ }
620684 _dataHandler ? . Dispose ( ) ;
621685 }
622686 catch ( Exception )
0 commit comments