Skip to content

Commit

Permalink
lift::client destructor infinite loop with libuv >= 1.48
Browse files Browse the repository at this point in the history
* libuv >= 1.48 at least has changed how uv loops get shutdown
* The loop seems to need to release its own resources
* Introduced a new async channel for shutdown for the loop to cleanup
  its own uv_handle_t objects.

Closes #149
  • Loading branch information
jbaldwin committed Jun 14, 2024
1 parent 6c2fd07 commit 8160356
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 13 deletions.
10 changes: 7 additions & 3 deletions inc/lift/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
};

Expand Down
34 changes: 24 additions & 10 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand All @@ -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;

Expand Down Expand Up @@ -133,23 +138,20 @@ 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<uv_handle_t>(&m_uv_timer_curl), uv_close_callback);
uv_close(uv_type_cast<uv_handle_t>(&m_uv_timer_timeout), uv_close_callback);
uv_close(uv_type_cast<uv_handle_t>(&m_uv_async), uv_close_callback);
uv_async_send(&m_uv_async_shutdown_pipe);
uv_stop(&m_uv_loop);

while (uv_loop_alive(&m_uv_loop))
{
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();
Expand Down Expand Up @@ -663,6 +665,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<client*>(handle->data);

uv_timer_stop(&c->m_uv_timer_curl);
uv_timer_stop(&c->m_uv_timer_timeout);
uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_timer_curl), uv_close_callback);
uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_timer_timeout), uv_close_callback);
uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_async), uv_close_callback);
uv_close(uv_type_cast<uv_handle_t>(&c->m_uv_async_shutdown_pipe), uv_close_callback);
}

auto on_uv_timesup_callback(uv_timer_t* handle) -> void
{
auto* c = static_cast<client*>(handle->data);
Expand Down

0 comments on commit 8160356

Please sign in to comment.