Skip to content

Commit 9be4570

Browse files
authored
Refactor S3 Transfer Manager uploadDirectory to limit the number of concurrent upload file requests. (#5031)
1 parent 97388aa commit 9be4570

File tree

4 files changed

+28
-19
lines changed

4 files changed

+28
-19
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "S3 Transfer Manager",
4+
"contributor": "",
5+
"description": "Set a limit on the number of concurrent upload file requests for upload directory. This fixes the OOM issue that could surface when users try to upload a directory that has millions of small files. See [#5023](https://github.com/aws/aws-sdk-java-v2/issues/5023)."
6+
}

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/DownloadDirectoryHelper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package software.amazon.awssdk.transfer.s3.internal;
1717

1818
import static software.amazon.awssdk.transfer.s3.internal.TransferConfigurationOption.DEFAULT_DELIMITER;
19-
import static software.amazon.awssdk.transfer.s3.internal.TransferConfigurationOption.DEFAULT_DOWNLOAD_DIRECTORY_MAX_CONCURRENCY;
19+
import static software.amazon.awssdk.transfer.s3.internal.TransferConfigurationOption.DEFAULT_DIRECTORY_TRANSFER_MAX_CONCURRENCY;
2020
import static software.amazon.awssdk.transfer.s3.internal.TransferConfigurationOption.DEFAULT_PREFIX;
2121

2222
import java.io.IOException;
@@ -110,7 +110,7 @@ private void doDownloadDirectory(CompletableFuture<CompletedDirectoryDownload> r
110110
new AsyncBufferingSubscriber<>(downloadSingleFile(downloadDirectoryRequest, request,
111111
failedFileDownloads),
112112
allOfFutures,
113-
DEFAULT_DOWNLOAD_DIRECTORY_MAX_CONCURRENCY);
113+
DEFAULT_DIRECTORY_TRANSFER_MAX_CONCURRENCY);
114114
listObjectsHelper.listS3ObjectsRecursively(request)
115115
.filter(downloadDirectoryRequest.filter())
116116
.subscribe(asyncBufferingSubscriber);

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/TransferConfigurationOption.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public final class TransferConfigurationOption<T> extends AttributeMap.Key<T> {
3737

3838
public static final String DEFAULT_DELIMITER = "/";
3939
public static final String DEFAULT_PREFIX = "";
40-
public static final int DEFAULT_DOWNLOAD_DIRECTORY_MAX_CONCURRENCY = 100;
40+
public static final int DEFAULT_DIRECTORY_TRANSFER_MAX_CONCURRENCY = 100;
4141

4242
private static final int DEFAULT_UPLOAD_DIRECTORY_MAX_DEPTH = Integer.MAX_VALUE;
4343

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/UploadDirectoryHelper.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.transfer.s3.internal;
1717

1818
import static software.amazon.awssdk.transfer.s3.internal.TransferConfigurationOption.DEFAULT_DELIMITER;
19+
import static software.amazon.awssdk.transfer.s3.internal.TransferConfigurationOption.DEFAULT_DIRECTORY_TRANSFER_MAX_CONCURRENCY;
1920
import static software.amazon.awssdk.transfer.s3.internal.TransferConfigurationOption.DEFAULT_PREFIX;
2021

2122
import java.io.IOException;
@@ -24,14 +25,13 @@
2425
import java.nio.file.LinkOption;
2526
import java.nio.file.Path;
2627
import java.util.Collection;
27-
import java.util.List;
2828
import java.util.concurrent.CompletableFuture;
2929
import java.util.concurrent.CompletionException;
3030
import java.util.concurrent.ConcurrentLinkedQueue;
3131
import java.util.function.Function;
32-
import java.util.stream.Collectors;
3332
import java.util.stream.Stream;
3433
import software.amazon.awssdk.annotations.SdkInternalApi;
34+
import software.amazon.awssdk.core.async.SdkPublisher;
3535
import software.amazon.awssdk.core.exception.SdkClientException;
3636
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
3737
import software.amazon.awssdk.transfer.s3.S3TransferManager;
@@ -90,23 +90,26 @@ private void doUploadDirectory(CompletableFuture<CompletedDirectoryUpload> retur
9090
validateDirectory(uploadDirectoryRequest);
9191

9292
Collection<FailedFileUpload> failedFileUploads = new ConcurrentLinkedQueue<>();
93-
List<CompletableFuture<CompletedFileUpload>> futures;
9493

95-
try (Stream<Path> entries = listFiles(directory, uploadDirectoryRequest)) {
96-
futures = entries.map(path -> {
97-
CompletableFuture<CompletedFileUpload> future = uploadSingleFile(uploadDirectoryRequest,
98-
failedFileUploads, path);
94+
Stream<Path> stream = listFiles(directory, uploadDirectoryRequest);
9995

100-
// Forward cancellation of the return future to all individual futures.
101-
CompletableFutureUtils.forwardExceptionTo(returnFuture, future);
102-
return future;
103-
}).collect(Collectors.toList());
104-
}
96+
SdkPublisher<Path> iterablePublisher = SdkPublisher.fromIterable(() -> stream.iterator())
97+
.doAfterOnCancel(() -> stream.close())
98+
.doAfterOnError(t -> stream.close())
99+
.doAfterOnComplete(() -> stream.close());
100+
101+
CompletableFuture<Void> allOfFutures = new CompletableFuture<>();
102+
103+
AsyncBufferingSubscriber<Path> bufferingSubscriber =
104+
new AsyncBufferingSubscriber<>(path -> uploadSingleFile(uploadDirectoryRequest, failedFileUploads, path),
105+
allOfFutures, DEFAULT_DIRECTORY_TRANSFER_MAX_CONCURRENCY);
106+
107+
iterablePublisher.subscribe(bufferingSubscriber);
108+
CompletableFutureUtils.forwardExceptionTo(returnFuture, allOfFutures);
105109

106-
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
107-
.whenComplete((r, t) -> returnFuture.complete(CompletedDirectoryUpload.builder()
108-
.failedTransfers(failedFileUploads)
109-
.build()));
110+
allOfFutures.whenComplete((r, t) -> returnFuture.complete(CompletedDirectoryUpload.builder()
111+
.failedTransfers(failedFileUploads)
112+
.build()));
110113
}
111114

112115
private void validateDirectory(UploadDirectoryRequest uploadDirectoryRequest) {

0 commit comments

Comments
 (0)