Skip to content

Commit 454c2f0

Browse files
committed
memory limit from env var
1 parent 1e2f77c commit 454c2f0

File tree

4 files changed

+238
-12
lines changed

4 files changed

+238
-12
lines changed

README.md

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,48 @@
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 environment variable value must be a valid positive integer representing gigabytes (GiB).
46+
* The value is converted from GiB to bytes internally (1 GiB = 1024³ bytes).
47+
* Invalid values or overflow conditions will cause client creation to fail with `AWS_ERROR_INVALID_ARGUMENT`.
1748

1849
## License
1950

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

88119
To download:
120+
89121
```
90122
aws-c-s3/build/samples/s3/s3 cp s3://<bucket-name>/<object-name> <download-path> --region <region>
91123
```
124+
92125
To upload:
126+
93127
```
94128
aws-c-s3/build/samples/s3/s3 cp <upload-path> s3://<bucket-name>/<object-name> --region <region>
95129
```
130+
96131
To list objects:
132+
97133
```
98134
aws-c-s3/build/samples/s3/s3 ls s3://<bucket-name> --region <region>
99135
```

source/s3_client.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <aws/common/atomics.h>
2222
#include <aws/common/clock.h>
2323
#include <aws/common/device_random.h>
24+
#include <aws/common/environment.h>
2425
#include <aws/common/file.h>
2526
#include <aws/common/json.h>
2627
#include <aws/common/math.h>
@@ -334,7 +335,7 @@ struct aws_s3_client *aws_s3_client_new(
334335
if (memory_limit_from_env_var) {
335336
uint64_t mem_limit_in_gib = 0;
336337
if (aws_byte_cursor_utf8_parse_u64(
337-
aws_byte_cursor_from_string(memory_limit_from_env_var), mem_limit_in_gib)) {
338+
aws_byte_cursor_from_string(memory_limit_from_env_var), &mem_limit_in_gib)) {
338339
aws_string_destroy(memory_limit_from_env_var);
339340
AWS_LOGF_ERROR(
340341
AWS_LS_S3_CLIENT,
@@ -344,18 +345,20 @@ struct aws_s3_client *aws_s3_client_new(
344345
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
345346
return NULL;
346347
}
348+
aws_string_destroy(memory_limit_from_env_var);
347349
uint64_t mem_limit_in_bytes = 0;
348350
/* Covert mem_limit_in_gib to bytes */
349-
if (aws_mul_u64_checked(mem_limit_in_gib, 1024, mem_limit_in_bytes) ||
350-
aws_mul_u64_checked(mem_limit_in_bytes, 1024, mem_limit_in_bytes) ||
351-
aws_mul_u64_checked(mem_limit_in_bytes, 1024, mem_limit_in_bytes)) {
351+
if (aws_mul_u64_checked(mem_limit_in_gib, 1024, &mem_limit_in_bytes) ||
352+
aws_mul_u64_checked(mem_limit_in_bytes, 1024, &mem_limit_in_bytes) ||
353+
aws_mul_u64_checked(mem_limit_in_bytes, 1024, &mem_limit_in_bytes)) {
352354
AWS_LOGF_ERROR(
353355
AWS_LS_S3_CLIENT,
354356
"Cannot create client from client_config; envrionment variable: %s, overflow detected.",
355357
s_memory_limit_env_var);
356358
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
357359
return NULL;
358360
}
361+
359362
mem_limit_configured = mem_limit_in_bytes;
360363
}
361364
} else {

tests/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,12 @@ add_net_test_case(client_update_upload_part_timeout)
448448
add_net_test_case(client_meta_request_override_part_size)
449449
add_net_test_case(client_meta_request_override_multipart_upload_threshold)
450450

451+
# Memory limit environment variable tests
452+
add_net_test_case(s3_client_memory_limit_from_env_var_valid)
453+
add_net_test_case(s3_client_memory_limit_config_takes_precedence)
454+
add_net_test_case(s3_client_memory_limit_from_env_var_invalid)
455+
add_net_test_case(s3_client_memory_limit_from_env_var_overflow)
456+
451457
add_net_test_case(test_s3_default_get_without_content_length)
452458

453459
set(TEST_BINARY_NAME ${PROJECT_NAME}-tests)
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/**
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
#include "s3_tester.h"
7+
#include <aws/common/environment.h>
8+
#include <aws/s3/private/s3_default_buffer_pool.h>
9+
#include <aws/s3/private/s3_util.h>
10+
#include <aws/testing/aws_test_harness.h>
11+
12+
#define TEST_CASE(NAME) \
13+
AWS_TEST_CASE(NAME, s_test_##NAME); \
14+
static int s_test_##NAME(struct aws_allocator *allocator, void *ctx)
15+
16+
static const char *s_memory_limit_env_var = "AWS_CRT_S3_MEMORY_LIMIT_IN_GIB";
17+
18+
/* Copied from s3_default_buffer_pool.c */
19+
static const size_t s_buffer_pool_reserved_mem = MB_TO_BYTES(128);
20+
21+
/**
22+
* Test that memory limit can be set via environment variable when config value is 0
23+
*/
24+
TEST_CASE(s3_client_memory_limit_from_env_var_valid) {
25+
(void)ctx;
26+
struct aws_s3_tester tester;
27+
ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));
28+
29+
/* Set environment variable to 4 GiB */
30+
struct aws_string *env_var_name = aws_string_new_from_c_str(allocator, s_memory_limit_env_var);
31+
struct aws_string *env_var_value = aws_string_new_from_c_str(allocator, "1");
32+
ASSERT_SUCCESS(aws_set_environment_value(env_var_name, env_var_value));
33+
34+
struct aws_s3_client_config client_config = {
35+
.part_size = MB_TO_BYTES(8),
36+
.throughput_target_gbps = 10.0,
37+
.memory_limit_in_bytes = 0, /* Will read from environment variable */
38+
};
39+
40+
ASSERT_SUCCESS(aws_s3_tester_bind_client(
41+
&tester, &client_config, AWS_S3_TESTER_BIND_CLIENT_REGION | AWS_S3_TESTER_BIND_CLIENT_SIGNING));
42+
43+
struct aws_s3_client *client = aws_s3_client_new(allocator, &client_config);
44+
ASSERT_TRUE(client != NULL);
45+
46+
/* Verify that buffer pool was configured with 4 GiB limit */
47+
size_t expected_memory_limit = GB_TO_BYTES(1) - s_buffer_pool_reserved_mem;
48+
ASSERT_TRUE(client->buffer_pool != NULL);
49+
struct aws_s3_default_buffer_pool_usage_stats stats = aws_s3_default_buffer_pool_get_usage(client->buffer_pool);
50+
ASSERT_UINT_EQUALS(stats.mem_limit, expected_memory_limit);
51+
52+
aws_s3_client_release(client);
53+
54+
/* Clean up environment variable */
55+
aws_string_destroy(env_var_name);
56+
aws_string_destroy(env_var_value);
57+
58+
aws_s3_tester_clean_up(&tester);
59+
return AWS_OP_SUCCESS;
60+
}
61+
62+
/**
63+
* Test that config value takes precedence over environment variable
64+
*/
65+
TEST_CASE(s3_client_memory_limit_config_takes_precedence) {
66+
(void)ctx;
67+
struct aws_s3_tester tester;
68+
ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));
69+
70+
/* Set environment variable to 4 GiB */
71+
struct aws_string *env_var_name = aws_string_new_from_c_str(allocator, s_memory_limit_env_var);
72+
struct aws_string *env_var_value = aws_string_new_from_c_str(allocator, "1");
73+
ASSERT_SUCCESS(aws_set_environment_value(env_var_name, env_var_value));
74+
aws_string_destroy(env_var_name);
75+
aws_string_destroy(env_var_value);
76+
77+
struct aws_s3_client_config client_config = {
78+
.part_size = MB_TO_BYTES(8),
79+
.throughput_target_gbps = 10.0,
80+
.memory_limit_in_bytes = GB_TO_BYTES(2), /* Config value should take precedence */
81+
};
82+
ASSERT_SUCCESS(aws_s3_tester_bind_client(
83+
&tester, &client_config, AWS_S3_TESTER_BIND_CLIENT_REGION | AWS_S3_TESTER_BIND_CLIENT_SIGNING));
84+
85+
struct aws_s3_client *client = aws_s3_client_new(allocator, &client_config);
86+
ASSERT_TRUE(client != NULL);
87+
88+
/* The 2 GiB from config should be used, not the 1 GiB from env var */
89+
ASSERT_TRUE(client->buffer_pool != NULL);
90+
size_t expected_memory_limit = GB_TO_BYTES(2) - s_buffer_pool_reserved_mem;
91+
ASSERT_TRUE(client->buffer_pool != NULL);
92+
struct aws_s3_default_buffer_pool_usage_stats stats = aws_s3_default_buffer_pool_get_usage(client->buffer_pool);
93+
ASSERT_UINT_EQUALS(stats.mem_limit, expected_memory_limit);
94+
95+
aws_s3_client_release(client);
96+
97+
/* Clean up environment variable */
98+
env_var_name = aws_string_new_from_c_str(allocator, s_memory_limit_env_var);
99+
ASSERT_SUCCESS(aws_unset_environment_value(env_var_name));
100+
aws_string_destroy(env_var_name);
101+
102+
aws_s3_tester_clean_up(&tester);
103+
return AWS_OP_SUCCESS;
104+
}
105+
106+
/**
107+
* Test that invalid environment variable value causes client creation to fail
108+
*/
109+
TEST_CASE(s3_client_memory_limit_from_env_var_invalid) {
110+
(void)ctx;
111+
struct aws_s3_tester tester;
112+
ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));
113+
114+
/* Set environment variable to invalid value */
115+
struct aws_string *env_var_name = aws_string_new_from_c_str(allocator, s_memory_limit_env_var);
116+
struct aws_string *env_var_value = aws_string_new_from_c_str(allocator, "invalid");
117+
ASSERT_SUCCESS(aws_set_environment_value(env_var_name, env_var_value));
118+
aws_string_destroy(env_var_name);
119+
aws_string_destroy(env_var_value);
120+
121+
struct aws_s3_client_config client_config = {
122+
.part_size = MB_TO_BYTES(8),
123+
.throughput_target_gbps = 10.0,
124+
.memory_limit_in_bytes = 0, /* Will try to read from environment variable */
125+
};
126+
ASSERT_SUCCESS(aws_s3_tester_bind_client(
127+
&tester, &client_config, AWS_S3_TESTER_BIND_CLIENT_REGION | AWS_S3_TESTER_BIND_CLIENT_SIGNING));
128+
129+
/* Client creation should fail due to invalid env var value */
130+
struct aws_s3_client *client = aws_s3_client_new(allocator, &client_config);
131+
ASSERT_TRUE(client == NULL);
132+
ASSERT_INT_EQUALS(AWS_ERROR_INVALID_ARGUMENT, aws_last_error());
133+
/* Client failed to set up. */
134+
tester.bound_to_client = false;
135+
/* Clean up environment variable */
136+
env_var_name = aws_string_new_from_c_str(allocator, s_memory_limit_env_var);
137+
ASSERT_SUCCESS(aws_unset_environment_value(env_var_name));
138+
aws_string_destroy(env_var_name);
139+
140+
aws_s3_tester_clean_up(&tester);
141+
return AWS_OP_SUCCESS;
142+
}
143+
144+
/**
145+
* Test that environment variable with value causing overflow is handled properly
146+
*/
147+
TEST_CASE(s3_client_memory_limit_from_env_var_overflow) {
148+
(void)ctx;
149+
struct aws_s3_tester tester;
150+
ASSERT_SUCCESS(aws_s3_tester_init(allocator, &tester));
151+
152+
/* Set environment variable to a very large value that would overflow when converted to bytes */
153+
struct aws_string *env_var_name = aws_string_new_from_c_str(allocator, s_memory_limit_env_var);
154+
struct aws_string *env_var_value = aws_string_new_from_c_str(allocator, "18446744073709551615"); /* UINT64_MAX */
155+
ASSERT_SUCCESS(aws_set_environment_value(env_var_name, env_var_value));
156+
aws_string_destroy(env_var_name);
157+
aws_string_destroy(env_var_value);
158+
159+
struct aws_s3_client_config client_config = {
160+
.part_size = MB_TO_BYTES(8),
161+
.throughput_target_gbps = 10.0,
162+
.memory_limit_in_bytes = 0, /* Will try to read from environment variable */
163+
};
164+
165+
ASSERT_SUCCESS(aws_s3_tester_bind_client(
166+
&tester, &client_config, AWS_S3_TESTER_BIND_CLIENT_REGION | AWS_S3_TESTER_BIND_CLIENT_SIGNING));
167+
168+
/* Client creation should fail due to overflow during GiB to bytes conversion */
169+
struct aws_s3_client *client = aws_s3_client_new(allocator, &client_config);
170+
ASSERT_TRUE(client == NULL);
171+
ASSERT_INT_EQUALS(AWS_ERROR_INVALID_ARGUMENT, aws_last_error());
172+
/* Client failed to set up. */
173+
tester.bound_to_client = false;
174+
175+
/* Clean up environment variable */
176+
env_var_name = aws_string_new_from_c_str(allocator, s_memory_limit_env_var);
177+
ASSERT_SUCCESS(aws_unset_environment_value(env_var_name));
178+
aws_string_destroy(env_var_name);
179+
aws_s3_tester_clean_up(&tester);
180+
return AWS_OP_SUCCESS;
181+
}

0 commit comments

Comments
 (0)