Skip to content

Commit 8f07568

Browse files
authored
add ref-counting to aws_websocket (#405)
1 parent 5400050 commit 8f07568

File tree

4 files changed

+53
-44
lines changed

4 files changed

+53
-44
lines changed

include/aws/http/websocket.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,21 @@ AWS_HTTP_API
351351
int aws_websocket_client_connect(const struct aws_websocket_client_connection_options *options);
352352

353353
/**
354+
* Increment the websocket's ref-count, preventing it from being destroyed.
355+
* @return Always returns the same pointer that is passed in.
356+
*/
357+
AWS_HTTP_API
358+
struct aws_websocket *aws_websocket_acquire(struct aws_websocket *websocket);
359+
360+
/**
361+
* Decrement the websocket's ref-count.
362+
* When the ref-count reaches zero, the connection will shut down, if it hasn't already.
354363
* Users must release the websocket when they are done with it.
355364
* The websocket's memory cannot be reclaimed until this is done.
356-
* If the websocket connection was not already shutting down, it will be shut down.
357365
* Callbacks may continue firing after this is called, with "shutdown" being the final callback.
358366
* This function may be called from any thread.
367+
*
368+
* It is safe to pass NULL, nothing will happen.
359369
*/
360370
AWS_HTTP_API
361371
void aws_websocket_release(struct aws_websocket *websocket);

source/websocket.c

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <aws/common/device_random.h>
1010
#include <aws/common/encoding.h>
1111
#include <aws/common/mutex.h>
12+
#include <aws/common/ref_count.h>
1213
#include <aws/http/private/websocket_decoder.h>
1314
#include <aws/http/private/websocket_encoder.h>
1415
#include <aws/http/request_response.h>
@@ -41,6 +42,7 @@ struct outgoing_frame {
4142

4243
struct aws_websocket {
4344
struct aws_allocator *alloc;
45+
struct aws_ref_count ref_count;
4446
struct aws_channel_handler channel_handler;
4547
struct aws_channel_slot *channel_slot;
4648
size_t initial_window_size;
@@ -137,9 +139,6 @@ struct aws_websocket {
137139

138140
/* Mirrors variable from thread_data */
139141
bool is_midchannel_handler;
140-
141-
/* Whether aws_websocket_release() has been called */
142-
bool is_released;
143142
} synced_data;
144143
};
145144

@@ -168,6 +167,7 @@ static int s_handler_shutdown(
168167
static size_t s_handler_initial_window_size(struct aws_channel_handler *handler);
169168
static size_t s_handler_message_overhead(struct aws_channel_handler *handler);
170169
static void s_handler_destroy(struct aws_channel_handler *handler);
170+
static void s_websocket_on_refcount_zero(void *user_data);
171171

172172
static int s_encoder_stream_outgoing_payload(struct aws_byte_buf *out_buf, void *user_data);
173173

@@ -271,6 +271,7 @@ struct aws_websocket *aws_websocket_handler_new(const struct aws_websocket_handl
271271
}
272272

273273
websocket->alloc = options->allocator;
274+
aws_ref_count_init(&websocket->ref_count, websocket, s_websocket_on_refcount_zero);
274275
websocket->channel_handler.vtable = &s_channel_handler_vtable;
275276
websocket->channel_handler.alloc = options->allocator;
276277
websocket->channel_handler.impl = websocket;
@@ -357,30 +358,28 @@ static void s_handler_destroy(struct aws_channel_handler *handler) {
357358
aws_mem_release(websocket->alloc, websocket);
358359
}
359360

360-
void aws_websocket_release(struct aws_websocket *websocket) {
361-
AWS_ASSERT(websocket);
362-
AWS_ASSERT(websocket->channel_slot);
363-
364-
bool was_already_released;
365-
366-
/* BEGIN CRITICAL SECTION */
367-
s_lock_synced_data(websocket);
368-
if (websocket->synced_data.is_released) {
369-
was_already_released = true;
370-
} else {
371-
was_already_released = false;
372-
websocket->synced_data.is_released = true;
373-
}
374-
s_unlock_synced_data(websocket);
375-
/* END CRITICAL SECTION */
361+
struct aws_websocket *aws_websocket_acquire(struct aws_websocket *websocket) {
362+
AWS_PRECONDITION(websocket);
363+
AWS_LOGF_TRACE(AWS_LS_HTTP_WEBSOCKET, "id=%p: Acquiring websocket ref-count.", (void *)websocket);
364+
aws_ref_count_acquire(&websocket->ref_count);
365+
return websocket;
366+
}
376367

377-
if (was_already_released) {
378-
AWS_LOGF_TRACE(
379-
AWS_LS_HTTP_WEBSOCKET, "id=%p: Ignoring multiple calls to websocket release.", (void *)websocket);
368+
void aws_websocket_release(struct aws_websocket *websocket) {
369+
if (!websocket) {
380370
return;
381371
}
382372

383-
AWS_LOGF_TRACE(AWS_LS_HTTP_WEBSOCKET, "id=%p: Websocket released, shut down if necessary.", (void *)websocket);
373+
AWS_LOGF_TRACE(AWS_LS_HTTP_WEBSOCKET, "id=%p: Releasing websocket ref-count.", (void *)websocket);
374+
aws_ref_count_release(&websocket->ref_count);
375+
}
376+
377+
static void s_websocket_on_refcount_zero(void *user_data) {
378+
struct aws_websocket *websocket = user_data;
379+
AWS_ASSERT(websocket->channel_slot);
380+
381+
AWS_LOGF_TRACE(
382+
AWS_LS_HTTP_WEBSOCKET, "id=%p: Websocket ref-count is zero, shut down if necessary.", (void *)websocket);
384383

385384
/* Channel might already be shut down, but make sure */
386385
s_schedule_channel_shutdown(websocket, AWS_ERROR_SUCCESS);
@@ -422,26 +421,6 @@ int aws_websocket_convert_to_midchannel_handler(struct aws_websocket *websocket)
422421
return aws_raise_error(AWS_ERROR_INVALID_STATE);
423422
}
424423

425-
bool was_released = false;
426-
427-
/* BEGIN CRITICAL SECTION */
428-
s_lock_synced_data(websocket);
429-
if (websocket->synced_data.is_released) {
430-
was_released = true;
431-
} else {
432-
websocket->synced_data.is_midchannel_handler = true;
433-
}
434-
s_unlock_synced_data(websocket);
435-
/* END CRITICAL SECTION */
436-
437-
if (was_released) {
438-
AWS_LOGF_ERROR(
439-
AWS_LS_HTTP_WEBSOCKET,
440-
"id=%p: Cannot convert websocket to midchannel handler because it was already released.",
441-
(void *)websocket);
442-
return aws_raise_error(AWS_ERROR_HTTP_CONNECTION_CLOSED);
443-
}
444-
445424
websocket->thread_data.is_midchannel_handler = true;
446425

447426
return AWS_OP_SUCCESS;

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ add_test_case(websocket_encoder_fragmented_message)
180180
add_test_case(websocket_encoder_fragmentation_failure_checks)
181181
add_test_case(websocket_encoder_payload_callback_can_fail_encoder)
182182
add_test_case(websocket_handler_sanity_check)
183+
add_test_case(websocket_handler_refcounting)
183184
add_test_case(websocket_handler_send_frame)
184185
add_test_case(websocket_handler_send_frame_off_thread)
185186
add_test_case(websocket_handler_send_multiple_frames)

tests/test_websocket_handler.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,25 @@ TEST_CASE(websocket_handler_sanity_check) {
650650
return AWS_OP_SUCCESS;
651651
}
652652

653+
TEST_CASE(websocket_handler_refcounting) {
654+
(void)ctx;
655+
struct tester tester;
656+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
657+
658+
/* acquire() and then release() a refcount. The websocket should not shut down yet */
659+
ASSERT_PTR_EQUALS(tester.websocket, aws_websocket_acquire(tester.websocket));
660+
aws_websocket_release(tester.websocket);
661+
662+
testing_channel_drain_queued_tasks(&tester.testing_channel);
663+
ASSERT_FALSE(tester.testing_channel.channel_shutdown_completed);
664+
665+
/* should be safe to call release() on NULL */
666+
aws_websocket_release(NULL);
667+
668+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
669+
return AWS_OP_SUCCESS;
670+
}
671+
653672
static int s_websocket_handler_send_frame_common(struct aws_allocator *allocator, bool on_thread) {
654673
struct tester tester;
655674
ASSERT_SUCCESS(s_tester_init(&tester, allocator));

0 commit comments

Comments
 (0)