aws-sdk-s3 1.199.1 → 1.202.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,27 +2,74 @@
2
2
 
3
3
  module Aws
4
4
  module S3
5
- # A high-level S3 transfer utility that provides enhanced upload and download
6
- # capabilities with automatic multipart handling, progress tracking, and
7
- # handling of large files. The following features are supported:
5
+ # A high-level S3 transfer utility that provides enhanced upload and download capabilities with automatic
6
+ # multipart handling, progress tracking, and handling of large files. The following features are supported:
8
7
  #
9
8
  # * upload a file with multipart upload
10
9
  # * upload a stream with multipart upload
11
10
  # * download a S3 object with multipart download
12
11
  # * track transfer progress by using progress listener
13
12
  #
13
+ # ## Executor Management
14
+ # TransferManager uses executors to handle concurrent operations during multipart transfers. You can control
15
+ # concurrency behavior by providing a custom executor or relying on the default executor management.
16
+ #
17
+ # ### Default Behavior
18
+ # When no `:executor` is provided, TransferManager creates a new DefaultExecutor for each individual
19
+ # operation (`download_file`, `upload_file`, etc.) and automatically shuts it down when that operation completes.
20
+ # Each operation gets its own isolated thread pool with the specified `:thread_count` (default 10 threads).
21
+ #
22
+ # ### Custom Executor
23
+ # You can provide your own executor (e.g., `Concurrent::ThreadPoolExecutor`) for fine-grained control over thread
24
+ # pools and resource management. When using a custom executor, you are responsible for shutting it down
25
+ # when finished. The executor may be reused across multiple TransferManager operations.
26
+ #
27
+ # Custom executors must implement the same interface as DefaultExecutor.
28
+ #
29
+ # **Required methods:**
30
+ #
31
+ # * `post(*args, &block)` - Execute a task with given arguments and block
32
+ # * `kill` - Immediately terminate all running tasks
33
+ #
34
+ # **Optional methods:**
35
+ #
36
+ # * `shutdown(timeout = nil)` - Gracefully shutdown the executor with optional timeout
37
+ #
38
+ # @example Using default executor (automatic creation and shutdown)
39
+ # tm = TransferManager.new # No executor provided
40
+ # # DefaultExecutor created, used, and shutdown automatically
41
+ # tm.download_file('/path/to/file', bucket: 'bucket', key: 'key')
42
+ #
43
+ # @example Using custom executor (manual shutdown required)
44
+ # require 'concurrent-ruby'
45
+ #
46
+ # executor = Concurrent::ThreadPoolExecutor.new(max_threads: 5)
47
+ # tm = TransferManager.new(executor: executor)
48
+ # tm.download_file('/path/to/file1', bucket: 'bucket', key: 'key1')
49
+ # executor.shutdown # You must shutdown custom executors
50
+ #
14
51
  class TransferManager
52
+
15
53
  # @param [Hash] options
16
54
  # @option options [S3::Client] :client (S3::Client.new)
17
55
  # The S3 client to use for {TransferManager} operations. If not provided, a new default client
18
56
  # will be created automatically.
57
+ # @option options [Object] :executor
58
+ # The executor to use for multipart operations. Must implement the same interface as {DefaultExecutor}.
59
+ # If not provided, a new {DefaultExecutor} will be created automatically for each operation and
60
+ # shutdown after completion. When provided a custom executor, it will be reused across operations, and
61
+ # you are responsible for shutting it down when finished.
19
62
  def initialize(options = {})
20
- @client = options.delete(:client) || Client.new
63
+ @client = options[:client] || Client.new
64
+ @executor = options[:executor]
21
65
  end
22
66
 
23
67
  # @return [S3::Client]
24
68
  attr_reader :client
25
69
 
70
+ # @return [Object]
71
+ attr_reader :executor
72
+
26
73
  # Downloads a file in S3 to a path on disk.
27
74
  #
28
75
  # # small files (< 5MB) are downloaded in a single API call
@@ -74,10 +121,7 @@ module Aws
74
121
  # @option options [Integer] :chunk_size required in `"get_range"` mode.
75
122
  #
76
123
  # @option options [Integer] :thread_count (10) Customize threads used in the multipart download.
77
- #
78
- # @option options [String] :version_id The object version id used to retrieve the object.
79
- #
80
- # @see https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html ObjectVersioning
124
+ # Only used when no custom executor is provided (creates {DefaultExecutor} with given thread count).
81
125
  #
82
126
  # @option options [String] :checksum_mode ("ENABLED")
83
127
  # When `"ENABLED"` and the object has a stored checksum, it will be used to validate the download and will
@@ -102,8 +146,11 @@ module Aws
102
146
  # @see Client#get_object
103
147
  # @see Client#head_object
104
148
  def download_file(destination, bucket:, key:, **options)
105
- downloader = FileDownloader.new(client: @client)
106
- downloader.download(destination, options.merge(bucket: bucket, key: key))
149
+ download_opts = options.merge(bucket: bucket, key: key)
150
+ executor = @executor || DefaultExecutor.new(max_threads: download_opts.delete(:thread_count))
151
+ downloader = FileDownloader.new(client: @client, executor: executor)
152
+ downloader.download(destination, download_opts)
153
+ executor.shutdown unless @executor
107
154
  true
108
155
  end
109
156
 
@@ -139,7 +186,7 @@ module Aws
139
186
  # A file on the local file system that will be uploaded. This can either be a `String` or `Pathname` to the
140
187
  # file, an open `File` object, or an open `Tempfile` object. If you pass an open `File` or `Tempfile` object,
141
188
  # then you are responsible for closing it after the upload completes. When using an open Tempfile, rewind it
142
- # before uploading or else the object will be empty.
189
+ # before uploading or else the object will be empty.
143
190
  #
144
191
  # @param [String] bucket
145
192
  # The name of the S3 bucket to upload to.
@@ -156,15 +203,14 @@ module Aws
156
203
  # Files larger han or equal to `:multipart_threshold` are uploaded using the S3 multipart upload APIs.
157
204
  # Default threshold is `100MB`.
158
205
  #
159
- # @option options [Integer] :thread_count (10)
160
- # The number of parallel multipart uploads. This option is not used if the file is smaller than
161
- # `:multipart_threshold`.
206
+ # @option options [Integer] :thread_count (10) Customize threads used in the multipart upload.
207
+ # Only used when no custom executor is provided (creates {DefaultExecutor} with the given thread count).
162
208
  #
163
209
  # @option options [Proc] :progress_callback (nil)
164
210
  # A Proc that will be called when each chunk of the upload is sent.
165
211
  # It will be invoked with `[bytes_read]` and `[total_sizes]`.
166
212
  #
167
- # @raise [MultipartUploadError] If an file is being uploaded in parts, and the upload can not be completed,
213
+ # @raise [MultipartUploadError] If a file is being uploaded in parts, and the upload can not be completed,
168
214
  # then the upload is aborted and this error is raised. The raised error has a `#errors` method that
169
215
  # returns the failures that caused the upload to be aborted.
170
216
  #
@@ -175,13 +221,16 @@ module Aws
175
221
  # @see Client#complete_multipart_upload
176
222
  # @see Client#upload_part
177
223
  def upload_file(source, bucket:, key:, **options)
178
- uploading_options = options.dup
224
+ upload_opts = options.merge(bucket: bucket, key: key)
225
+ executor = @executor || DefaultExecutor.new(max_threads: upload_opts.delete(:thread_count))
179
226
  uploader = FileUploader.new(
180
- multipart_threshold: uploading_options.delete(:multipart_threshold),
181
- client: @client
227
+ multipart_threshold: upload_opts.delete(:multipart_threshold),
228
+ client: @client,
229
+ executor: executor
182
230
  )
183
- response = uploader.upload(source, uploading_options.merge(bucket: bucket, key: key))
231
+ response = uploader.upload(source, upload_opts)
184
232
  yield response if block_given?
233
+ executor.shutdown unless @executor
185
234
  true
186
235
  end
187
236
 
@@ -217,7 +266,8 @@ module Aws
217
266
  # {Client#upload_part} can be provided.
218
267
  #
219
268
  # @option options [Integer] :thread_count (10)
220
- # The number of parallel multipart uploads.
269
+ # The number of parallel multipart uploads. Only used when no custom executor is provided (creates
270
+ # {DefaultExecutor} with the given thread count). An additional thread is used internally for task coordination.
221
271
  #
222
272
  # @option options [Boolean] :tempfile (false)
223
273
  # Normally read data is stored in memory when building the parts in order to complete the underlying
@@ -237,14 +287,16 @@ module Aws
237
287
  # @see Client#complete_multipart_upload
238
288
  # @see Client#upload_part
239
289
  def upload_stream(bucket:, key:, **options, &block)
240
- uploading_options = options.dup
290
+ upload_opts = options.merge(bucket: bucket, key: key)
291
+ executor = @executor || DefaultExecutor.new(max_threads: upload_opts.delete(:thread_count))
241
292
  uploader = MultipartStreamUploader.new(
242
293
  client: @client,
243
- thread_count: uploading_options.delete(:thread_count),
244
- tempfile: uploading_options.delete(:tempfile),
245
- part_size: uploading_options.delete(:part_size)
294
+ executor: executor,
295
+ tempfile: upload_opts.delete(:tempfile),
296
+ part_size: upload_opts.delete(:part_size)
246
297
  )
247
- uploader.upload(uploading_options.merge(bucket: bucket, key: key), &block)
298
+ uploader.upload(upload_opts, &block)
299
+ executor.shutdown unless @executor
248
300
  true
249
301
  end
250
302
  end