diff --git a/client/include/loon/client.h b/client/include/loon/client.h index b5073e0..3c1c309 100644 --- a/client/include/loon/client.h +++ b/client/include/loon/client.h @@ -149,6 +149,17 @@ class IClient */ virtual void wait_until_ready() = 0; + /** + * @brief Sets a callback for when the client is connected and ready. + * + * It is recommended to call this method before start(), + * to reliably detect client ready state. + * Do not call any client methods within the callback, + * as client locks are held when it is called, + * which would cause a deadlock. + */ + virtual void on_ready(std::function callback) = 0; + /** * @brief Sets a callback for when the client has unrecoverably failed. * @@ -455,6 +466,11 @@ class Client : public IClient return m_impl->wait_until_ready(timeout); } + inline void on_ready(std::function callback) override + { + return m_impl->on_ready(callback); + } + inline void on_failed(std::function callback) override { return m_impl->on_failed(callback); diff --git a/client/src/client.cpp b/client/src/client.cpp index 133e81d..ebb45c8 100644 --- a/client/src/client.cpp +++ b/client/src/client.cpp @@ -326,13 +326,18 @@ void ClientImpl::on_hello(Hello const& hello) } } - m_cv_connection_ready.notify_all(); - log(Info) << "ready" << var("base_url", m_hello->base_url()); - // The connection is idling if no content was registered yet. if (m_content.empty()) { set_idle(true); } + + log(Info) << "ready" << var("base_url", m_hello->base_url()); + + // Call the callback before notifying that the connection is ready. + if (m_ready_callback) { + m_ready_callback(); + } + m_cv_connection_ready.notify_all(); } bool ClientImpl::check_request_limit(decltype(m_content)::iterator it) diff --git a/client/src/client.h b/client/src/client.h index 93e8ca8..b346519 100644 --- a/client/src/client.h +++ b/client/src/client.h @@ -57,6 +57,12 @@ class ClientImpl : public IClient wait_until_ready(lock, timeout); } + inline void on_ready(std::function callback) override + { + std::lock_guard lock(m_mutex); + m_ready_callback = callback; + } + inline void on_failed(std::function callback) override { std::lock_guard lock(m_mutex); @@ -315,6 +321,7 @@ class ClientImpl : public IClient const ClientOptions m_options{}; std::deque m_no_content_request_history{}; + std::function m_ready_callback{}; std::function m_failed_callback{}; bool m_started{ false }; diff --git a/client/test/client_test.cpp b/client/test/client_test.cpp index 469a0df..00807ae 100644 --- a/client/test/client_test.cpp +++ b/client/test/client_test.cpp @@ -437,6 +437,15 @@ TEST(Client, FailsWhenMinCacheDurationIsSetAndServerDoesNotCacheResponses) EXPECT_THROW(client->wait_for_hello(), ClientNotConnectedException); } +TEST(Client, ReadyWhenClientIsStarted) +{ + auto client = create_client(false); + ExpectCalled callback; + client->on_ready(callback.get()); + client->start(); + EXPECT_NO_THROW(client->wait_until_ready()); +} + TEST(Client, FailsWhenMinCacheDurationIsSetButResponseIsNotCached) { uint32_t cache_duration = 10;