77import io .deephaven .util .channel .CompletableOutputStream ;
88import org .jetbrains .annotations .NotNull ;
99import org .jetbrains .annotations .Nullable ;
10+ import software .amazon .awssdk .awscore .AwsRequest ;
1011import software .amazon .awssdk .core .async .AsyncRequestBody ;
1112import software .amazon .awssdk .services .s3 .S3AsyncClient ;
1213import software .amazon .awssdk .services .s3 .S3Uri ;
1314import software .amazon .awssdk .services .s3 .internal .multipart .SdkPojoConversionUtils ;
14- import software .amazon .awssdk .services .s3 .model .AbortMultipartUploadRequest ;
15- import software .amazon .awssdk .services .s3 .model .AbortMultipartUploadResponse ;
16- import software .amazon .awssdk .services .s3 .model .CompleteMultipartUploadRequest ;
17- import software .amazon .awssdk .services .s3 .model .CompletedMultipartUpload ;
18- import software .amazon .awssdk .services .s3 .model .CompletedPart ;
19- import software .amazon .awssdk .services .s3 .model .CreateMultipartUploadRequest ;
20- import software .amazon .awssdk .services .s3 .model .CreateMultipartUploadResponse ;
21- import software .amazon .awssdk .services .s3 .model .UploadPartRequest ;
22- import software .amazon .awssdk .services .s3 .model .UploadPartResponse ;
15+ import software .amazon .awssdk .services .s3 .model .*;
2316
2417import java .io .IOException ;
2518import java .net .URI ;
2619import java .nio .ByteBuffer ;
27- import java .util .ArrayList ;
28- import java .util .Collections ;
29- import java .util .Comparator ;
30- import java .util .List ;
31- import java .util .Objects ;
32- import java .util .concurrent .CancellationException ;
33- import java .util .concurrent .CompletableFuture ;
34- import java .util .concurrent .CompletionException ;
35- import java .util .concurrent .ExecutionException ;
36- import java .util .concurrent .Semaphore ;
20+ import java .time .Duration ;
21+ import java .util .*;
22+ import java .util .concurrent .*;
3723
3824import static io .deephaven .extensions .s3 .S3ReadContext .handleS3Exception ;
25+ import static io .deephaven .extensions .s3 .S3Utils .addTimeout ;
3926
4027class S3CompletableOutputStream extends CompletableOutputStream {
4128
@@ -290,10 +277,10 @@ public void close() throws IOException {
290277 ////////// Helper methods and classes //////////
291278
292279 private String initiateMultipartUpload () throws IOException {
293- final CreateMultipartUploadRequest createMultipartUploadRequest = CreateMultipartUploadRequest .builder ()
280+ final CreateMultipartUploadRequest . Builder builder = CreateMultipartUploadRequest .builder ()
294281 .bucket (uri .bucket ().orElseThrow ())
295- .key (uri .key ().orElseThrow ())
296- .build ();
282+ .key (uri .key ().orElseThrow ());
283+ final CreateMultipartUploadRequest createMultipartUploadRequest = applyOverrideConfiguration ( builder ) .build ();
297284 // Note: We can add support for other parameters like tagging, storage class, encryption, permissions, etc. in
298285 // future
299286 final CompletableFuture <CreateMultipartUploadResponse > initiateUploadFuture =
@@ -306,6 +293,7 @@ private String initiateMultipartUpload() throws IOException {
306293 try {
307294 response = initiateUploadFuture .get ();
308295 } catch (final InterruptedException | ExecutionException | CancellationException e ) {
296+ initiateUploadFuture .cancel (true );
309297 throw handleS3Exception (e , String .format ("initiating multipart upload for uri %s" , uri ), s3Instructions );
310298 }
311299 return response .uploadId ();
@@ -334,12 +322,12 @@ private void sendPartRequest(boolean onDone) throws IOException {
334322 return ;
335323 }
336324
337- final UploadPartRequest uploadPartRequest = UploadPartRequest .builder ()
325+ final UploadPartRequest . Builder builder = UploadPartRequest .builder ()
338326 .bucket (uri .bucket ().orElseThrow ())
339327 .key (uri .key ().orElseThrow ())
340328 .uploadId (uploadId )
341- .partNumber (nextPartNumber )
342- .build ();
329+ .partNumber (nextPartNumber );
330+ final UploadPartRequest uploadPartRequest = applyOverrideConfiguration ( builder ) .build ();
343331
344332 final int partNumber = nextPartNumber ;
345333 final CompletableFuture <UploadPartResponse > uploadPartFuture = s3AsyncClient .uploadPart (uploadPartRequest ,
@@ -449,17 +437,20 @@ private void completeMultipartUpload() throws IOException {
449437 // Sort the completed parts by part number, as required by S3
450438 completedParts .sort (Comparator .comparingInt (CompletedPart ::partNumber ));
451439
452- final CompleteMultipartUploadRequest completeRequest = CompleteMultipartUploadRequest .builder ()
440+ final CompleteMultipartUploadRequest . Builder builder = CompleteMultipartUploadRequest .builder ()
453441 .bucket (uri .bucket ().orElseThrow ())
454442 .key (uri .key ().orElseThrow ())
455443 .uploadId (uploadId )
456444 .multipartUpload (CompletedMultipartUpload .builder ()
457445 .parts (completedParts )
458- .build ())
459- .build ();
446+ .build ());
447+ final CompleteMultipartUploadRequest completeRequest = applyOverrideConfiguration (builder ).build ();
448+ final CompletableFuture <CompleteMultipartUploadResponse > uploadFuture =
449+ s3AsyncClient .completeMultipartUpload (completeRequest );
460450 try {
461- s3AsyncClient . completeMultipartUpload ( completeRequest ) .get ();
451+ uploadFuture .get ();
462452 } catch (final InterruptedException | ExecutionException | CancellationException e ) {
453+ uploadFuture .cancel (true );
463454 final IOException ex = handleS3Exception (e ,
464455 String .format ("completing multipart upload for uri %s" , uri ), s3Instructions );
465456 failAll (ex );
@@ -503,19 +494,32 @@ private void abortMultipartUpload() throws IOException {
503494 }
504495
505496 // Initiate the abort request
506- final AbortMultipartUploadRequest abortRequest = AbortMultipartUploadRequest .builder ()
497+ final AbortMultipartUploadRequest . Builder builder = AbortMultipartUploadRequest .builder ()
507498 .bucket (uri .bucket ().orElseThrow ())
508499 .key (uri .key ().orElseThrow ())
509- .uploadId (uploadId )
510- .build ();
500+ .uploadId (uploadId );
501+ final AbortMultipartUploadRequest abortRequest = applyOverrideConfiguration ( builder ) .build ();
511502 final CompletableFuture <AbortMultipartUploadResponse > future = s3AsyncClient .abortMultipartUpload (abortRequest );
512503
513504 // Wait for the abort to complete
514505 try {
515506 future .get ();
516507 } catch (final InterruptedException | ExecutionException | CancellationException e ) {
508+ future .cancel (true );
517509 throw handleS3Exception (e ,
518510 String .format ("aborting multipart upload for uri %s" , uri ), s3Instructions );
519511 }
520512 }
513+
514+ /**
515+ * Applies the write timeout, then generates a request for the given builder and request class.
516+ *
517+ * @param builder an instance of a {@link S3Request.Builder} class
518+ * @return the builder
519+ */
520+ private <B extends AwsRequest .Builder > B applyOverrideConfiguration (@ NotNull B builder ) {
521+ final Duration writeTimeout = s3Instructions .writeTimeout ();
522+ builder .overrideConfiguration (b -> addTimeout (b , writeTimeout ));
523+ return builder ;
524+ }
521525}
0 commit comments