@@ -14,9 +14,9 @@ mod service;
14
14
15
15
use crate :: error;
16
16
use crate :: io:: InputStream ;
17
- use aws_smithy_types:: byte_stream:: ByteStream ;
18
17
use context:: UploadContext ;
19
18
pub use handle:: UploadHandle ;
19
+ use handle:: { MultipartUploadData , UploadType } ;
20
20
/// Request type for uploads to Amazon S3
21
21
pub use input:: { UploadInput , UploadInputBuilder } ;
22
22
/// Response type for uploads to Amazon S3
@@ -36,57 +36,58 @@ pub(crate) struct Upload;
36
36
37
37
impl Upload {
38
38
/// Execute a single `Upload` transfer operation
39
- pub ( crate ) async fn orchestrate (
39
+ pub ( crate ) fn orchestrate (
40
40
handle : Arc < crate :: client:: Handle > ,
41
41
mut input : crate :: operation:: upload:: UploadInput ,
42
42
) -> Result < UploadHandle , error:: Error > {
43
- let min_mpu_threshold = handle. mpu_threshold_bytes ( ) ;
44
-
45
43
let stream = input. take_body ( ) ;
46
- let ctx = new_context ( handle, input) ;
47
-
48
- // MPU has max of 10K parts which requires us to know the upper bound on the content length (today anyway)
49
- // While true for file-based workloads, the upper `size_hint` might not be equal to the actual bytes transferred.
50
- let content_length = stream
51
- . size_hint ( )
52
- . upper ( )
53
- . ok_or_else ( crate :: io:: error:: Error :: upper_bound_size_hint_required) ?;
44
+ let ctx = new_context ( handle. clone ( ) , input) ;
45
+ Ok ( UploadHandle :: new (
46
+ ctx. clone ( ) ,
47
+ tokio:: spawn ( try_start_upload ( handle. clone ( ) , stream, ctx) ) ,
48
+ ) )
49
+ }
50
+ }
54
51
52
+ async fn try_start_upload (
53
+ handle : Arc < crate :: client:: Handle > ,
54
+ stream : InputStream ,
55
+ ctx : UploadContext ,
56
+ ) -> Result < UploadType , crate :: error:: Error > {
57
+ let min_mpu_threshold = handle. mpu_threshold_bytes ( ) ;
58
+
59
+ // MPU has max of 10K parts which requires us to know the upper bound on the content length (today anyway)
60
+ // While true for file-based workloads, the upper `size_hint` might not be equal to the actual bytes transferred.
61
+ let content_length = stream
62
+ . size_hint ( )
63
+ . upper ( )
64
+ . ok_or_else ( crate :: io:: error:: Error :: upper_bound_size_hint_required) ?;
65
+
66
+ let upload_type = if content_length < min_mpu_threshold && !stream. is_mpu_only ( ) {
67
+ tracing:: trace!( "upload request content size hint ({content_length}) less than min part size threshold ({min_mpu_threshold}); sending as single PutObject request" ) ;
68
+ UploadType :: PutObject ( tokio:: spawn ( put_object (
69
+ ctx. clone ( ) ,
70
+ stream,
71
+ content_length,
72
+ ) ) )
73
+ } else {
74
+ // TODO - to upload a 0 byte object via MPU you have to send [CreateMultipartUpload, UploadPart(part=1, 0 bytes), CompleteMultipartUpload]
75
+ // we should add tests for this and hide this edge case from the user (e.g. send an empty part when a custom PartStream returns `None` immediately)
55
76
// FIXME - investigate what it would take to allow non mpu uploads for `PartStream` implementations
56
- let handle = if content_length < min_mpu_threshold && !stream. is_mpu_only ( ) {
57
- tracing:: trace!( "upload request content size hint ({content_length}) less than min part size threshold ({min_mpu_threshold}); sending as single PutObject request" ) ;
58
- try_start_put_object ( ctx, stream, content_length) . await ?
59
- } else {
60
- // TODO - to upload a 0 byte object via MPU you have to send [CreateMultipartUpload, UploadPart(part=1, 0 bytes), CompleteMultipartUpload]
61
- // we should add tests for this and hide this edge case from the user (e.g. send an empty part when a custom PartStream returns `None` immediately)
62
- try_start_mpu_upload ( ctx, stream, content_length) . await ?
63
- } ;
64
-
65
- Ok ( handle)
66
- }
77
+ try_start_mpu_upload ( ctx, stream, content_length) . await ?
78
+ } ;
79
+ Ok ( upload_type)
67
80
}
68
81
69
- async fn try_start_put_object (
82
+ async fn put_object (
70
83
ctx : UploadContext ,
71
84
stream : InputStream ,
72
85
content_length : u64 ,
73
- ) -> Result < UploadHandle , crate :: error:: Error > {
74
- let byte_stream = stream. into_byte_stream ( ) . await ?;
86
+ ) -> Result < UploadOutput , error:: Error > {
87
+ let body = stream. into_byte_stream ( ) . await ?;
75
88
let content_length: i64 = content_length. try_into ( ) . map_err ( |_| {
76
89
error:: invalid_input ( format ! ( "content_length:{} is invalid." , content_length) )
77
90
} ) ?;
78
-
79
- Ok ( UploadHandle :: new_put_object (
80
- ctx. clone ( ) ,
81
- tokio:: spawn ( put_object ( ctx. clone ( ) , byte_stream, content_length) ) ,
82
- ) )
83
- }
84
-
85
- async fn put_object (
86
- ctx : UploadContext ,
87
- body : ByteStream ,
88
- content_length : i64 ,
89
- ) -> Result < UploadOutput , error:: Error > {
90
91
// FIXME - This affects performance in cases with a lot of small files workloads. We need a way to schedule
91
92
// more work for a lot of small files.
92
93
let _permit = ctx. handle . scheduler . acquire_permit ( ) . await ?;
@@ -149,7 +150,7 @@ async fn try_start_mpu_upload(
149
150
ctx : UploadContext ,
150
151
stream : InputStream ,
151
152
content_length : u64 ,
152
- ) -> Result < UploadHandle , crate :: error:: Error > {
153
+ ) -> Result < UploadType , crate :: error:: Error > {
153
154
let part_size = cmp:: max (
154
155
ctx. handle . upload_part_size_bytes ( ) ,
155
156
content_length / MAX_PARTS ,
@@ -161,18 +162,22 @@ async fn try_start_mpu_upload(
161
162
"multipart upload started with upload id: {:?}" ,
162
163
mpu. upload_id
163
164
) ;
164
-
165
- let mut handle = UploadHandle :: new_multipart ( ctx) ;
166
- handle. set_response ( mpu) ;
167
- distribute_work ( & mut handle, stream, part_size) ?;
168
- Ok ( handle)
165
+ let upload_id = mpu. upload_id . clone ( ) . expect ( "upload_id is present" ) ;
166
+ let mut mpu_data = MultipartUploadData {
167
+ upload_part_tasks : Default :: default ( ) ,
168
+ read_body_tasks : Default :: default ( ) ,
169
+ response : Some ( mpu) ,
170
+ upload_id : upload_id. clone ( ) ,
171
+ } ;
172
+
173
+ distribute_work ( & mut mpu_data, ctx, stream, part_size) ?;
174
+ Ok ( UploadType :: MultipartUpload ( mpu_data) )
169
175
}
170
176
171
177
fn new_context ( handle : Arc < crate :: client:: Handle > , req : UploadInput ) -> UploadContext {
172
178
UploadContext {
173
179
handle,
174
180
request : Arc :: new ( req) ,
175
- upload_id : None ,
176
181
}
177
182
}
178
183
@@ -225,6 +230,7 @@ mod test {
225
230
use crate :: io:: InputStream ;
226
231
use crate :: operation:: upload:: UploadInput ;
227
232
use crate :: types:: { ConcurrencySetting , PartSize } ;
233
+ use aws_sdk_s3:: operation:: abort_multipart_upload:: AbortMultipartUploadOutput ;
228
234
use aws_sdk_s3:: operation:: complete_multipart_upload:: CompleteMultipartUploadOutput ;
229
235
use aws_sdk_s3:: operation:: create_multipart_upload:: CreateMultipartUploadOutput ;
230
236
use aws_sdk_s3:: operation:: put_object:: PutObjectOutput ;
@@ -233,6 +239,7 @@ mod test {
233
239
use bytes:: Bytes ;
234
240
use std:: ops:: Deref ;
235
241
use std:: sync:: Arc ;
242
+ use std:: sync:: Barrier ;
236
243
use test_common:: mock_client_with_stubbed_http_client;
237
244
238
245
#[ tokio:: test]
@@ -297,7 +304,7 @@ mod test {
297
304
. key ( "test-key" )
298
305
. body ( stream) ;
299
306
300
- let handle = request. send_with ( & tm) . await . unwrap ( ) ;
307
+ let handle = request. initiate_with ( & tm) . unwrap ( ) ;
301
308
302
309
let resp = handle. join ( ) . await . unwrap ( ) ;
303
310
assert_eq ! ( expected_upload_id. deref( ) , resp. upload_id. unwrap( ) . deref( ) ) ;
@@ -331,9 +338,70 @@ mod test {
331
338
. bucket ( "test-bucket" )
332
339
. key ( "test-key" )
333
340
. body ( stream) ;
334
- let handle = request. send_with ( & tm) . await . unwrap ( ) ;
341
+ let handle = request. initiate_with ( & tm) . unwrap ( ) ;
335
342
let resp = handle. join ( ) . await . unwrap ( ) ;
336
343
assert_eq ! ( resp. upload_id( ) , None ) ;
337
344
assert_eq ! ( expected_e_tag. deref( ) , resp. e_tag( ) . unwrap( ) ) ;
338
345
}
346
+
347
+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
348
+ async fn test_abort_multipart_upload ( ) {
349
+ let expected_upload_id = Arc :: new ( "test-upload" . to_owned ( ) ) ;
350
+ let body = Bytes :: from_static ( b"every adolescent dog goes bonkers early" ) ;
351
+ let stream = InputStream :: from ( body) ;
352
+ let bucket = "test-bucket" ;
353
+ let key = "test-key" ;
354
+ let wait_till_create_mpu = Arc :: new ( Barrier :: new ( 2 ) ) ;
355
+
356
+ let upload_id = expected_upload_id. clone ( ) ;
357
+ let create_mpu =
358
+ mock ! ( aws_sdk_s3:: Client :: create_multipart_upload) . then_output ( move || {
359
+ CreateMultipartUploadOutput :: builder ( )
360
+ . upload_id ( upload_id. as_ref ( ) . to_owned ( ) )
361
+ . build ( )
362
+ } ) ;
363
+
364
+ let upload_part = mock ! ( aws_sdk_s3:: Client :: upload_part) . then_output ( {
365
+ let wait_till_create_mpu = wait_till_create_mpu. clone ( ) ;
366
+ move || {
367
+ wait_till_create_mpu. wait ( ) ;
368
+ UploadPartOutput :: builder ( ) . build ( )
369
+ }
370
+ } ) ;
371
+
372
+ let abort_mpu = mock ! ( aws_sdk_s3:: Client :: abort_multipart_upload)
373
+ . match_requests ( {
374
+ let upload_id: Arc < String > = expected_upload_id. clone ( ) ;
375
+ move |input| {
376
+ input. upload_id . as_ref ( ) == Some ( & upload_id)
377
+ && input. bucket ( ) == Some ( bucket)
378
+ && input. key ( ) == Some ( key)
379
+ }
380
+ } )
381
+ . then_output ( || AbortMultipartUploadOutput :: builder ( ) . build ( ) ) ;
382
+
383
+ let client = mock_client_with_stubbed_http_client ! (
384
+ aws_sdk_s3,
385
+ RuleMode :: Sequential ,
386
+ & [ create_mpu, upload_part, abort_mpu]
387
+ ) ;
388
+
389
+ let tm_config = crate :: Config :: builder ( )
390
+ . concurrency ( ConcurrencySetting :: Explicit ( 1 ) )
391
+ . set_multipart_threshold ( PartSize :: Target ( 10 ) )
392
+ . set_target_part_size ( PartSize :: Target ( 5 * 1024 * 1024 ) )
393
+ . client ( client)
394
+ . build ( ) ;
395
+
396
+ let tm = crate :: Client :: new ( tm_config) ;
397
+
398
+ let request = UploadInput :: builder ( )
399
+ . bucket ( "test-bucket" )
400
+ . key ( "test-key" )
401
+ . body ( stream) ;
402
+ let handle = request. initiate_with ( & tm) . unwrap ( ) ;
403
+ wait_till_create_mpu. wait ( ) ;
404
+ let abort = handle. abort ( ) . await . unwrap ( ) ;
405
+ assert_eq ! ( abort. upload_id( ) . unwrap( ) , expected_upload_id. deref( ) ) ;
406
+ }
339
407
}
0 commit comments