Skip to content

Commit 212b175

Browse files
sichanyooSichan Yoo
andauthored
feat: Add memory management (#19)
* Update README.md * Add memory manager and memory managemenet for downloadObject and uploadObject. * Increase file descriptor for linux ubuntu environment in GH actions. * Correct comments on maxInMemoryBytes config option & limit memory limit for concurrentDownload integration test. * Try lower maxInMemoryBytes to appease GH CI environments. * If this doesn't work, change approach to batch memory mgmt. * Remove changes that didn't fix concurrent download test failure for linux. * Refactor memory mgmt to batch based for downloads. * Fix concurrent download integ test to use range download type. Also, add conversion method to download object to remove reference to body from triage getObject outputs. * Try adding retry to writeData, see if that addresses issue in Linux. * Revert "Try adding retry to writeData, see if that addresses issue in Linux." This reverts commit ffe4bd8. * Try increasing file descriptor again. * Slightly change how writeData is done & add logging for bytes written. * Add more liux CI cases. * Disable integ tests that use symlink for linux because symlink resolution behavior in linux differs between versions. * Limit maxConections to see if it resolves stream request timeout issue during download step in DirectoryTransferIntegrationTests.swift. * Add a withMemoryPermission() helper function to avoid calling waitForMemory() and releaseMemory() directly from transfer logic. Add documentation comments for errors thrown by each operation & remove no longer used ones. Modify default maxInMemoryBytes values for iOS, tvOS, and watchOS. --------- Co-authored-by: Sichan Yoo <chanyoo@amazon.com>
1 parent e8600ec commit 212b175

File tree

13 files changed

+285
-223
lines changed

13 files changed

+285
-223
lines changed

.github/workflows/integration-test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,13 @@ jobs:
113113
strategy:
114114
fail-fast: false
115115
matrix:
116+
runner:
117+
- ubuntu-24.04
118+
- ubuntu-24.04-arm
116119
os:
117120
- jammy
118121
version:
119-
- "6.0"
122+
- "5.9"
120123
steps:
121124
- name: Configure AWS Credentials for Integration Tests
122125
uses: aws-actions/configure-aws-credentials@v4
@@ -149,5 +152,6 @@ jobs:
149152
swift build --build-tests
150153
- name: Run Integration Tests
151154
run: |
155+
ulimit -n 2048
152156
cd aws-sdk-swift-s3-transfer-manager
153157
swift test --filter S3TransferManagerIntegrationTests

README.md

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Or you could pass the config object to the initializer to customize S3TM by doin
7070
```swift
7171
// Create the custom S3 client config that you want S3TM to use.
7272
let customS3ClientConfig = try S3Client.S3ClientConfiguration(
73-
region: "some-region",
73+
region: "us-west-2",
7474
. . . custom S3 client configurations . . .
7575
)
7676

@@ -92,27 +92,24 @@ For more information on what each configuration does, please refer to [the docum
9292

9393
### Upload an object
9494

95-
To upload a file to Amazon S3, you need to provide the input struct UploadObjectInput, which contains a subset of PutObjectInput struct properties and an array of TransferListener. You must provide the destination bucket, the S3 object key to use, and the object body.
95+
To upload a file to Amazon S3, you need to provide the input struct `UploadObjectInput`, which contains a subset of `PutObjectInput` struct properties and an array of transfer listeners. You must provide the destination bucket, the S3 object key to use, and the object body.
9696

9797
When object being uploaded is bigger than the threshold configured by `multipartUploadThresholdBytes` (16MB default), S3TM breaks them down into parts, each with the part size configured by `targetPartSizeBytes` (8MB default), and uploads them concurrently using S3’s [multipart upload feature](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html#mpu-process).
9898

9999
```swift
100-
let s3tm = try await S3TransferManager()
101-
102100
// Construct UploadObjectInput.
103101
let uploadObjectInput = UploadObjectInput(
104102
body: ByteStream.stream(
105103
FileStream(fileHandle: try FileHandle(forReadingFrom: URL(string: "file-to-upload.txt")!))
106104
),
107105
bucket: "destination-bucket",
108-
key: "some-key",
109-
transferListeners: [UploadObjectLoggingTransferListener()]
106+
key: "some-key"
110107
)
111108

112109
// Call .uploadObject and save the returned task.
113110
let uploadObjectTask = try s3tm.uploadObject(input: uploadObjectInput)
114111

115-
// Optional: await on the returned task and retrieve the operation output or an error.
112+
// Optional for every transfer operation: await on the returned task and retrieve the operation output or an error.
116113
// Even if you don't do this, the task executes in the background.
117114
do {
118115
let uploadObjectOutput = try await uploadObjectTask.value
@@ -123,13 +120,11 @@ do {
123120

124121
### Download an object
125122

126-
To download an object from Amazon S3, you need to provide the input struct DownloadObjectInput, which contains the download destination, a subset of GetObjectInput struct properties, and an array of TransferListener. The download destination is an instance of [Swift’s Foundation.OutputStream](https://developer.apple.com/documentation/foundation/outputstream). You must provide the download destination, the source bucket, and the S3 object key of the object to download.
123+
To download an object from Amazon S3, you need to provide the input struct `DownloadObjectInput`, which contains the download destination, a subset of `GetObjectInput` struct properties, and an array of transfer listeners. The download destination is an instance of [Swift’s Foundation.OutputStream](https://developer.apple.com/documentation/foundation/outputstream). You must provide the download destination, the source bucket, and the S3 object key of the object to download.
127124

128125
When object being downloaded is bigger than the size of a single part configured by `targetPartSizeBytes` (8MB default), S3TM downloads the object in parts concurrently using either part numbers or byte ranges as configured by `multipartDownloadType` (`.part` default).
129126

130127
```swift
131-
let s3tm = try await S3TransferManager()
132-
133128
// Construct DownloadObjectInput.
134129
let downloadObjectInput = DownloadObjectInput(
135130
outputStream: OutputStream(toFileAtPath: "destination-file.txt", append: true)!,
@@ -139,25 +134,16 @@ let downloadObjectInput = DownloadObjectInput(
139134

140135
// Call .downloadObject and save the returned task.
141136
let downloadObjectTask = try s3tm.downloadObject(input: downloadObjectInput)
142-
143-
// Optional: await on the returned task and retrieve the operation output or an error.
144-
// Even if you don't do this, the task executes in the background.
145-
do {
146-
let downloadObjectOutput = try await downloadObjectTask.value
147-
} catch {
148-
// Handle error.
149-
}
137+
let downloadObjectOutput = try await downloadObjectTask.value
150138
```
151139

152140
### Upload a directory
153141

154-
To upload a local directory to a S3 bucket, you need to provide the input struct UploadDirectoryInput and provide the destination bucket, and the source directory’s URL.
142+
To upload a local directory to a S3 bucket, you need to provide the input struct `UploadDirectoryInput` and provide the destination bucket, and the source directory’s URL.
155143

156-
The UploadDirectoryInput struct has several optional properties that configure the transfer behavior. For more details on what each input configuration does, refer to [the documentation comments on the UploadDirectoryInput](https://github.com/aws/aws-sdk-swift-s3-transfer-manager/blob/main/Sources/S3TransferManager/Model/OperationInput/UploadDirectoryInput.swift).
144+
The `UploadDirectoryInput` struct has several optional properties that configure the transfer behavior. For more details on what each input configuration does, refer to [the documentation comments on the UploadDirectoryInput](https://github.com/aws/aws-sdk-swift-s3-transfer-manager/blob/main/Sources/S3TransferManager/Model/OperationInput/UploadDirectoryInput.swift).
157145

158146
```swift
159-
let s3tm = try await S3TransferManager()
160-
161147
// Construct UploadDirectoryInput.
162148
let uploadDirectoryInput = try UploadDirectoryInput(
163149
bucket: "destination-bucket",
@@ -166,25 +152,16 @@ let uploadDirectoryInput = try UploadDirectoryInput(
166152

167153
// Call .uploadDirectory and save the returned task.
168154
let uploadDirectoryTask = try s3tm.uploadDirectory(input: uploadDirectoryInput)
169-
170-
// Optional: await on the returned task and retrieve the operation output or an error.
171-
// Even if you don't do this, the task executes in the background.
172-
do {
173-
let uploadDirectoryOutput = try await uploadDirectoryTask.value
174-
} catch {
175-
// Handle error.
176-
}
155+
let uploadDirectoryOutput = try await uploadDirectoryTask.value
177156
```
178157

179158
### Download a bucket
180159

181-
To download a S3 bucket to a local directory, you need to provide the input struct DownloadBucketInput and provide the source bucket, and the destination directory URL.
160+
To download a S3 bucket to a local directory, you need to provide the input struct `DownloadBucketInput` and provide the source bucket, and the destination directory URL.
182161

183-
The DownloadBucketInput struct has several optional properties that configure the transfer behavior. For more details on what each input configuration does, refer to [the documentation comments on the DownloadBucketInput](https://github.com/aws/aws-sdk-swift-s3-transfer-manager/blob/main/Sources/S3TransferManager/Model/OperationInput/DownloadBucketInput.swift).
162+
The `DownloadBucketInput` struct has several optional properties that configure the transfer behavior. For more details on what each input configuration does, refer to [the documentation comments on the DownloadBucketInput](https://github.com/aws/aws-sdk-swift-s3-transfer-manager/blob/main/Sources/S3TransferManager/Model/OperationInput/DownloadBucketInput.swift).
184163

185164
```swift
186-
let s3tm = try await S3TransferManager()
187-
188165
// Construct DownloadBucketInput.
189166
let downloadBucketInput = DownloadBucketInput(
190167
bucket: "source-bucket",
@@ -193,21 +170,14 @@ let downloadBucketInput = DownloadBucketInput(
193170

194171
// Call .downloadBucket and save the returned task.
195172
let downloadBucketTask = try s3tm.downloadBucket(input: downloadBucketInput)
196-
197-
// Optional: await on the returned task and retrieve the operation output or an error.
198-
// Even if you don't do this, the task executes in the background.
199-
do {
200-
let downloadBucketOutput = try await downloadBucketTask.value
201-
} catch {
202-
// Handle error.
203-
}
173+
let downloadBucketOutput = try await downloadBucketTask.value
204174
```
205175

206176
### Monitor transfer progress
207177

208-
You can optionally configure transfer listeners for any of the S3TM operations above. The Amazon S3 Transfer Manager for Swift provides 2 canned transfer progress listeners for you. They’re LoggingTransferListeners and StreamingTransferListeners. There's a specific listener type for each operation, e.g., `UploadObjectLoggingTransferListener` is a LoggingTransferListener for the single object upload operation.
178+
You can optionally configure transfer listeners for any of the S3TM operations above. The Amazon S3 Transfer Manager for Swift provides 2 canned transfer progress listeners for you. They’re `LoggingTransferListener`s and `StreamingTransferListener`s. There's a specific listener type for each operation, e.g., `UploadObjectLoggingTransferListener` is a `LoggingTransferListener` for the single object upload operation.
209179

210-
The LoggingTransferListeners log transfer events to the console using [swift-log](https://github.com/apple/swift-log). The StreamingTransferListeners publish transfer events to its AsyncThrowingStream instance property, which can be awaited on to consume and handle events as needed. You can configure any number of transfer listeners for the S3TM operations via their inputs (e.g., UploadObjectInput's `transferListeners` field). You can add your own custom transfer listeners as well, by implementing a struct or a class that conforms to the TransferListener protocol and configuring it in the input structs.
180+
The `LoggingTransferListener`s log transfer events to the console using [swift-log](https://github.com/apple/swift-log). The `StreamingTransferListener`s publish transfer events to its `AsyncThrowingStream` instance property, which can be awaited on to consume and handle events as needed. You can configure any number of transfer listeners for the S3TM operations via their inputs (e.g., `UploadObjectInput`'s `transferListeners` field). You can add your own custom transfer listeners as well, by implementing a struct or a class that conforms to the `TransferListener` protocol and configuring it in the input structs.
211181

212182
See below for the example usage of the two canned transfer listeners.
213183

@@ -233,7 +203,7 @@ let uploadObjectTask = try s3tm.uploadObject(input: uploadObjectInput)
233203

234204
#### StreamingTransferListener
235205

236-
For StreamingTransferListener, you must close the underlying AsyncThrowingStream after transfer completion by explicitly calling closeStream() on the StreamingTransferListener instance to prevent memory leaks and hanging stream consumers.
206+
For `StreamingTransferListener`, you must close the underlying `AsyncThrowingStream` after transfer completion by explicitly calling `closeStream()` on the `StreamingTransferListener` instance to prevent memory leaks and hanging stream consumers.
237207

238208
```swift
239209
let s3tm = try await S3TransferManager()

Sources/S3TransferManager/Operation/DownloadBucket.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,21 @@ private actor DownloadTracker {
380380

381381
/// A non-exhaustive list of errors that can be thrown by the `downloadBucket` operation of `S3TransferManager`.
382382
public enum S3TMDownloadBucketError: Error {
383+
/// Thrown when the destination URL is not a directory.
383384
case ProvidedDestinationIsNotADirectory
385+
/// Thrown when directory doesn't exist at provided destination URL and creating a new directory fails.
384386
case FailedToCreateDestinationDirectory
387+
/// Thrown when there's no object in the bucket that fits the download criteria.
385388
case FailedToRetrieveObjectsUsingListObjectsV2
389+
/// Thrown when creating a new output stream for an individual download object call fails.
386390
case FailedToCreateOutputStreamForFileURL(url: URL)
391+
/// Thrown when an individual donwload object call fails. Wraps the bubbled up error as well as the `DownloadObjectInput` used for the failed call.
387392
case FailedToDownloadAnObject(
388393
originalErrorFromDownloadObject: Error,
389394
failedDownloadObjectInput: DownloadObjectInput
390395
)
396+
/// Thrown when creating nested directories inside destination directory fails. Nested directories get created to mirror bucket contents.
391397
case FailedToCreateNestedDestinationDirectory(at: URL)
398+
/// Thrown when atomic rename operation for a file fails after download completion.
392399
case FailedToRenameTemporaryFileAfterDownload(tempFile: URL)
393400
}

0 commit comments

Comments
 (0)