diff --git a/inc/lift/client.hpp b/inc/lift/client.hpp index 5878433..5f77e47 100644 --- a/inc/lift/client.hpp +++ b/inc/lift/client.hpp @@ -77,10 +77,10 @@ class client ~client(); - client(const client&) = delete; - client(client&&) = delete; + client(const client&) = delete; + client(client&&) = delete; auto operator=(const client&) noexcept -> client& = delete; - auto operator=(client&&) noexcept -> client& = delete; + auto operator=(client&&) noexcept -> client& = delete; /** * @return True if the event loop is currently running. @@ -224,6 +224,8 @@ class client uv_loop_t m_uv_loop{}; /// The async trigger for injecting new requests into the event loop. uv_async_t m_uv_async{}; + /// The async trigger to let uv_run() know its being shutdown + uv_async_t m_uv_async_shutdown_pipe{}; /// libcurl requires a single timer to drive internal timeouts/wake-ups. uv_timer_t m_uv_timer_curl{}; /// If set, the amount of time connections are allowed to connect, this can be @@ -477,6 +479,8 @@ class client */ friend auto on_uv_requests_accept_async(uv_async_t* handle) -> void; + friend auto on_uv_shutdown_async(uv_async_t* handle) -> void; + friend auto on_uv_timesup_callback(uv_timer_t* handle) -> void; }; diff --git a/src/client.cpp b/src/client.cpp index 647af0b..b3679fc 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -27,10 +27,10 @@ class curl_context ~curl_context() = default; - curl_context(const curl_context&) = delete; - curl_context(curl_context&&) = delete; + curl_context(const curl_context&) = delete; + curl_context(curl_context&&) = delete; auto operator=(const curl_context&) noexcept -> curl_context& = delete; - auto operator=(curl_context&&) noexcept -> curl_context& = delete; + auto operator=(curl_context&&) noexcept -> curl_context& = delete; auto init(uv_loop_t* uv_loop, curl_socket_t sock_fd) -> void { @@ -79,6 +79,8 @@ auto on_uv_curl_perform_callback(uv_poll_t* req, int status, int events) -> void auto on_uv_requests_accept_async(uv_async_t* handle) -> void; +auto on_uv_shutdown_async(uv_async_t* handle) -> void; + auto on_uv_timesup_callback(uv_timer_t* handle) -> void; client::client(options opts) @@ -100,6 +102,9 @@ client::client(options opts) uv_async_init(&m_uv_loop, &m_uv_async, on_uv_requests_accept_async); m_uv_async.data = this; + uv_async_init(&m_uv_loop, &m_uv_async_shutdown_pipe, on_uv_shutdown_async); + m_uv_async_shutdown_pipe.data = this; + uv_timer_init(&m_uv_loop, &m_uv_timer_curl); m_uv_timer_curl.data = this; @@ -133,23 +138,23 @@ client::~client() { m_is_stopping.exchange(true, std::memory_order_release); + // Block until all requests are completed. while (!empty()) { std::this_thread::sleep_for(1ms); } - uv_timer_stop(&m_uv_timer_curl); - uv_timer_stop(&m_uv_timer_timeout); - uv_close(uv_type_cast(&m_uv_timer_curl), uv_close_callback); - uv_close(uv_type_cast(&m_uv_timer_timeout), uv_close_callback); - uv_close(uv_type_cast(&m_uv_async), uv_close_callback); + // This breaks the main UV_RUN_DEFAULT loop. + uv_stop(&m_uv_loop); + // This tells the loop to cleanup all its resources. + uv_async_send(&m_uv_async_shutdown_pipe); - while (uv_loop_alive(&m_uv_loop)) + // Wait for the loop to finish cleaning up before closing up shop. + while (uv_loop_alive(&m_uv_loop) > 0) { std::this_thread::sleep_for(1ms); - uv_async_send(&m_uv_async); // fake a request to make sure the loop wakes up } - uv_stop(&m_uv_loop); + uv_loop_close(&m_uv_loop); m_background_thread.join(); @@ -212,7 +217,15 @@ auto client::run() -> void } m_is_running.exchange(true, std::memory_order_release); - uv_run(&m_uv_loop, UV_RUN_DEFAULT); + if (uv_run(&m_uv_loop, UV_RUN_DEFAULT) > 0) + { + // Run until all uv_handle_t objects are cleaned up. + while (uv_run(&m_uv_loop, UV_RUN_NOWAIT) > 0) + { + std::this_thread::sleep_for(1ms); + } + } + m_is_running.exchange(false, std::memory_order_release); if (m_on_thread_callback != nullptr) @@ -663,6 +676,18 @@ auto on_uv_requests_accept_async(uv_async_t* handle) -> void c->m_grabbed_requests.clear(); } +auto on_uv_shutdown_async(uv_async_t* handle) -> void +{ + auto* c = static_cast(handle->data); + + uv_timer_stop(&c->m_uv_timer_curl); + uv_timer_stop(&c->m_uv_timer_timeout); + uv_close(uv_type_cast(&c->m_uv_timer_curl), uv_close_callback); + uv_close(uv_type_cast(&c->m_uv_timer_timeout), uv_close_callback); + uv_close(uv_type_cast(&c->m_uv_async), uv_close_callback); + uv_close(uv_type_cast(&c->m_uv_async_shutdown_pipe), uv_close_callback); +} + auto on_uv_timesup_callback(uv_timer_t* handle) -> void { auto* c = static_cast(handle->data);