aws-sdk-s3 1.232.1 → 1.233.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/VERSION +1 -1
- data/lib/aws-sdk-s3/client.rb +4 -4
- data/lib/aws-sdk-s3/customizations/object.rb +40 -28
- data/lib/aws-sdk-s3/default_executor.rb +65 -19
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +22 -14
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +32 -17
- data/lib/aws-sdk-s3/transfer_manager.rb +57 -37
- data/lib/aws-sdk-s3.rb +1 -1
- data/sig/client.rbs +1 -1
- data/sig/types.rbs +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a94f968574a4140131a94ed10f6e238db29a2cb3ceac5eaab1bccdda34efa4f
|
|
4
|
+
data.tar.gz: 63bbf8c2512e03438f6e22c634bb9070cc52e0b06096d7a6945dc55100303631
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 46c2a374e3f4bbd446d32b3403f08e2b648f37dc5f1cfb1d1c2224a211b43e6450ebdaa9cc3bbf9c6fa3e2a5b0884671127baf13163391fb11647a339f34ae5f
|
|
7
|
+
data.tar.gz: 564b1d9d422faa9180efc8e86f31803c517335608ff98eb05fa328094cc5de1a23cd22f13afeb09aa3e84aeb5f0e0f3b8a2e9a2f69437403efcbc3aa7bb2ebb2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
Unreleased Changes
|
|
2
2
|
------------------
|
|
3
3
|
|
|
4
|
+
1.233.1 (2026-10-01)
|
|
5
|
+
------------------
|
|
6
|
+
|
|
7
|
+
* Issue - Fix unbounded memory growth in `upload_stream` on `TransferManager` and `Aws::S3::Object` when the data source outpaces the upload (#3407).
|
|
8
|
+
|
|
9
|
+
* Issue - Prevent `upload_file`, `upload_stream` and `download_file` on `TransferManager` and `Aws::S3::Object` from hanging when a worker thread is terminated by a non-`StandardError`.
|
|
10
|
+
|
|
11
|
+
1.233.0 (2026-09-30)
|
|
12
|
+
------------------
|
|
13
|
+
|
|
14
|
+
* Feature - Amazon S3 adds a new optional S3 Inventory field, IntelligentTieringReferenceDate, reporting the reference date S3 Intelligent-Tiering uses to evaluate an object's tier-transition eligibility. The value is populated for objects in the Intelligent-Tiering storage class and left blank for others.
|
|
15
|
+
|
|
16
|
+
1.232.3 (2026-09-28)
|
|
17
|
+
------------------
|
|
18
|
+
|
|
19
|
+
* Issue - Remove `:thread_count` from transfer options even when a custom `:executor` is configured on `TransferManager`, fixing an `ArgumentError` raised on single-part `upload_file` calls.
|
|
20
|
+
|
|
21
|
+
* Issue - Fix `:thread_count` being ignored by the deprecated `Object#download_file`, which always used the default thread count.
|
|
22
|
+
|
|
23
|
+
1.232.2 (2026-09-25)
|
|
24
|
+
------------------
|
|
25
|
+
|
|
26
|
+
* Issue - Ensure the internally-created executor is shutdown on error in `TransferManager` and `Aws::S3::Object` resource methods, preventing leaked worker threads on multipart transfer failures (#3419).
|
|
27
|
+
|
|
4
28
|
1.232.1 (2026-09-16)
|
|
5
29
|
------------------
|
|
6
30
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.233.1
|
data/lib/aws-sdk-s3/client.rb
CHANGED
|
@@ -7723,7 +7723,7 @@ module Aws::S3
|
|
|
7723
7723
|
# resp.inventory_configuration.id #=> String
|
|
7724
7724
|
# resp.inventory_configuration.included_object_versions #=> String, one of "All", "Current"
|
|
7725
7725
|
# resp.inventory_configuration.optional_fields #=> Array
|
|
7726
|
-
# resp.inventory_configuration.optional_fields[0] #=> String, one of "Size", "LastModifiedDate", "StorageClass", "ETag", "IsMultipartUploaded", "ReplicationStatus", "EncryptionStatus", "ObjectLockRetainUntilDate", "ObjectLockMode", "ObjectLockLegalHoldStatus", "ObjectLockEventHoldStatus", "ObjectLockEventHoldDuration", "IntelligentTieringAccessTier", "BucketKeyStatus", "ChecksumAlgorithm", "ObjectAccessControlList", "ObjectOwner", "LifecycleExpirationDate"
|
|
7726
|
+
# resp.inventory_configuration.optional_fields[0] #=> String, one of "Size", "LastModifiedDate", "StorageClass", "ETag", "IsMultipartUploaded", "ReplicationStatus", "EncryptionStatus", "ObjectLockRetainUntilDate", "ObjectLockMode", "ObjectLockLegalHoldStatus", "ObjectLockEventHoldStatus", "ObjectLockEventHoldDuration", "IntelligentTieringAccessTier", "BucketKeyStatus", "ChecksumAlgorithm", "ObjectAccessControlList", "ObjectOwner", "LifecycleExpirationDate", "IntelligentTieringReferenceDate"
|
|
7727
7727
|
# resp.inventory_configuration.schedule.frequency #=> String, one of "Daily", "Weekly"
|
|
7728
7728
|
#
|
|
7729
7729
|
# @see http://docs.aws.amazon.com/goto/WebAPI/s3-2006-03-01/GetBucketInventoryConfiguration AWS API Documentation
|
|
@@ -12760,7 +12760,7 @@ module Aws::S3
|
|
|
12760
12760
|
# resp.inventory_configuration_list[0].id #=> String
|
|
12761
12761
|
# resp.inventory_configuration_list[0].included_object_versions #=> String, one of "All", "Current"
|
|
12762
12762
|
# resp.inventory_configuration_list[0].optional_fields #=> Array
|
|
12763
|
-
# resp.inventory_configuration_list[0].optional_fields[0] #=> String, one of "Size", "LastModifiedDate", "StorageClass", "ETag", "IsMultipartUploaded", "ReplicationStatus", "EncryptionStatus", "ObjectLockRetainUntilDate", "ObjectLockMode", "ObjectLockLegalHoldStatus", "ObjectLockEventHoldStatus", "ObjectLockEventHoldDuration", "IntelligentTieringAccessTier", "BucketKeyStatus", "ChecksumAlgorithm", "ObjectAccessControlList", "ObjectOwner", "LifecycleExpirationDate"
|
|
12763
|
+
# resp.inventory_configuration_list[0].optional_fields[0] #=> String, one of "Size", "LastModifiedDate", "StorageClass", "ETag", "IsMultipartUploaded", "ReplicationStatus", "EncryptionStatus", "ObjectLockRetainUntilDate", "ObjectLockMode", "ObjectLockLegalHoldStatus", "ObjectLockEventHoldStatus", "ObjectLockEventHoldDuration", "IntelligentTieringAccessTier", "BucketKeyStatus", "ChecksumAlgorithm", "ObjectAccessControlList", "ObjectOwner", "LifecycleExpirationDate", "IntelligentTieringReferenceDate"
|
|
12764
12764
|
# resp.inventory_configuration_list[0].schedule.frequency #=> String, one of "Daily", "Weekly"
|
|
12765
12765
|
# resp.is_truncated #=> Boolean
|
|
12766
12766
|
# resp.next_continuation_token #=> String
|
|
@@ -16319,7 +16319,7 @@ module Aws::S3
|
|
|
16319
16319
|
# },
|
|
16320
16320
|
# id: "InventoryId", # required
|
|
16321
16321
|
# included_object_versions: "All", # required, accepts All, Current
|
|
16322
|
-
# optional_fields: ["Size"], # accepts Size, LastModifiedDate, StorageClass, ETag, IsMultipartUploaded, ReplicationStatus, EncryptionStatus, ObjectLockRetainUntilDate, ObjectLockMode, ObjectLockLegalHoldStatus, ObjectLockEventHoldStatus, ObjectLockEventHoldDuration, IntelligentTieringAccessTier, BucketKeyStatus, ChecksumAlgorithm, ObjectAccessControlList, ObjectOwner, LifecycleExpirationDate
|
|
16322
|
+
# optional_fields: ["Size"], # accepts Size, LastModifiedDate, StorageClass, ETag, IsMultipartUploaded, ReplicationStatus, EncryptionStatus, ObjectLockRetainUntilDate, ObjectLockMode, ObjectLockLegalHoldStatus, ObjectLockEventHoldStatus, ObjectLockEventHoldDuration, IntelligentTieringAccessTier, BucketKeyStatus, ChecksumAlgorithm, ObjectAccessControlList, ObjectOwner, LifecycleExpirationDate, IntelligentTieringReferenceDate
|
|
16323
16323
|
# schedule: { # required
|
|
16324
16324
|
# frequency: "Daily", # required, accepts Daily, Weekly
|
|
16325
16325
|
# },
|
|
@@ -24209,7 +24209,7 @@ module Aws::S3
|
|
|
24209
24209
|
tracer: tracer
|
|
24210
24210
|
)
|
|
24211
24211
|
context[:gem_name] = 'aws-sdk-s3'
|
|
24212
|
-
context[:gem_version] = '1.
|
|
24212
|
+
context[:gem_version] = '1.233.1'
|
|
24213
24213
|
Seahorse::Client::Request.new(handlers, context)
|
|
24214
24214
|
end
|
|
24215
24215
|
|
|
@@ -380,7 +380,9 @@ module Aws
|
|
|
380
380
|
# and {Client#upload_part} can be provided.
|
|
381
381
|
#
|
|
382
382
|
# @option options [Integer] :thread_count (10) The number of parallel multipart uploads.
|
|
383
|
-
# An additional thread is used internally for task coordination.
|
|
383
|
+
# An additional thread is used internally for task coordination. This also bounds
|
|
384
|
+
# how many parts are buffered ahead of the upload, limiting memory usage to roughly
|
|
385
|
+
# `2 * :thread_count * :part_size`.
|
|
384
386
|
#
|
|
385
387
|
# @option options [Boolean] :tempfile (false) Normally read data is stored
|
|
386
388
|
# in memory when building the parts in order to complete the underlying
|
|
@@ -405,18 +407,22 @@ module Aws
|
|
|
405
407
|
# @see Client#upload_part
|
|
406
408
|
def upload_stream(options = {}, &block)
|
|
407
409
|
upload_opts = options.merge(bucket: bucket_name, key: key)
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
410
|
+
thread_count = upload_opts.delete(:thread_count) || DefaultExecutor::DEFAULT_MAX_THREADS
|
|
411
|
+
executor = DefaultExecutor.new(max_threads: thread_count, max_queue: thread_count)
|
|
412
|
+
begin
|
|
413
|
+
uploader = MultipartStreamUploader.new(
|
|
414
|
+
client: client,
|
|
415
|
+
executor: executor,
|
|
416
|
+
tempfile: upload_opts.delete(:tempfile),
|
|
417
|
+
part_size: upload_opts.delete(:part_size)
|
|
418
|
+
)
|
|
419
|
+
Aws::Plugins::UserAgent.metric('RESOURCE_MODEL') do
|
|
420
|
+
uploader.upload(upload_opts, &block)
|
|
421
|
+
end
|
|
422
|
+
true
|
|
423
|
+
ensure
|
|
424
|
+
executor.shutdown
|
|
417
425
|
end
|
|
418
|
-
executor.shutdown
|
|
419
|
-
true
|
|
420
426
|
end
|
|
421
427
|
deprecated(:upload_stream, use: 'Aws::S3::TransferManager#upload_stream', version: 'next major version')
|
|
422
428
|
|
|
@@ -480,17 +486,20 @@ module Aws
|
|
|
480
486
|
def upload_file(source, options = {})
|
|
481
487
|
upload_opts = options.merge(bucket: bucket_name, key: key)
|
|
482
488
|
executor = DefaultExecutor.new(max_threads: upload_opts.delete(:thread_count))
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
489
|
+
begin
|
|
490
|
+
uploader = FileUploader.new(
|
|
491
|
+
client: client,
|
|
492
|
+
executor: executor,
|
|
493
|
+
multipart_threshold: upload_opts.delete(:multipart_threshold)
|
|
494
|
+
)
|
|
495
|
+
response = Aws::Plugins::UserAgent.metric('RESOURCE_MODEL') do
|
|
496
|
+
uploader.upload(source, upload_opts)
|
|
497
|
+
end
|
|
498
|
+
yield response if block_given?
|
|
499
|
+
true
|
|
500
|
+
ensure
|
|
501
|
+
executor.shutdown
|
|
490
502
|
end
|
|
491
|
-
yield response if block_given?
|
|
492
|
-
executor.shutdown
|
|
493
|
-
true
|
|
494
503
|
end
|
|
495
504
|
deprecated(:upload_file, use: 'Aws::S3::TransferManager#upload_file', version: 'next major version')
|
|
496
505
|
|
|
@@ -561,13 +570,16 @@ module Aws
|
|
|
561
570
|
# @see Client#head_object
|
|
562
571
|
def download_file(destination, options = {})
|
|
563
572
|
download_opts = options.merge(bucket: bucket_name, key: key)
|
|
564
|
-
executor = DefaultExecutor.new(max_threads: download_opts.delete(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
573
|
+
executor = DefaultExecutor.new(max_threads: download_opts.delete(:thread_count))
|
|
574
|
+
begin
|
|
575
|
+
downloader = FileDownloader.new(client: client, executor: executor)
|
|
576
|
+
Aws::Plugins::UserAgent.metric('RESOURCE_MODEL') do
|
|
577
|
+
downloader.download(destination, download_opts)
|
|
578
|
+
end
|
|
579
|
+
true
|
|
580
|
+
ensure
|
|
581
|
+
executor.shutdown
|
|
568
582
|
end
|
|
569
|
-
executor.shutdown
|
|
570
|
-
true
|
|
571
583
|
end
|
|
572
584
|
deprecated(:download_file, use: 'Aws::S3::TransferManager#download_file', version: 'next major version')
|
|
573
585
|
|
|
@@ -4,6 +4,13 @@ module Aws
|
|
|
4
4
|
module S3
|
|
5
5
|
# @api private
|
|
6
6
|
class DefaultExecutor
|
|
7
|
+
# Raised when a task is posted to an executor that is shutting down or has been shut down.
|
|
8
|
+
class RejectedExecutionError < RuntimeError
|
|
9
|
+
def initialize(msg = 'Executor has been shutdown and is no longer accepting tasks')
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
DEFAULT_MAX_THREADS = 10
|
|
8
15
|
RUNNING = :running
|
|
9
16
|
SHUTTING_DOWN = :shutting_down
|
|
@@ -11,10 +18,12 @@ module Aws
|
|
|
11
18
|
|
|
12
19
|
def initialize(options = {})
|
|
13
20
|
@max_threads = options[:max_threads] || DEFAULT_MAX_THREADS
|
|
21
|
+
@max_queue = options[:max_queue] || 0
|
|
14
22
|
@state = RUNNING
|
|
15
|
-
@queue = Queue.new
|
|
23
|
+
@queue = @max_queue.zero? ? Queue.new : SizedQueue.new(@max_queue)
|
|
16
24
|
@pool = []
|
|
17
25
|
@mutex = Mutex.new
|
|
26
|
+
@fatal_error = nil
|
|
18
27
|
end
|
|
19
28
|
|
|
20
29
|
# Submits a task for execution.
|
|
@@ -23,12 +32,15 @@ module Aws
|
|
|
23
32
|
# @return [Boolean] Returns true if the task was submitted successfully
|
|
24
33
|
def post(*args, &block)
|
|
25
34
|
@mutex.synchronize do
|
|
26
|
-
raise
|
|
35
|
+
raise RejectedExecutionError unless @state == RUNNING
|
|
27
36
|
|
|
28
|
-
@queue << [args, block]
|
|
29
37
|
ensure_worker_available
|
|
30
38
|
end
|
|
39
|
+
# Outside the mutex so a caller blocked on a full queue can't hold up #shutdown or #kill.
|
|
40
|
+
@queue.push([args, block])
|
|
31
41
|
true
|
|
42
|
+
rescue ClosedQueueError
|
|
43
|
+
raise RejectedExecutionError
|
|
32
44
|
end
|
|
33
45
|
|
|
34
46
|
# Immediately terminates all worker threads and clears pending tasks.
|
|
@@ -38,6 +50,7 @@ module Aws
|
|
|
38
50
|
def kill
|
|
39
51
|
@mutex.synchronize do
|
|
40
52
|
@state = SHUTDOWN
|
|
53
|
+
@queue.close
|
|
41
54
|
@pool.each(&:kill)
|
|
42
55
|
@pool.clear
|
|
43
56
|
@queue.clear
|
|
@@ -52,35 +65,58 @@ module Aws
|
|
|
52
65
|
# If nil, waits indefinitely. If timeout expires, remaining threads are killed.
|
|
53
66
|
# @return [Boolean] true when shutdown is complete
|
|
54
67
|
def shutdown(timeout = nil)
|
|
68
|
+
return true unless begin_shutdown
|
|
69
|
+
|
|
70
|
+
deadline = timeout && (Time.now + timeout)
|
|
71
|
+
join_workers(deadline)
|
|
72
|
+
kill_remaining_workers if timeout
|
|
73
|
+
|
|
74
|
+
finalize_shutdown
|
|
75
|
+
raise @fatal_error if @fatal_error
|
|
76
|
+
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def begin_shutdown
|
|
55
83
|
@mutex.synchronize do
|
|
56
|
-
return
|
|
84
|
+
return false if @state == SHUTDOWN
|
|
57
85
|
|
|
58
86
|
@state = SHUTTING_DOWN
|
|
59
|
-
|
|
87
|
+
# Close rather than push sentinels, which could block on a full queue.
|
|
88
|
+
@queue.close
|
|
60
89
|
end
|
|
90
|
+
true
|
|
91
|
+
end
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
remaining = deadline - Time.now
|
|
66
|
-
break if remaining <= 0
|
|
93
|
+
def join_workers(deadline)
|
|
94
|
+
until (threads = @mutex.synchronize { @pool.select(&:alive?) }).empty?
|
|
95
|
+
threads.each do |thread|
|
|
96
|
+
remaining = deadline - Time.now if deadline
|
|
97
|
+
break if remaining && remaining <= 0
|
|
67
98
|
|
|
68
|
-
|
|
99
|
+
begin
|
|
100
|
+
thread.join(remaining && [remaining, 0].max)
|
|
101
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
102
|
+
nil # recorded by #replace_worker and raised by #shutdown
|
|
103
|
+
end
|
|
69
104
|
end
|
|
70
|
-
|
|
71
|
-
else
|
|
72
|
-
@pool.each(&:join)
|
|
105
|
+
break if deadline && Time.now >= deadline
|
|
73
106
|
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def kill_remaining_workers
|
|
110
|
+
@mutex.synchronize { @pool.select(&:alive?).each(&:kill) }
|
|
111
|
+
end
|
|
74
112
|
|
|
113
|
+
def finalize_shutdown
|
|
75
114
|
@mutex.synchronize do
|
|
76
115
|
@pool.clear
|
|
77
116
|
@state = SHUTDOWN
|
|
78
117
|
end
|
|
79
|
-
true
|
|
80
118
|
end
|
|
81
119
|
|
|
82
|
-
private
|
|
83
|
-
|
|
84
120
|
def ensure_worker_available
|
|
85
121
|
return unless @state == RUNNING
|
|
86
122
|
|
|
@@ -91,11 +127,21 @@ module Aws
|
|
|
91
127
|
def spawn_worker
|
|
92
128
|
Thread.new do
|
|
93
129
|
while (job = @queue.shift)
|
|
94
|
-
break if job == :shutdown
|
|
95
|
-
|
|
96
130
|
args, block = job
|
|
97
131
|
block.call(*args)
|
|
98
132
|
end
|
|
133
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
134
|
+
# Replace this worker so queued tasks still run.
|
|
135
|
+
replace_worker(e)
|
|
136
|
+
raise
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def replace_worker(error)
|
|
141
|
+
@mutex.synchronize do
|
|
142
|
+
@fatal_error ||= error
|
|
143
|
+
@pool.delete(Thread.current)
|
|
144
|
+
@pool << spawn_worker unless @state == SHUTDOWN
|
|
99
145
|
end
|
|
100
146
|
end
|
|
101
147
|
end
|
|
@@ -140,7 +140,7 @@ module Aws
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def upload_with_executor(pending, completed, options)
|
|
143
|
-
|
|
143
|
+
queued_parts = 0
|
|
144
144
|
completion_queue = Queue.new
|
|
145
145
|
abort_upload = false
|
|
146
146
|
errors = []
|
|
@@ -149,25 +149,33 @@ module Aws
|
|
|
149
149
|
while (part = pending.shift)
|
|
150
150
|
break if abort_upload
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
152
|
+
begin
|
|
153
|
+
@executor.post(part) do |p|
|
|
154
|
+
Thread.current[:net_http_override_body_stream_chunk] = @http_chunk_size if @http_chunk_size
|
|
155
|
+
update_progress(progress, p)
|
|
156
|
+
resp = @client.upload_part(p)
|
|
157
|
+
completed_part = { etag: resp.etag, part_number: p[:part_number] }
|
|
158
|
+
apply_part_checksum(resp, completed_part)
|
|
159
|
+
completed.push(completed_part)
|
|
160
|
+
# Any error, or the upload completes without this part.
|
|
161
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
162
|
+
abort_upload = true
|
|
163
|
+
errors << e
|
|
164
|
+
ensure
|
|
165
|
+
p[:body].close
|
|
166
|
+
Thread.current[:net_http_override_body_stream_chunk] = nil if @http_chunk_size
|
|
167
|
+
completion_queue << :done
|
|
168
|
+
end
|
|
169
|
+
queued_parts += 1
|
|
160
170
|
rescue StandardError => e
|
|
171
|
+
# Rejected by the executor; abort rather than orphan the upload.
|
|
161
172
|
abort_upload = true
|
|
162
173
|
errors << e
|
|
163
|
-
|
|
164
|
-
p[:body].close
|
|
165
|
-
Thread.current[:net_http_override_body_stream_chunk] = nil if @http_chunk_size
|
|
166
|
-
completion_queue << :done
|
|
174
|
+
break
|
|
167
175
|
end
|
|
168
176
|
end
|
|
169
177
|
|
|
170
|
-
|
|
178
|
+
queued_parts.times { completion_queue.pop }
|
|
171
179
|
errors
|
|
172
180
|
end
|
|
173
181
|
|
|
@@ -113,18 +113,22 @@ module Aws
|
|
|
113
113
|
def read_to_part_body(read_pipe)
|
|
114
114
|
return if read_pipe.closed?
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if
|
|
116
|
+
if @tempfile
|
|
117
|
+
temp_io = Tempfile.new('aws-sdk-s3-upload_stream')
|
|
118
|
+
temp_io.binmode
|
|
119
|
+
bytes_copied = IO.copy_stream(read_pipe, temp_io, @part_size)
|
|
120
|
+
temp_io.rewind
|
|
121
|
+
if bytes_copied.zero?
|
|
122
122
|
temp_io.close
|
|
123
123
|
temp_io.unlink
|
|
124
|
+
nil
|
|
125
|
+
else
|
|
126
|
+
temp_io
|
|
124
127
|
end
|
|
125
|
-
nil
|
|
126
128
|
else
|
|
127
|
-
|
|
129
|
+
# A single sized read; copy_stream into a StringIO grows by doubling and fragments the heap.
|
|
130
|
+
data = read_pipe.read(@part_size)
|
|
131
|
+
data.nil? ? nil : StringIO.new(data)
|
|
128
132
|
end
|
|
129
133
|
end
|
|
130
134
|
|
|
@@ -139,20 +143,31 @@ module Aws
|
|
|
139
143
|
end
|
|
140
144
|
break unless part_body || current_part_num == 1
|
|
141
145
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
begin
|
|
147
|
+
@executor.post(part_body, current_part_num, options) do |body, num, opts|
|
|
148
|
+
part = opts.merge(body: body, part_number: num)
|
|
149
|
+
resp = @client.upload_part(part)
|
|
150
|
+
completed_part = create_completed_part(resp, part)
|
|
151
|
+
completed.push(completed_part)
|
|
152
|
+
# Any error, or the upload completes without this part.
|
|
153
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
154
|
+
mutex.synchronize do
|
|
155
|
+
errors.push(e)
|
|
156
|
+
read_pipe.close_read unless read_pipe.closed?
|
|
157
|
+
end
|
|
158
|
+
ensure
|
|
159
|
+
clear_body(body)
|
|
160
|
+
completion_queue << :done
|
|
161
|
+
end
|
|
162
|
+
queued_parts += 1
|
|
148
163
|
rescue StandardError => e
|
|
164
|
+
# Rejected by the executor. Closing the pipe stops the producer so the upload can abort.
|
|
149
165
|
mutex.synchronize do
|
|
150
166
|
errors.push(e)
|
|
151
167
|
read_pipe.close_read unless read_pipe.closed?
|
|
152
168
|
end
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
completion_queue << :done
|
|
169
|
+
clear_body(part_body)
|
|
170
|
+
break
|
|
156
171
|
end
|
|
157
172
|
end
|
|
158
173
|
queued_parts.times { completion_queue.pop }
|
|
@@ -160,11 +160,14 @@ module Aws
|
|
|
160
160
|
# * `:errors` - Array of errors for failed downloads (only present when failures occur)
|
|
161
161
|
def download_directory(destination, bucket:, **options)
|
|
162
162
|
Aws::Plugins::UserAgent.metric('S3_TRANSFER', 'S3_TRANSFER_DOWNLOAD_DIRECTORY') do
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
thread_count = options.delete(:thread_count)
|
|
164
|
+
executor = @executor || DefaultExecutor.new(max_threads: thread_count)
|
|
165
|
+
begin
|
|
166
|
+
downloader = DirectoryDownloader.new(client: @client, executor: executor, logger: @logger)
|
|
167
|
+
downloader.download(destination, bucket: bucket, **options)
|
|
168
|
+
ensure
|
|
169
|
+
executor.shutdown unless @executor
|
|
170
|
+
end
|
|
168
171
|
end
|
|
169
172
|
end
|
|
170
173
|
|
|
@@ -245,11 +248,15 @@ module Aws
|
|
|
245
248
|
# @see Client#head_object
|
|
246
249
|
def download_file(destination, bucket:, key:, **options)
|
|
247
250
|
download_opts = options.merge(bucket: bucket, key: key)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
thread_count = download_opts.delete(:thread_count)
|
|
252
|
+
executor = @executor || DefaultExecutor.new(max_threads: thread_count)
|
|
253
|
+
begin
|
|
254
|
+
downloader = FileDownloader.new(client: @client, executor: executor)
|
|
255
|
+
downloader.download(destination, download_opts)
|
|
256
|
+
true
|
|
257
|
+
ensure
|
|
258
|
+
executor.shutdown unless @executor
|
|
259
|
+
end
|
|
253
260
|
end
|
|
254
261
|
|
|
255
262
|
# Uploads all files under the given directory to the provided S3 bucket.
|
|
@@ -360,11 +367,14 @@ module Aws
|
|
|
360
367
|
# * `:errors` - Array of error objects for failed uploads (only present when failures occur)
|
|
361
368
|
def upload_directory(source, bucket:, **options)
|
|
362
369
|
Aws::Plugins::UserAgent.metric('S3_TRANSFER', 'S3_TRANSFER_UPLOAD_DIRECTORY') do
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
370
|
+
thread_count = options.delete(:thread_count)
|
|
371
|
+
executor = @executor || DefaultExecutor.new(max_threads: thread_count)
|
|
372
|
+
begin
|
|
373
|
+
uploader = DirectoryUploader.new(client: @client, executor: executor, logger: @logger)
|
|
374
|
+
uploader.upload(source, bucket, **options.merge(http_chunk_size: resolve_http_chunk_size(options)))
|
|
375
|
+
ensure
|
|
376
|
+
executor.shutdown unless @executor
|
|
377
|
+
end
|
|
368
378
|
end
|
|
369
379
|
end
|
|
370
380
|
|
|
@@ -443,18 +453,21 @@ module Aws
|
|
|
443
453
|
def upload_file(source, bucket:, key:, **options)
|
|
444
454
|
upload_opts = options.merge(bucket: bucket, key: key)
|
|
445
455
|
http_chunk_size = resolve_http_chunk_size(upload_opts)
|
|
446
|
-
|
|
447
|
-
executor = @executor || DefaultExecutor.new(max_threads:
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
thread_count = upload_opts.delete(:thread_count)
|
|
457
|
+
executor = @executor || DefaultExecutor.new(max_threads: thread_count)
|
|
458
|
+
begin
|
|
459
|
+
uploader = FileUploader.new(
|
|
460
|
+
multipart_threshold: upload_opts.delete(:multipart_threshold),
|
|
461
|
+
http_chunk_size: http_chunk_size,
|
|
462
|
+
client: @client,
|
|
463
|
+
executor: executor
|
|
464
|
+
)
|
|
465
|
+
response = uploader.upload(source, upload_opts)
|
|
466
|
+
yield response if block_given?
|
|
467
|
+
true
|
|
468
|
+
ensure
|
|
469
|
+
executor.shutdown unless @executor
|
|
470
|
+
end
|
|
458
471
|
end
|
|
459
472
|
|
|
460
473
|
# Uploads a stream in a streaming fashion to S3.
|
|
@@ -491,6 +504,9 @@ module Aws
|
|
|
491
504
|
# @option options [Integer] :thread_count (10)
|
|
492
505
|
# The number of parallel multipart uploads. Only used when no custom executor is provided (creates
|
|
493
506
|
# {DefaultExecutor} with the given thread count). An additional thread is used internally for task coordination.
|
|
507
|
+
# This also bounds how many parts are buffered ahead of the upload, limiting memory usage to roughly
|
|
508
|
+
# `2 * :thread_count * :part_size`. When a custom `:executor` is provided, it is responsible for applying
|
|
509
|
+
# its own backpressure.
|
|
494
510
|
#
|
|
495
511
|
# @option options [Boolean] :tempfile (false)
|
|
496
512
|
# Normally read data is stored in memory when building the parts in order to complete the underlying
|
|
@@ -511,16 +527,20 @@ module Aws
|
|
|
511
527
|
# @see Client#upload_part
|
|
512
528
|
def upload_stream(bucket:, key:, **options, &block)
|
|
513
529
|
upload_opts = options.merge(bucket: bucket, key: key)
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
530
|
+
thread_count = upload_opts.delete(:thread_count) || DefaultExecutor::DEFAULT_MAX_THREADS
|
|
531
|
+
executor = @executor || DefaultExecutor.new(max_threads: thread_count, max_queue: thread_count)
|
|
532
|
+
begin
|
|
533
|
+
uploader = MultipartStreamUploader.new(
|
|
534
|
+
client: @client,
|
|
535
|
+
executor: executor,
|
|
536
|
+
tempfile: upload_opts.delete(:tempfile),
|
|
537
|
+
part_size: upload_opts.delete(:part_size)
|
|
538
|
+
)
|
|
539
|
+
uploader.upload(upload_opts, &block)
|
|
540
|
+
true
|
|
541
|
+
ensure
|
|
542
|
+
executor.shutdown unless @executor
|
|
543
|
+
end
|
|
524
544
|
end
|
|
525
545
|
|
|
526
546
|
private
|
data/lib/aws-sdk-s3.rb
CHANGED
data/sig/client.rbs
CHANGED
|
@@ -1671,7 +1671,7 @@ module Aws
|
|
|
1671
1671
|
}?,
|
|
1672
1672
|
id: ::String,
|
|
1673
1673
|
included_object_versions: ("All" | "Current"),
|
|
1674
|
-
optional_fields: Array[("Size" | "LastModifiedDate" | "StorageClass" | "ETag" | "IsMultipartUploaded" | "ReplicationStatus" | "EncryptionStatus" | "ObjectLockRetainUntilDate" | "ObjectLockMode" | "ObjectLockLegalHoldStatus" | "ObjectLockEventHoldStatus" | "ObjectLockEventHoldDuration" | "IntelligentTieringAccessTier" | "BucketKeyStatus" | "ChecksumAlgorithm" | "ObjectAccessControlList" | "ObjectOwner" | "LifecycleExpirationDate")]?,
|
|
1674
|
+
optional_fields: Array[("Size" | "LastModifiedDate" | "StorageClass" | "ETag" | "IsMultipartUploaded" | "ReplicationStatus" | "EncryptionStatus" | "ObjectLockRetainUntilDate" | "ObjectLockMode" | "ObjectLockLegalHoldStatus" | "ObjectLockEventHoldStatus" | "ObjectLockEventHoldDuration" | "IntelligentTieringAccessTier" | "BucketKeyStatus" | "ChecksumAlgorithm" | "ObjectAccessControlList" | "ObjectOwner" | "LifecycleExpirationDate" | "IntelligentTieringReferenceDate")]?,
|
|
1675
1675
|
schedule: {
|
|
1676
1676
|
frequency: ("Daily" | "Weekly")
|
|
1677
1677
|
}
|
data/sig/types.rbs
CHANGED
|
@@ -1515,7 +1515,7 @@ module Aws::S3
|
|
|
1515
1515
|
attr_accessor filter: Types::InventoryFilter
|
|
1516
1516
|
attr_accessor id: ::String
|
|
1517
1517
|
attr_accessor included_object_versions: ("All" | "Current")
|
|
1518
|
-
attr_accessor optional_fields: ::Array[("Size" | "LastModifiedDate" | "StorageClass" | "ETag" | "IsMultipartUploaded" | "ReplicationStatus" | "EncryptionStatus" | "ObjectLockRetainUntilDate" | "ObjectLockMode" | "ObjectLockLegalHoldStatus" | "ObjectLockEventHoldStatus" | "ObjectLockEventHoldDuration" | "IntelligentTieringAccessTier" | "BucketKeyStatus" | "ChecksumAlgorithm" | "ObjectAccessControlList" | "ObjectOwner" | "LifecycleExpirationDate")]
|
|
1518
|
+
attr_accessor optional_fields: ::Array[("Size" | "LastModifiedDate" | "StorageClass" | "ETag" | "IsMultipartUploaded" | "ReplicationStatus" | "EncryptionStatus" | "ObjectLockRetainUntilDate" | "ObjectLockMode" | "ObjectLockLegalHoldStatus" | "ObjectLockEventHoldStatus" | "ObjectLockEventHoldDuration" | "IntelligentTieringAccessTier" | "BucketKeyStatus" | "ChecksumAlgorithm" | "ObjectAccessControlList" | "ObjectOwner" | "LifecycleExpirationDate" | "IntelligentTieringReferenceDate")]
|
|
1519
1519
|
attr_accessor schedule: Types::InventorySchedule
|
|
1520
1520
|
SENSITIVE: []
|
|
1521
1521
|
end
|