Skip to content

Commit b5fe351

Browse files
Merge branch 'main' into get_size
2 parents c0b262a + a9218e5 commit b5fe351

57 files changed

Lines changed: 12935 additions & 4957 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ jobs:
223223
uses: actions/checkout@v4
224224
- name: Build ${{ env.PACKAGE_NAME }} + consumers
225225
run: |
226+
python3 -m venv .venv
227+
source .venv/bin/activate
226228
python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')"
227229
chmod a+x builder
228230
./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DASSERT_LOCK_HELD=ON

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ benchmarks/dashboard-stack/package-lock.json
7171

7272
# virtual environment
7373
.venv/
74+
.cache/

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
108108

109109
include(CTest)
110110
if (BUILD_TESTING)
111+
add_definitions(-DAWS_C_S3_ENABLE_TEST_STUBS)
111112
add_subdirectory(tests)
112113
if (NOT BYO_CRYPTO AND NOT CMAKE_CROSSCOMPILING)
113114
add_subdirectory(samples)

README.md

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,49 @@
33
The AWS-C-S3 library is an asynchronous AWS S3 client focused on maximizing throughput and network utilization.
44

55
### Key features:
6-
- **Automatic Request Splitting**: Improves throughput by automatically splitting the request into part-sized chunks and performing parallel uploads/downloads of these chunks over multiple connections. There's a cap on the throughput of single S3 connection, the only way to go faster is multiple parallel connections.
7-
- **Automatic Retries**: Increases resilience by retrying individual failed chunks of a file transfer, eliminating the need to restart transfers from scratch after an intermittent error.
8-
- **DNS Load Balancing**: DNS resolver continuously harvests Amazon S3 IP addresses. When load is spread across the S3 fleet, overall throughput more reliable than if all connections are going to a single IP.
9-
- **Advanced Network Management**: The client incorporates automatic request parallelization, effective timeouts and retries, and efficient connection reuse. This approach helps to maximize throughput and network utilization, and to avoid network overloads.
10-
- **Thread Pools and Async I/O**: Avoids bottlenecks associated with single-thread processing.
11-
- **Parallel Reads**: When uploading a large file from disk, reads from multiple parts of the file in parallel. This is faster than reading the file sequentially from beginning to end.
6+
7+
* **Automatic Request Splitting**: Improves throughput by automatically splitting the request into part-sized chunks and performing parallel uploads/downloads of these chunks over multiple connections. There's a cap on the throughput of single S3 connection, the only way to go faster is multiple parallel connections.
8+
* **Automatic Retries**: Increases resilience by retrying individual failed chunks of a file transfer, eliminating the need to restart transfers from scratch after an intermittent error.
9+
* **DNS Load Balancing**: DNS resolver continuously harvests Amazon S3 IP addresses. When load is spread across the S3 fleet, overall throughput more reliable than if all connections are going to a single IP.
10+
* **Advanced Network Management**: The client incorporates automatic request parallelization, effective timeouts and retries, and efficient connection reuse. This approach helps to maximize throughput and network utilization, and to avoid network overloads.
11+
* **Thread Pools and Async I/O**: Avoids bottlenecks associated with single-thread processing.
12+
* **Parallel Reads**: When uploading a large file from disk, reads from multiple parts of the file in parallel. This is faster than reading the file sequentially from beginning to end.
1213

1314
### Documentation
1415

15-
- [GetObject](docs/GetObject.md): A visual representation of the GetObject request flow.
16-
- [Memory Aware Requests Execution](docs/memory_aware_request_execution.md): An in-depth guide on optimizing memory usage during request executions.
16+
* [GetObject](docs/GetObject.md): A visual representation of the GetObject request flow.
17+
* [Memory Aware Requests Execution](docs/memory_aware_request_execution.md): An in-depth guide on optimizing memory usage during request executions.
18+
19+
### Configuration
20+
21+
#### Memory Limit
22+
23+
The S3 client uses a buffer pool to manage memory for concurrent transfers. You can control the memory limit in two ways:
24+
25+
1. **Via Configuration** (Recommended): Set `memory_limit_in_bytes` in `aws_s3_client_config`:
26+
27+
```c
28+
struct aws_s3_client_config config = {
29+
.memory_limit_in_bytes = GB_TO_BYTES(4), // 4 GiB limit
30+
// ... other configuration
31+
};
32+
```
33+
34+
2. **Via Environment Variable**: Set the `AWS_CRT_S3_MEMORY_LIMIT_IN_GIB` environment variable:
35+
36+
```bash
37+
export AWS_CRT_S3_MEMORY_LIMIT_IN_GIB=4 # 4 GiB limit
38+
```
39+
40+
**Priority**: The configuration value takes precedence over the environment variable. If `memory_limit_in_bytes` is set to a non-zero value in the config, the environment variable is ignored.
41+
42+
**Default Behavior**: If neither is set (config is 0 and environment variable is not set), the client sets a default memory limit based on the target throughput.
43+
44+
**Notes**:
45+
* The limit applies per client. If multiple clients created, limit will apply to each separately.
46+
* The environment variable value must be a valid positive integer representing gigabytes (GiB).
47+
* The value is converted from GiB to bytes internally (1 GiB = 1024³ bytes).
48+
* Invalid values or overflow conditions will cause client creation to fail with `AWS_ERROR_INVALID_ARGUMENT`.
1749

1850
## License
1951

@@ -86,14 +118,19 @@ cmake --build aws-c-s3/build --target install
86118
After installing all the dependencies, and building aws-c-s3, you can run the sample directly from the s3 build directory.
87119

88120
To download:
121+
89122
```
90123
aws-c-s3/build/samples/s3/s3 cp s3://<bucket-name>/<object-name> <download-path> --region <region>
91124
```
125+
92126
To upload:
127+
93128
```
94129
aws-c-s3/build/samples/s3/s3 cp <upload-path> s3://<bucket-name>/<object-name> --region <region>
95130
```
131+
96132
To list objects:
133+
97134
```
98135
aws-c-s3/build/samples/s3/s3 ls s3://<bucket-name> --region <region>
99136
```

include/aws/s3/private/s3_auto_ranged_get.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ struct aws_s3_auto_ranged_get {
2121

2222
struct aws_string *etag;
2323

24+
/* Estimated object stored part size based on ETag analysis */
25+
uint64_t estimated_object_stored_part_size;
26+
/* Number of parts stored in S3. We derive this from ETag, if ETag is not formatted as expected, this will be
27+
* default to 1.
28+
* Note: For S3Express Append, the object will be treated as a single part, even though, it can be multiple parts
29+
* stored in S3.
30+
*/
31+
uint64_t num_stored_parts;
32+
/* Part size was set or not from user for this meta request. */
33+
bool part_size_set;
34+
bool force_dynamic_part_size;
35+
2436
bool initial_message_has_start_range;
2537
bool initial_message_has_end_range;
2638
uint64_t initial_range_start;
@@ -74,6 +86,7 @@ AWS_S3_API struct aws_s3_meta_request *aws_s3_meta_request_auto_ranged_get_new(
7486
struct aws_allocator *allocator,
7587
struct aws_s3_client *client,
7688
size_t part_size,
89+
bool part_size_set,
7790
const struct aws_s3_meta_request_options *options);
7891

7992
AWS_EXTERN_C_END

include/aws/s3/private/s3_checksums.h

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,29 @@ struct aws_s3_meta_request_checksum_config_storage {
6262
};
6363

6464
/**
65-
* a stream that takes in a stream
65+
* Helper stream that takes in a stream and the checksum context to help finalize the checksum from the underlying
66+
* stream.
67+
* The context will be only finalized when the checksum stream has read to the end of stream.
68+
*
69+
* Note: seek this stream will immediately fail, as it would prevent an accurate calculation of the
70+
* checksum.
71+
*
72+
* @param allocator
73+
* @param existing_stream The real content to read from. Destroying the checksum stream destroys the existing stream.
74+
* outputs the checksum of existing stream to checksum_output upon destruction. Will be kept
75+
* alive by the checksum stream
76+
* @param context Checksum context to keep and get checksum requirements from.
77+
*/
78+
AWS_S3_API
79+
struct aws_input_stream *aws_checksum_stream_new_with_context(
80+
struct aws_allocator *allocator,
81+
struct aws_input_stream *existing_stream,
82+
struct aws_s3_upload_request_checksum_context *context);
83+
84+
/**
85+
* Helper stream that takes in a stream to keep track of the checksum of the underlying stream during read.
86+
* Invoke `aws_checksum_stream_finalize_checksum` to get the checksum of the data has been read so far.
87+
*
6688
* Note: seek this stream will immediately fail, as it would prevent an accurate calculation of the
6789
* checksum.
6890
*
@@ -85,15 +107,6 @@ struct aws_input_stream *aws_checksum_stream_new(
85107
AWS_S3_API
86108
int aws_checksum_stream_finalize_checksum(struct aws_input_stream *checksum_stream, struct aws_byte_buf *checksum_buf);
87109

88-
/**
89-
* Finalize the checksum has read so far to the checksum context.
90-
* Not thread safe.
91-
*/
92-
AWS_S3_API
93-
int aws_checksum_stream_finalize_checksum_context(
94-
struct aws_input_stream *checksum_stream,
95-
struct aws_s3_upload_request_checksum_context *checksum_context);
96-
97110
/**
98111
* TODO: properly support chunked encoding.
99112
* Creates a chunked encoding stream that wraps an existing stream and adds checksum trailers.

include/aws/s3/private/s3_client_impl.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,20 @@ struct aws_s3_client_vtable {
168168

169169
void (*finish_destroy)(struct aws_s3_client *client);
170170

171-
struct aws_parallel_input_stream *(
172-
*parallel_input_stream_new_from_file)(struct aws_allocator *allocator, struct aws_byte_cursor file_name);
171+
struct aws_parallel_input_stream *(*parallel_input_stream_new_from_file)(
172+
struct aws_allocator *allocator,
173+
struct aws_byte_cursor file_name,
174+
struct aws_event_loop_group *reading_elg,
175+
bool direct_io_read);
173176

174177
struct aws_http_stream *(*http_connection_make_request)(
175178
struct aws_http_connection *client_connection,
176179
const struct aws_http_make_request_options *options);
177180

178-
void (*after_prepare_upload_part_finish)(struct aws_s3_request *request, struct aws_http_message *message);
181+
#ifdef AWS_C_S3_ENABLE_TEST_STUBS
182+
/********************* TEST ONLY STUB **************************/
183+
void (*after_prepare_upload_part_finish_stub)(struct aws_s3_request *request, struct aws_http_message *message);
184+
#endif
179185
};
180186

181187
struct aws_s3_upload_part_timeout_stats {
@@ -231,10 +237,21 @@ struct aws_s3_client {
231237
* to meta requests for use. */
232238
const size_t part_size;
233239

240+
bool part_size_set;
241+
234242
/* Size of parts for files when doing gets or puts. This exists on the client as configurable option that is passed
235243
* to meta requests for use. */
236244
const uint64_t max_part_size;
237245

246+
/* Calculated optimal range size for GET operations based on client configuration (memory limits, throughput
247+
* targets). This is used when part_size is not explicitly configured, replacing the default with reasonable
248+
* calculation. Value is calculated during client initialization and remains constant for the client's lifetime. */
249+
const uint64_t optimal_range_size;
250+
251+
/* File I/O options. */
252+
bool fio_options_set;
253+
struct aws_s3_file_io_options fio_opts;
254+
238255
/* The size threshold in bytes for when to use multipart uploads for a AWS_S3_META_REQUEST_TYPE_PUT_OBJECT meta
239256
* request. Uploads over this size will automatically use a multipart upload strategy, while uploads smaller or
240257
* equal to this threshold will use a single request to upload the whole object. If not set, `part_size` will be
@@ -351,6 +368,9 @@ struct aws_s3_client {
351368

352369
/* Number of requests currently scheduled to be streamed the response body or are actively being streamed. */
353370
struct aws_atomic_var num_requests_streaming_response;
371+
372+
/* Number of overall requests currently streaming the request body instead of buffering. */
373+
struct aws_atomic_var num_requests_streaming_request_body;
354374
} stats;
355375

356376
struct {

include/aws/s3/private/s3_default_buffer_pool.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* SPDX-License-Identifier: Apache-2.0.
77
*/
88

9+
#include <aws/common/hash_table.h>
10+
#include <aws/common/mutex.h>
911
#include <aws/s3/s3.h>
1012
#include <aws/s3/s3_buffer_pool.h>
1113

@@ -59,11 +61,79 @@ struct aws_s3_default_buffer_pool_usage_stats {
5961
/* Secondary memory reserved, but not yet used. Accurate, maps directly to base allocator. */
6062
size_t secondary_reserved;
6163

64+
/* Overall memory allocated for special-sized blocks. */
65+
size_t special_blocks_allocated;
66+
/* Number of special block sizes created. */
67+
size_t special_blocks_num;
68+
/* Memory reserved in special-sized blocks. */
69+
size_t special_blocks_reserved;
70+
/* Memory used in special-sized blocks. */
71+
size_t special_blocks_used;
72+
6273
/* Bytes used in "forced" buffers (created even if they exceed memory limits).
6374
* This is always <= primary_used + secondary_used */
6475
size_t forced_used;
6576
};
6677

78+
/* Structure to track special-sized blocks */
79+
struct s3_special_block_list {
80+
struct aws_allocator *allocator;
81+
size_t buffer_size; /* Size of buffers in this list */
82+
struct aws_array_list blocks; /* Array of uint8_t* pointers to allocated blocks */
83+
};
84+
85+
struct aws_s3_default_buffer_pool {
86+
struct aws_allocator *base_allocator;
87+
struct aws_mutex mutex;
88+
89+
size_t block_size;
90+
size_t chunk_size;
91+
/* size at which allocations should go to secondary */
92+
size_t primary_size_cutoff;
93+
94+
/* NOTE: See aws_s3_buffer_pool_usage_stats for descriptions of most fields */
95+
96+
size_t mem_limit;
97+
98+
size_t primary_allocated;
99+
size_t primary_reserved;
100+
size_t primary_used;
101+
102+
size_t special_blocks_allocated;
103+
size_t special_blocks_reserved;
104+
size_t special_blocks_used;
105+
106+
size_t secondary_reserved;
107+
size_t secondary_used;
108+
109+
size_t forced_used;
110+
111+
struct aws_array_list blocks;
112+
113+
struct aws_linked_list pending_reserves;
114+
115+
/* Special-sized blocks: hash table mapping size -> struct s3_special_block_list * */
116+
/* TODO: let's discuss about the special list lifetime. Should we just keep it with the memory pool? Concern is that
117+
* the pool will live with the client, and may result in all sorts of special lists to be around. */
118+
struct aws_hash_table special_blocks;
119+
120+
/* TEST ONLY: to force the special blocks alive during trim. */
121+
bool force_keeping_special_blocks;
122+
};
123+
124+
struct s3_pending_reserve {
125+
struct aws_linked_list_node node;
126+
struct aws_future_s3_buffer_ticket *ticket_future;
127+
struct aws_s3_default_buffer_ticket *ticket;
128+
struct aws_s3_buffer_pool_reserve_meta meta;
129+
};
130+
131+
struct s3_buffer_pool_block {
132+
size_t block_size;
133+
uint8_t *block_ptr;
134+
uint16_t alloc_bit_mask;
135+
};
136+
67137
/*
68138
* Create new buffer pool.
69139
* chunk_size - specifies the size of memory that will most commonly be acquired

0 commit comments

Comments
 (0)