diff --git a/include/aws/io/channel_bootstrap.h b/include/aws/io/channel_bootstrap.h index 4e62e026e..9f06c9362 100644 --- a/include/aws/io/channel_bootstrap.h +++ b/include/aws/io/channel_bootstrap.h @@ -218,9 +218,9 @@ struct aws_server_socket_channel_bootstrap_options { uint32_t port; const struct aws_socket_options *socket_options; const struct aws_tls_connection_options *tls_options; + aws_server_bootstrap_on_listener_setup_fn *setup_callback; aws_server_bootstrap_on_accept_channel_setup_fn *incoming_callback; aws_server_bootstrap_on_accept_channel_shutdown_fn *shutdown_callback; - aws_server_bootstrap_on_listener_setup_fn *setup_callback; aws_server_bootstrap_on_server_listener_destroy_fn *destroy_callback; bool enable_read_back_pressure; void *user_data; @@ -297,6 +297,9 @@ AWS_IO_API int aws_server_bootstrap_set_alpn_callback( * shutting down. Immediately after the `shutdown_callback` returns, the channel is cleaned up automatically. All * callbacks are invoked the thread of the event-loop that the listening socket is assigned to * + * `setup_callback`. If set, the callback will be asynchronously invoked when the listener is ready for use. For Apple + * Network Framework, the listener is not usable until the callback is invoked. + * * Upon shutdown of your application, you'll want to call `aws_server_bootstrap_destroy_socket_listener` with the return * value from this function. * @@ -305,9 +308,6 @@ AWS_IO_API int aws_server_bootstrap_set_alpn_callback( AWS_IO_API struct aws_socket *aws_server_bootstrap_new_socket_listener( const struct aws_server_socket_channel_bootstrap_options *bootstrap_options); -AWS_IO_API struct aws_socket *aws_server_bootstrap_new_socket_listener_async( - const struct aws_server_socket_channel_bootstrap_options *bootstrap_options); - /** * Shuts down 'listener' and cleans up any resources associated with it. Any incoming channels on `listener` will still * be active. `destroy_callback` will be invoked after the server socket listener is destroyed, and all associated diff --git a/include/aws/io/socket.h b/include/aws/io/socket.h index 0c5d12152..b278717ce 100644 --- a/include/aws/io/socket.h +++ b/include/aws/io/socket.h @@ -176,8 +176,8 @@ struct aws_socket_listener_options { void *on_accept_result_user_data; // This callback is invoked when the listener starts accepting incoming connections. - // It is only triggered in asynchronous listener APIs while using nw_socket. - aws_socket_on_accept_started_fn *on_accept_start_result; + // If the callback set, the socket must not be released before the callback invoked. + aws_socket_on_accept_started_fn *on_accept_start; void *on_accept_start_user_data; }; diff --git a/source/channel_bootstrap.c b/source/channel_bootstrap.c index 7ef2b3e6a..dd4fcb3ee 100644 --- a/source/channel_bootstrap.c +++ b/source/channel_bootstrap.c @@ -1704,141 +1704,6 @@ static void s_listener_destroy_task(struct aws_task *task, void *arg, enum aws_t aws_socket_clean_up(&server_connection_args->listener); } -struct aws_socket *aws_server_bootstrap_new_socket_listener( - const struct aws_server_socket_channel_bootstrap_options *bootstrap_options) { - AWS_PRECONDITION(bootstrap_options); - AWS_PRECONDITION(bootstrap_options->bootstrap); - AWS_PRECONDITION(bootstrap_options->incoming_callback); - AWS_PRECONDITION(bootstrap_options->shutdown_callback); - - struct server_connection_args *server_connection_args = - aws_mem_calloc(bootstrap_options->bootstrap->allocator, 1, sizeof(struct server_connection_args)); - if (!server_connection_args) { - return NULL; - } - - AWS_LOGF_INFO( - AWS_LS_IO_CHANNEL_BOOTSTRAP, - "id=%p: attempting to initialize a new " - "server socket listener for %s:%u", - (void *)bootstrap_options->bootstrap, - bootstrap_options->host_name, - bootstrap_options->port); - - aws_ref_count_init( - &server_connection_args->ref_count, - server_connection_args, - (aws_simple_completion_callback *)s_server_connection_args_destroy); - server_connection_args->user_data = bootstrap_options->user_data; - server_connection_args->bootstrap = aws_server_bootstrap_acquire(bootstrap_options->bootstrap); - server_connection_args->shutdown_callback = bootstrap_options->shutdown_callback; - server_connection_args->incoming_callback = bootstrap_options->incoming_callback; - server_connection_args->destroy_callback = bootstrap_options->destroy_callback; - server_connection_args->on_protocol_negotiated = bootstrap_options->bootstrap->on_protocol_negotiated; - server_connection_args->enable_read_back_pressure = bootstrap_options->enable_read_back_pressure; - - aws_task_init( - &server_connection_args->listener_destroy_task, - s_listener_destroy_task, - server_connection_args, - "listener socket destroy"); - - if (bootstrap_options->tls_options) { - AWS_LOGF_INFO( - AWS_LS_IO_CHANNEL_BOOTSTRAP, "id=%p: using tls on listener", (void *)bootstrap_options->tls_options); - if (aws_tls_connection_options_copy(&server_connection_args->tls_options, bootstrap_options->tls_options)) { - goto cleanup_server_connection_args; - } - - server_connection_args->use_tls = true; - - server_connection_args->tls_user_data = bootstrap_options->tls_options->user_data; - - /* in order to honor any callbacks a user may have installed on their tls_connection_options, - * we need to wrap them if they were set.*/ - if (bootstrap_options->bootstrap->on_protocol_negotiated) { - server_connection_args->tls_options.advertise_alpn_message = true; - } - - if (bootstrap_options->tls_options->on_data_read) { - server_connection_args->user_on_data_read = bootstrap_options->tls_options->on_data_read; - server_connection_args->tls_options.on_data_read = s_tls_server_on_data_read; - } - - if (bootstrap_options->tls_options->on_error) { - server_connection_args->user_on_error = bootstrap_options->tls_options->on_error; - server_connection_args->tls_options.on_error = s_tls_server_on_error; - } - - if (bootstrap_options->tls_options->on_negotiation_result) { - server_connection_args->user_on_negotiation_result = bootstrap_options->tls_options->on_negotiation_result; - } - - server_connection_args->tls_options.on_negotiation_result = s_tls_server_on_negotiation_result; - server_connection_args->tls_options.user_data = server_connection_args; - } - - struct aws_event_loop *connection_loop = - aws_event_loop_group_get_next_loop(bootstrap_options->bootstrap->event_loop_group); - - if (aws_socket_init( - &server_connection_args->listener, - bootstrap_options->bootstrap->allocator, - bootstrap_options->socket_options)) { - goto cleanup_server_connection_args; - } - - struct aws_socket_endpoint endpoint; - AWS_ZERO_STRUCT(endpoint); - size_t host_name_len = 0; - if (aws_secure_strlen(bootstrap_options->host_name, sizeof(endpoint.address), &host_name_len)) { - goto cleanup_server_connection_args; - } - - memcpy(endpoint.address, bootstrap_options->host_name, host_name_len); - endpoint.port = bootstrap_options->port; - - if (aws_socket_bind(&server_connection_args->listener, &endpoint)) { - goto cleanup_listener; - } - - if (aws_socket_listen(&server_connection_args->listener, 1024)) { - goto cleanup_listener; - } - - struct aws_socket_listener_options options = { - .on_accept_result = s_on_server_connection_result, - .on_accept_result_user_data = server_connection_args, - .on_accept_start_result = NULL, - .on_accept_start_user_data = NULL, - }; - - if (aws_socket_start_accept(&server_connection_args->listener, connection_loop, options)) { - goto cleanup_listener; - } - - return &server_connection_args->listener; - -cleanup_listener: - - ; // This line just used to avoid expression error after the label - - SETUP_SOCKET_SHUTDOWN_CALLBACKS( - bootstrap_options->bootstrap->allocator, - &server_connection_args->listener, - socket_shutdown_release_server_connection_args, - s_socket_shutdown_complete_release_server_connection_fn, - server_connection_args) - - aws_socket_clean_up(&server_connection_args->listener); - return NULL; - -cleanup_server_connection_args: - s_server_connection_args_release(server_connection_args); - - return NULL; -} - /* Called when a listener connection attempt task completes. */ static void s_on_listener_connection_established(struct aws_socket *socket, int error_code, void *user_data) { @@ -1873,13 +1738,16 @@ static void s_on_listener_connection_established(struct aws_socket *socket, int return; } -struct aws_socket *aws_server_bootstrap_new_socket_listener_async( - const struct aws_server_socket_channel_bootstrap_options *bootstrap_options) { +struct aws_socket *s_server_bootstrap_new_socket_listener( + const struct aws_server_socket_channel_bootstrap_options *bootstrap_options, + bool async_setup) { AWS_PRECONDITION(bootstrap_options); AWS_PRECONDITION(bootstrap_options->bootstrap); AWS_PRECONDITION(bootstrap_options->incoming_callback); AWS_PRECONDITION(bootstrap_options->shutdown_callback); - AWS_PRECONDITION(bootstrap_options->setup_callback); + if (async_setup) { + AWS_PRECONDITION(bootstrap_options->setup_callback); + } struct server_connection_args *server_connection_args = aws_mem_calloc(bootstrap_options->bootstrap->allocator, 1, sizeof(struct server_connection_args)); @@ -1977,18 +1845,26 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener_async( goto cleanup_listener; } - // Acquire for listener establish callbacks, should be released in `s_on_listener_connection_established` - s_server_connection_args_acquire(server_connection_args); - struct aws_socket_listener_options options = { .on_accept_result = s_on_server_connection_result, .on_accept_result_user_data = server_connection_args, - .on_accept_start_result = s_on_listener_connection_established, - .on_accept_start_user_data = server_connection_args, + .on_accept_start = NULL, + .on_accept_start_user_data = NULL, }; + if (async_setup) { + // If we use an async socket, acquire the connection args for listener establish callbacks, if + // aws_socket_start_accept succeed, the args should be released in `s_on_listener_connection_established` + s_server_connection_args_acquire(server_connection_args); + options.on_accept_start = s_on_listener_connection_established; + options.on_accept_start_user_data = server_connection_args; + } + if (aws_socket_start_accept(&server_connection_args->listener, connection_loop, options)) { - s_server_connection_args_release(server_connection_args); + if (async_setup) { + // release the args we acquired above + s_server_connection_args_release(server_connection_args); + } goto cleanup_listener; } @@ -2014,6 +1890,11 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener_async( return NULL; } +struct aws_socket *aws_server_bootstrap_new_socket_listener( + const struct aws_server_socket_channel_bootstrap_options *bootstrap_options) { + return s_server_bootstrap_new_socket_listener(bootstrap_options, bootstrap_options->setup_callback); +} + void aws_server_bootstrap_destroy_socket_listener(struct aws_server_bootstrap *bootstrap, struct aws_socket *listener) { struct server_connection_args *server_connection_args = AWS_CONTAINER_OF(listener, struct server_connection_args, listener); diff --git a/source/darwin/nw_socket.c b/source/darwin/nw_socket.c index a01c05a1b..d59e42cd3 100644 --- a/source/darwin/nw_socket.c +++ b/source/darwin/nw_socket.c @@ -1565,7 +1565,6 @@ static int s_socket_listen_fn(struct aws_socket *socket, int backlog_size) { struct listener_state_changed_args { struct aws_task task; struct aws_allocator *allocator; - struct aws_socket *socket; struct nw_socket *nw_socket; nw_listener_state_t state; int error; @@ -1588,10 +1587,9 @@ static void s_process_listener_state_changed_task(struct aws_task *task, void *a (void *)nw_socket, (void *)nw_listener); - /* Ideally we should not have a canceled task here, as nw_socket keeps a reference to event loop, therefore the - * event loop should never be destroyed before the nw_socket get destroyed. If we manually cancel the task, we - * should make sure we carefully handled the state change eventually, as the socket relies on this task to release - * and cleanup. + /* Ideally we should not have a task with AWS_TASK_STATUS_CANCELED here, as the event loop should never be destroyed + * before the nw_socket get destroyed. If we manually cancel the task, we should make sure we carefully handled the + * state change eventually, as the socket relies on this task to release and cleanup. */ if (status != AWS_TASK_STATUS_CANCELED) { @@ -1612,10 +1610,10 @@ static void s_process_listener_state_changed_task(struct aws_task *task, void *a crt_error_code); s_lock_base_socket(nw_socket); + struct aws_socket *aws_socket = nw_socket->base_socket_synced_data.base_socket; s_lock_socket_synced_data(nw_socket); - s_set_socket_state(nw_socket, listener_state_changed_args->socket, ERROR); + s_set_socket_state(nw_socket, aws_socket, ERROR); s_unlock_socket_synced_data(nw_socket); - struct aws_socket *aws_socket = nw_socket->base_socket_synced_data.base_socket; if (nw_socket->on_accept_started_fn) { nw_socket->on_accept_started_fn( aws_socket, crt_error_code, nw_socket->listen_accept_started_user_data); @@ -1646,9 +1644,12 @@ static void s_process_listener_state_changed_task(struct aws_task *task, void *a case nw_listener_state_cancelled: { AWS_LOGF_DEBUG( AWS_LS_IO_SOCKET, "id=%p handle=%p: listener cancelled.", (void *)nw_socket, (void *)nw_listener); + s_lock_base_socket(nw_socket); + struct aws_socket *aws_socket = nw_socket->base_socket_synced_data.base_socket; s_lock_socket_synced_data(nw_socket); - s_set_socket_state(nw_socket, listener_state_changed_args->socket, CLOSED); + s_set_socket_state(nw_socket, aws_socket, CLOSED); s_unlock_socket_synced_data(nw_socket); + s_unlock_base_socket(nw_socket); s_socket_release_internal_ref(nw_socket); } break; default: @@ -1679,13 +1680,10 @@ static void s_handle_listener_state_changed_fn( nw_error_code, crt_error_code); - s_lock_base_socket(nw_socket); - struct aws_socket *aws_socket = nw_socket->base_socket_synced_data.base_socket; - if (aws_socket && s_validate_event_loop(nw_socket->event_loop)) { + if (s_validate_event_loop(nw_socket->event_loop)) { struct listener_state_changed_args *args = aws_mem_calloc(nw_socket->allocator, 1, sizeof(struct listener_state_changed_args)); - args->socket = aws_socket; args->nw_socket = nw_socket; args->allocator = nw_socket->allocator; args->error = crt_error_code; @@ -1694,16 +1692,9 @@ static void s_handle_listener_state_changed_fn( s_socket_acquire_internal_ref(nw_socket); aws_task_init(&args->task, s_process_listener_state_changed_task, args, "ListenerStateChangedTask"); aws_event_loop_schedule_task_now(nw_socket->event_loop, &args->task); - } else if (state == nw_listener_state_cancelled) { - // If socket is already destroyed and the listener is canceled, directly closed the internal socket. - s_lock_socket_synced_data(nw_socket); - s_set_socket_state(nw_socket, aws_socket, CLOSED); - s_unlock_socket_synced_data(nw_socket); - - s_socket_release_internal_ref(nw_socket); + } else { + AWS_FATAL_ASSERT(true && "The nw_socket should be always attached to a validate event loop."); } - - s_unlock_base_socket(nw_socket); } static int s_socket_start_accept_fn( @@ -1740,7 +1731,7 @@ static int s_socket_start_accept_fn( socket->accept_result_fn = options.on_accept_result; socket->connect_accept_user_data = options.on_accept_result_user_data; - nw_socket->on_accept_started_fn = options.on_accept_start_result; + nw_socket->on_accept_started_fn = options.on_accept_start; nw_socket->listen_accept_started_user_data = options.on_accept_start_user_data; s_set_event_loop(socket, accept_loop); diff --git a/source/posix/socket.c b/source/posix/socket.c index 9425bbf3c..6074578e6 100644 --- a/source/posix/socket.c +++ b/source/posix/socket.c @@ -1135,6 +1135,49 @@ static void s_socket_accept_event( socket->io_handle.data.fd); } +struct on_start_accept_result_args { + struct aws_task task; + int error; + struct aws_allocator *allocator; + struct aws_socket *socket; + aws_socket_on_accept_started_fn *on_accept_start; + void *on_accept_start_user_data; +}; + +static void s_process_invoke_on_accept_start(struct aws_task *task, void *args, enum aws_task_status status) { + (void)task; + struct on_start_accept_result_args *on_accept_args = args; + if (status == AWS_TASK_STATUS_RUN_READY) { + struct aws_socket *socket = on_accept_args->socket; + + if (on_accept_args->on_accept_start) { + // socket should not be cleaned up until on_accept_result callback is invoked. + AWS_FATAL_ASSERT(socket); + on_accept_args->on_accept_start(socket, on_accept_args->error, on_accept_args->on_accept_start_user_data); + } + } + aws_mem_release(on_accept_args->allocator, args); +} + +static void s_invoke_on_accept_start( + struct aws_allocator *allocator, + struct aws_event_loop *loop, + struct aws_socket *socket, + int error, + aws_socket_on_accept_started_fn *on_accept_start, + void *on_accept_start_user_data) { + struct on_start_accept_result_args *args = aws_mem_calloc(allocator, 1, sizeof(struct on_start_accept_result_args)); + + args->allocator = allocator; + args->socket = socket; + args->error = error; + args->on_accept_start = on_accept_start; + args->on_accept_start_user_data = on_accept_start_user_data; + + aws_task_init(&args->task, s_process_invoke_on_accept_start, args, "SocketOnAcceptStartResultTask"); + aws_event_loop_schedule_task_now(loop, &args->task); +} + static int s_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, @@ -1142,14 +1185,6 @@ static int s_socket_start_accept( AWS_ASSERT(options.on_accept_result); AWS_ASSERT(accept_loop); - if (options.on_accept_start_result || options.on_accept_start_user_data) { - AWS_LOGF_DEBUG( - AWS_LS_IO_SOCKET, - "id=%p handle=%p: the posix socket does not support on_accept_start_result callback. Ignore the options.", - (void *)socket, - (void *)socket->io_handle.data.handle); - } - if (socket->event_loop) { AWS_LOGF_ERROR( AWS_LS_IO_SOCKET, @@ -1191,6 +1226,13 @@ static int s_socket_start_accept( return AWS_OP_ERR; } + s_invoke_on_accept_start( + socket->allocator, + accept_loop, + socket, + AWS_OP_SUCCESS, + options.on_accept_start, + options.on_accept_start_user_data); return AWS_OP_SUCCESS; } diff --git a/source/windows/iocp/socket.c b/source/windows/iocp/socket.c index 279e01792..af41e2cfb 100644 --- a/source/windows/iocp/socket.c +++ b/source/windows/iocp/socket.c @@ -617,13 +617,6 @@ static int s_socket_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, struct aws_socket_listener_options options) { - if (options.on_accept_start_result || options.on_accept_start_user_data) { - AWS_LOGF_DEBUG( - AWS_LS_IO_SOCKET, - "id=%p handle=%p: the iocp socket does not support on_accept_start_result callback. Ignore the options.", - (void *)socket, - (void *)socket->io_handle.data.handle); - } struct iocp_socket *socket_impl = socket->impl; return socket_impl->winsock_vtable->start_accept(socket, accept_loop, options); } @@ -2049,6 +2042,49 @@ static void s_tcp_accept_event( } } +struct on_start_accept_result_args { + struct aws_task task; + int error; + struct aws_allocator *allocator; + struct aws_socket *socket; + aws_socket_on_accept_started_fn *on_accept_start; + void *on_accept_start_user_data; +}; + +static void s_process_invoke_on_accept_start(struct aws_task *task, void *args, enum aws_task_status status) { + (void)task; + struct on_start_accept_result_args *on_accept_args = args; + if (status == AWS_TASK_STATUS_RUN_READY) { + struct aws_socket *socket = on_accept_args->socket; + + if (on_accept_args->on_accept_start) { + // socket should not be cleaned up until on_accept_result callback is invoked. + AWS_FATAL_ASSERT(socket); + on_accept_args->on_accept_start(socket, on_accept_args->error, on_accept_args->on_accept_start_user_data); + } + } + aws_mem_release(on_accept_args->allocator, args); +} + +static void s_invoke_on_accept_start( + struct aws_allocator *allocator, + struct aws_event_loop *loop, + struct aws_socket *socket, + int error, + aws_socket_on_accept_started_fn *on_accept_start, + void *on_accept_start_user_data) { + struct on_start_accept_result_args *args = aws_mem_calloc(allocator, 1, sizeof(struct on_start_accept_result_args)); + + args->allocator = allocator; + args->socket = socket; + args->error = error; + args->on_accept_start = on_accept_start; + args->on_accept_start_user_data = on_accept_start_user_data; + + aws_task_init(&args->task, s_process_invoke_on_accept_start, args, "SocketOnAcceptStartResultTask"); + aws_event_loop_schedule_task_now(loop, &args->task); +} + static int s_tcp_start_accept( struct aws_socket *socket, struct aws_event_loop *accept_loop, @@ -2096,6 +2132,13 @@ static int s_tcp_start_accept( int err = s_socket_setup_accept(socket, el_to_use); if (!err || aws_last_error() == AWS_IO_READ_WOULD_BLOCK) { + s_invoke_on_accept_start( + socket->allocator, + accept_loop, + socket, + AWS_OP_SUCCESS, + options.on_accept_start, + options.on_accept_start_user_data); return AWS_OP_SUCCESS; } @@ -2296,6 +2339,14 @@ static int s_local_start_accept( } } + s_invoke_on_accept_start( + socket->allocator, + accept_loop, + socket, + AWS_OP_SUCCESS, + options.on_accept_start, + options.on_accept_start_user_data); + return AWS_OP_SUCCESS; } diff --git a/tests/socket_handler_test.c b/tests/socket_handler_test.c index 96a6ccb8f..bcaeaa54a 100644 --- a/tests/socket_handler_test.c +++ b/tests/socket_handler_test.c @@ -390,19 +390,14 @@ static int s_local_server_tester_init( .user_data = args, }; - if (tester->socket_options.impl_type == AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK || - (tester->socket_options.impl_type == AWS_SOCKET_IMPL_PLATFORM_DEFAULT && - aws_socket_get_default_impl_type() == AWS_SOCKET_IMPL_APPLE_NETWORK_FRAMEWORK)) { - tester->listener = aws_server_bootstrap_new_socket_listener_async(&bootstrap_options); - ASSERT_SUCCESS(aws_mutex_lock(args->mutex)); - /* wait for listener to connected */ - ASSERT_SUCCESS(aws_condition_variable_wait_pred( - args->condition_variable, args->mutex, s_listener_connected_predicate, args)); - ASSERT_TRUE(args->error_code == AWS_OP_SUCCESS); - ASSERT_SUCCESS(aws_mutex_unlock(args->mutex)); - } else { - tester->listener = aws_server_bootstrap_new_socket_listener(&bootstrap_options); - } + tester->listener = aws_server_bootstrap_new_socket_listener(&bootstrap_options); + ASSERT_SUCCESS(aws_mutex_lock(args->mutex)); + /* wait for listener to connected */ + ASSERT_SUCCESS( + aws_condition_variable_wait_pred(args->condition_variable, args->mutex, s_listener_connected_predicate, args)); + ASSERT_TRUE(args->error_code == AWS_OP_SUCCESS); + ASSERT_SUCCESS(aws_mutex_unlock(args->mutex)); + ASSERT_NOT_NULL(tester->listener); /* find out which port the socket is bound to */ diff --git a/tests/socket_test.c b/tests/socket_test.c index 7caa975bc..50175396d 100644 --- a/tests/socket_test.c +++ b/tests/socket_test.c @@ -1413,6 +1413,7 @@ struct nw_socket_bind_args { struct aws_socket *listener; struct aws_mutex *mutex; struct aws_condition_variable *condition_variable; + bool start_listening; bool incoming_invoked; bool error_invoked; bool shutdown_complete; @@ -1433,6 +1434,12 @@ static bool s_bind_args_shutdown_completed_predicate(void *arg) { return bind_args->shutdown_complete; } +static bool s_bind_args_start_listening_predicate(void *arg) { + struct nw_socket_bind_args *bind_args = arg; + + return bind_args->start_listening; +} + static void s_local_listener_incoming_destroy_listener_bind( struct aws_socket *socket, int error_code, @@ -1454,6 +1461,20 @@ static void s_local_listener_incoming_destroy_listener_bind( aws_mutex_unlock(listener_args->mutex); } +static void s_local_listener_start_accept(struct aws_socket *socket, int error_code, void *user_data) { + (void)socket; + struct nw_socket_bind_args *listener_args = (struct nw_socket_bind_args *)user_data; + aws_mutex_lock(listener_args->mutex); + + if (!error_code) { + listener_args->start_listening = true; + } else { + listener_args->error_invoked = true; + } + aws_condition_variable_notify_one(listener_args->condition_variable); + aws_mutex_unlock(listener_args->mutex); +} + /* Ensure that binding to port 0 results in OS assigning a port */ static int s_test_bind_on_zero_port( struct aws_allocator *allocator, @@ -1509,15 +1530,23 @@ static int s_test_bind_on_zero_port( ASSERT_SUCCESS(aws_socket_listen(&incoming, 1024)); struct aws_socket_listener_options listener_options = { .on_accept_result = s_local_listener_incoming_destroy_listener_bind, - .on_accept_result_user_data = &listener_args}; + .on_accept_result_user_data = &listener_args, + .on_accept_start = s_local_listener_start_accept, + .on_accept_start_user_data = &listener_args}; ASSERT_SUCCESS(aws_socket_start_accept(&incoming, event_loop, listener_options)); // Apple Dispatch Queue requires a listener to be ready before it can get the assigned port. We wait until the // port is back. - while (local_address1.port == 0) { - ASSERT_SUCCESS(aws_socket_get_bound_address(&incoming, &local_address1)); - } + ASSERT_SUCCESS(aws_mutex_lock(listener_args.mutex)); + ASSERT_SUCCESS(aws_condition_variable_wait_pred( + listener_args.condition_variable, + listener_args.mutex, + s_bind_args_start_listening_predicate, + &listener_args)); + ASSERT_SUCCESS(aws_mutex_unlock(listener_args.mutex)); + + ASSERT_SUCCESS(aws_socket_get_bound_address(&incoming, &local_address1)); } else { if (sock_type != AWS_SOCKET_DGRAM) {