Skip to content

Commit c4930d2

Browse files
authored
Fix memory leak during signing (#596)
**Issue:** (internal ticket V1637484435) When doing SigV4A signing from aws-sdk-js-v3, memory usage gradually increases due to memory leaks. **Description of changes:** Fix leak that occurred if headers were queried repeatedly. **Future Work:** All native unit tests should check that they aren't leaking memory. I spent a few hours trying to add such tests, but couldn't get it working in a reliable way. I didn't want this to hold back the fix, so leaving it for another day. See these blog posts for hints on various techniques NodeJS has used for leak testing through the years: https://joyeecheung.github.io/blog/2024/03/17/memory-leak-testing-v8-node-js-1/, https://joyeecheung.github.io/blog/2024/03/17/memory-leak-testing-v8-node-js-2/
1 parent b9f731d commit c4930d2

File tree

4 files changed

+23
-31
lines changed

4 files changed

+23
-31
lines changed

source/http_connection.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,12 @@ struct http_connection_binding {
149149
/* finalizer called when node cleans up this object */
150150
static void s_http_connection_from_manager_binding_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
151151
(void)finalize_hint;
152-
(void)env;
153152
struct http_connection_binding *binding = finalize_data;
154153

154+
if (binding->node_external != NULL) {
155+
napi_delete_reference(env, binding->node_external);
156+
}
157+
155158
/* no release call, the http_client_connection_manager has already released it */
156159
aws_mem_release(binding->allocator, binding);
157160
}

source/http_connection_manager.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ struct aws_http_connection_manager *aws_napi_get_http_connection_manager(
2727

2828
static void s_http_connection_manager_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
2929
(void)finalize_hint;
30-
(void)env;
3130
struct http_connection_manager_binding *binding = finalize_data;
31+
if (binding->node_external != NULL) {
32+
napi_delete_reference(env, binding->node_external);
33+
}
3234
aws_mem_release(binding->allocator, binding);
3335
}
3436

source/http_message.c

+15-28
Original file line numberDiff line numberDiff line change
@@ -79,29 +79,29 @@ napi_status aws_napi_http_message_bind(napi_env env, napi_value exports) {
7979

8080
struct http_request_binding {
8181
struct aws_http_message *native;
82-
struct aws_allocator *allocator;
8382

8483
napi_ref node_headers;
8584
};
8685

87-
/* Need a special finalizer to avoid releasing a request object we don't own */
88-
static void s_napi_wrapped_http_request_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
89-
(void)env;
86+
static void s_napi_http_request_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
9087
(void)finalize_hint;
91-
9288
struct http_request_binding *binding = finalize_data;
93-
struct aws_allocator *allocator = binding->allocator;
89+
struct aws_allocator *allocator = aws_napi_get_allocator();
90+
91+
if (binding->node_headers != NULL) {
92+
napi_delete_reference(env, binding->node_headers);
93+
}
9494

95+
aws_http_message_release(binding->native);
9596
aws_mem_release(allocator, binding);
9697
}
9798

9899
napi_status aws_napi_http_message_wrap(napi_env env, struct aws_http_message *message, napi_value *result) {
99100

100101
struct http_request_binding *binding =
101102
aws_mem_calloc(aws_napi_get_allocator(), 1, sizeof(struct http_request_binding));
102-
binding->native = message;
103-
binding->allocator = aws_napi_get_allocator();
104-
return aws_napi_wrap(env, &s_request_class_info, binding, s_napi_wrapped_http_request_finalize, result);
103+
binding->native = aws_http_message_acquire(message);
104+
return aws_napi_wrap(env, &s_request_class_info, binding, s_napi_http_request_finalize, result);
105105
}
106106

107107
struct aws_http_message *aws_napi_http_message_unwrap(napi_env env, napi_value js_object) {
@@ -115,20 +115,6 @@ struct aws_http_message *aws_napi_http_message_unwrap(napi_env env, napi_value j
115115
* Constructor
116116
**********************************************************************************************************************/
117117

118-
static void s_napi_http_request_finalize(napi_env env, void *finalize_data, void *finalize_hint) {
119-
(void)env;
120-
121-
struct http_request_binding *binding = finalize_data;
122-
struct aws_allocator *allocator = finalize_hint;
123-
124-
if (binding->node_headers != NULL) {
125-
napi_delete_reference(env, binding->node_headers);
126-
}
127-
128-
aws_http_message_destroy(binding->native);
129-
aws_mem_release(allocator, binding);
130-
}
131-
132118
static napi_value s_request_constructor(napi_env env, const struct aws_napi_callback_info *cb_info) {
133119

134120
struct aws_allocator *alloc = aws_napi_get_allocator();
@@ -167,7 +153,7 @@ static napi_value s_request_constructor(napi_env env, const struct aws_napi_call
167153
}
168154

169155
napi_value node_this = cb_info->native_this;
170-
AWS_NAPI_CALL(env, napi_wrap(env, node_this, binding, s_napi_http_request_finalize, alloc, NULL), {
156+
AWS_NAPI_CALL(env, napi_wrap(env, node_this, binding, s_napi_http_request_finalize, NULL, NULL), {
171157
napi_throw_error(env, NULL, "Failed to wrap HttpRequest");
172158
goto cleanup;
173159
});
@@ -177,7 +163,7 @@ static napi_value s_request_constructor(napi_env env, const struct aws_napi_call
177163
cleanup:
178164
if (binding) {
179165
if (binding->native) {
180-
aws_http_message_destroy(binding->native);
166+
aws_http_message_release(binding->native);
181167
}
182168
aws_mem_release(alloc, binding);
183169
}
@@ -238,15 +224,16 @@ static napi_value s_request_headers_get(napi_env env, void *native_this) {
238224

239225
napi_value result = NULL;
240226
if (binding->node_headers) {
227+
/* This HTTP request already has a reference to the Node object for the headers */
241228
AWS_NAPI_ENSURE(env, napi_get_reference_value(env, binding->node_headers, &result));
242229
} else {
230+
/* There is no Node object for these headers.
231+
* Create a Node object, and a reference to it, and store that reference on this HTTP request */
243232
struct aws_http_headers *headers = aws_http_message_get_headers(binding->native);
244233
AWS_NAPI_ENSURE(env, aws_napi_http_headers_wrap(env, headers, &result));
234+
AWS_NAPI_ENSURE(env, napi_create_reference(env, result, 1, &binding->node_headers));
245235
}
246236

247-
/* Store the value for later */
248-
AWS_NAPI_ENSURE(env, napi_create_reference(env, result, 1, &binding->node_headers));
249-
250237
return result;
251238
}
252239

source/http_stream.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static void s_on_response_call(napi_env env, napi_value on_response, void *conte
7474
}
7575

7676
/* clean up the response buffer */
77-
aws_http_message_destroy(binding->response);
77+
aws_http_message_release(binding->response);
7878
binding->response = NULL;
7979
}
8080

0 commit comments

Comments
 (0)