From 1b857a7017fbde3f2c05ccef1cd9d16ba020895a Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 1 Apr 2024 12:49:19 -0700 Subject: [PATCH 1/6] fix: do not block identify on SSE client shutdown completion --- libs/client-sdk/src/client_impl.cpp | 13 +++++-------- libs/server-sent-events/src/client.cpp | 21 +++++++++++++++------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/libs/client-sdk/src/client_impl.cpp b/libs/client-sdk/src/client_impl.cpp index 79ab4d63e..a38c2ac08 100644 --- a/libs/client-sdk/src/client_impl.cpp +++ b/libs/client-sdk/src/client_impl.cpp @@ -149,14 +149,11 @@ std::future ClientImpl::IdentifyAsync(Context context) { } void ClientImpl::RestartDataSource() { - auto start_op = [this]() { - data_source_ = data_source_factory_(); - data_source_->Start(); - }; - if (!data_source_) { - return start_op(); + if (data_source_) { + data_source_->ShutdownAsync(nullptr); } - data_source_->ShutdownAsync(start_op); + data_source_ = data_source_factory_(); + data_source_->Start(); } std::future ClientImpl::StartAsyncInternal( @@ -170,7 +167,7 @@ std::future ClientImpl::StartAsyncInternal( if (auto const state = status.State(); IsInitialized(state)) { init_promise->set_value(result(status.State())); return true; /* delete this change listener since the desired - state was reached */ + state was reached */ } return false; /* keep the change listener */ }); diff --git a/libs/server-sent-events/src/client.cpp b/libs/server-sent-events/src/client.cpp index 142768242..8bbf0d16a 100644 --- a/libs/server-sent-events/src/client.cpp +++ b/libs/server-sent-events/src/client.cpp @@ -44,6 +44,13 @@ auto const kDefaultInitialReconnectDelay = std::chrono::seconds(1); // Maximum duration between backoff attempts. auto const kDefaultMaxBackoffDelay = std::chrono::seconds(30); +// When we shut down the SSL stream using async_shutdown operation, it +// appears that the completion handler isn't invoked for about 5 minutes. Either +// we or the server are misbehaving here. In any case, there is no need to wait +// 5 minutes, we should give a couple seconds to receive a valid response or +// else stop waiting. +auto const kShutdownTimeout = std::chrono::seconds(5); + static boost::optional ToOptRef( std::optional& maybe_val) { if (maybe_val) { @@ -341,15 +348,17 @@ class FoxyClient : public Client, void do_shutdown(std::function completion) { shutting_down_ = true; backoff_timer_.cancel(); + session_->opts.timeout = kShutdownTimeout; + if (session_->stream.is_ssl()) { + session_->stream.ssl().next_layer().cancel(); + } session_->async_shutdown(beast::bind_front_handler( - &FoxyClient::on_shutdown, std::move(completion))); + &FoxyClient::on_shutdown, shared_from_this(), + std::move(completion))); } - static void on_shutdown(std::function completion, - boost::system::error_code ec) { - // Because do_shutdown doesn't use shared_from_this() when initiating - // the async_shutdown op, the client may already be destroyed - hence - // this static method. + void on_shutdown(std::function completion, + boost::system::error_code ec) { boost::ignore_unused(ec); if (completion) { completion(); From 17e83baf6cc0d469d1c1175da1dfba4b65209ed1 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Mon, 1 Apr 2024 16:48:31 -0700 Subject: [PATCH 2/6] debug commit; revert me --- examples/hello-cpp-client/main.cpp | 13 +++ libs/client-sdk/src/client_impl.cpp | 9 ++- libs/server-sent-events/src/client.cpp | 16 ++-- .../client_session/async_shutdown.impl.hpp | 80 +++++++++++-------- 4 files changed, 76 insertions(+), 42 deletions(-) diff --git a/examples/hello-cpp-client/main.cpp b/examples/hello-cpp-client/main.cpp index 3ac7c99ea..3caa04ee5 100644 --- a/examples/hello-cpp-client/main.cpp +++ b/examples/hello-cpp-client/main.cpp @@ -62,6 +62,19 @@ int main() { std::cout << "*** Feature flag '" << FEATURE_FLAG_KEY << "' is " << (flag_value ? "true" : "false") << " for this user\n\n"; + for (int i = 0; i < 5; i++) { + auto user_key = "example-user-key-" + std::to_string(i); + auto context2 = + ContextBuilder().Kind("user", user_key).Name("Bob").Build(); + + std::cout << "identifying " << user_key << std::endl; + auto fut = client.IdentifyAsync(context2); + fut.wait(); + std::cout << "identified " << user_key << "!" << std::endl; + + std::this_thread::sleep_for(std::chrono::seconds(5)); + } + return 0; } diff --git a/libs/client-sdk/src/client_impl.cpp b/libs/client-sdk/src/client_impl.cpp index a38c2ac08..55f590b57 100644 --- a/libs/client-sdk/src/client_impl.cpp +++ b/libs/client-sdk/src/client_impl.cpp @@ -150,7 +150,14 @@ std::future ClientImpl::IdentifyAsync(Context context) { void ClientImpl::RestartDataSource() { if (data_source_) { - data_source_->ShutdownAsync(nullptr); + auto then = std::chrono::steady_clock::now(); + data_source_->ShutdownAsync([then]() { + std::cout << "Shutdown in " + << std::chrono::duration_cast( + std::chrono::steady_clock::now() - then) + .count() + << "ms" << std::endl; + }); } data_source_ = data_source_factory_(); data_source_->Start(); diff --git a/libs/server-sent-events/src/client.cpp b/libs/server-sent-events/src/client.cpp index 8bbf0d16a..d65fd2ba6 100644 --- a/libs/server-sent-events/src/client.cpp +++ b/libs/server-sent-events/src/client.cpp @@ -49,7 +49,7 @@ auto const kDefaultMaxBackoffDelay = std::chrono::seconds(30); // we or the server are misbehaving here. In any case, there is no need to wait // 5 minutes, we should give a couple seconds to receive a valid response or // else stop waiting. -auto const kShutdownTimeout = std::chrono::seconds(5); +auto const kShutdownTimeout = std::chrono::seconds(10); static boost::optional ToOptRef( std::optional& maybe_val) { @@ -292,8 +292,10 @@ class FoxyClient : public Client, // if we're shutting down, so shutting_down_ is needed to // disambiguate. if (shutting_down_) { - // End the chain of async operations. - return; + session_->opts.timeout = kShutdownTimeout; + + return session_->async_shutdown(beast::bind_front_handler( + &FoxyClient::on_shutdown, shared_from_this(), []() {})); } errors_(Error::ReadTimeout); return async_backoff( @@ -337,6 +339,7 @@ class FoxyClient : public Client, } void async_shutdown(std::function completion) override { + std::cout << "shutdown requested, posting..\n"; // Get on the session's executor, otherwise the code in the completion // handler could race. boost::asio::post(session_->get_executor(), @@ -346,15 +349,14 @@ class FoxyClient : public Client, } void do_shutdown(std::function completion) { + std::cout << "shutdown request executing..\n"; shutting_down_ = true; backoff_timer_.cancel(); - session_->opts.timeout = kShutdownTimeout; if (session_->stream.is_ssl()) { session_->stream.ssl().next_layer().cancel(); + } else { + session_->stream.plain().cancel(); } - session_->async_shutdown(beast::bind_front_handler( - &FoxyClient::on_shutdown, shared_from_this(), - std::move(completion))); } void on_shutdown(std::function completion, diff --git a/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp b/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp index e76119928..6eba9fb7c 100644 --- a/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp +++ b/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp @@ -1,8 +1,9 @@ // -// Copyright (c) 2018-2019 Christian Mazakas (christian dot mazakas at gmail dot com) +// Copyright (c) 2018-2019 Christian Mazakas (christian dot mazakas at gmail dot +// com) // -// Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Official repository: https://github.com/LeonineKing1199/foxy // @@ -11,39 +12,50 @@ #define FOXY_IMPL_CLIENT_SESSION_ASYNC_SHUTDOWN_IMPL_HPP_ #include - -namespace launchdarkly::foxy -{ +#include +namespace launchdarkly::foxy { template template -auto -basic_client_session::async_shutdown(ShutdownHandler&& handler) & -> - typename boost::asio::async_result, - void(boost::system::error_code)>::return_type -{ - return ::launchdarkly::foxy::detail::async_timer( - [self = this, coro = boost::asio::coroutine()](auto& cb, boost::system::error_code ec = {}, - std::size_t bytes_transferrred = 0) mutable { - auto& s = *self; - - BOOST_ASIO_CORO_REENTER(coro) - { - if (s.stream.is_ssl()) { - BOOST_ASIO_CORO_YIELD s.stream.ssl().async_shutdown(std::move(cb)); - if (ec == boost::asio::ssl::error::stream_truncated) { ec = {}; } - if (ec) { goto upcall; } - } - - s.stream.plain().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); - s.stream.plain().close(ec); - - upcall: - return cb.complete(ec); - } - }, - *this, std::forward(handler)); +auto basic_client_session::async_shutdown( + ShutdownHandler&& handler) & -> + typename boost::asio::async_result< + std::decay_t, + void(boost::system::error_code)>::return_type { + return ::launchdarkly::foxy::detail::async_timer( + [self = this, coro = boost::asio::coroutine()]( + auto& cb, boost::system::error_code ec = {}, + std::size_t bytes_transferrred = 0) mutable { + auto& s = *self; + + BOOST_ASIO_CORO_REENTER(coro) { + if (s.stream.is_ssl()) { + std::cout << "-> ssl().async_shutdown()\n"; + BOOST_ASIO_CORO_YIELD s.stream.ssl().async_shutdown( + std::move(cb)); + std::cout << "<- ssl().async_shutdown: " << ec.message() + << '\n'; + if (ec == boost::asio::ssl::error::stream_truncated) { + ec = {}; + } + if (ec) { + goto upcall; + } + } + + std::cout << "plain socket shutdown\n"; + s.stream.plain().shutdown( + boost::asio::ip::tcp::socket::shutdown_both, ec); + s.stream.plain().close(ec); + + upcall: + std::cout << "invoke completion handler\n"; + return cb.complete(ec); + } + }, + *this, std::forward(handler)); } -} // namespace launchdarkly::foxy +} // namespace launchdarkly::foxy -#endif // FOXY_IMPL_CLIENT_SESSION_ASYNC_SHUTDOWN_IMPL_HPP_ +#endif // FOXY_IMPL_CLIENT_SESSION_ASYNC_SHUTDOWN_IMPL_HPP_ From e8d3a7154d8e4271f2eb584672ed16ae255674eb Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Tue, 2 Apr 2024 10:54:03 -0700 Subject: [PATCH 3/6] testing out a hack --- libs/server-sent-events/src/client.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/server-sent-events/src/client.cpp b/libs/server-sent-events/src/client.cpp index d65fd2ba6..44ebb3bca 100644 --- a/libs/server-sent-events/src/client.cpp +++ b/libs/server-sent-events/src/client.cpp @@ -292,10 +292,7 @@ class FoxyClient : public Client, // if we're shutting down, so shutting_down_ is needed to // disambiguate. if (shutting_down_) { - session_->opts.timeout = kShutdownTimeout; - - return session_->async_shutdown(beast::bind_front_handler( - &FoxyClient::on_shutdown, shared_from_this(), []() {})); + return; } errors_(Error::ReadTimeout); return async_backoff( @@ -348,6 +345,7 @@ class FoxyClient : public Client, std::move(completion))); } + void on_shutdown_write() { std::cout << "shutdown write completed\n"; } void do_shutdown(std::function completion) { std::cout << "shutdown request executing..\n"; shutting_down_ = true; @@ -357,6 +355,15 @@ class FoxyClient : public Client, } else { session_->stream.plain().cancel(); } + session_->opts.timeout = kShutdownTimeout; + session_->async_shutdown(beast::bind_front_handler( + &FoxyClient::on_shutdown, shared_from_this(), + std::move(completion))); + // Run async_write with a single null byte: + session_->stream.async_write_some( + net::buffer("\0", 1), + beast::bind_front_handler(&FoxyClient::on_shutdown_write, + shared_from_this())); } void on_shutdown(std::function completion, From 2dbdd2926c8ac7d972f099a50c3402c4803c4179 Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 3 Apr 2024 12:39:22 -0700 Subject: [PATCH 4/6] shutdown plain socket --- .../data_sources/streaming_data_source.cpp | 1 + libs/server-sent-events/src/client.cpp | 27 ++----- .../client_session/async_shutdown.impl.hpp | 80 ++++++++----------- 3 files changed, 42 insertions(+), 66 deletions(-) diff --git a/libs/client-sdk/src/data_sources/streaming_data_source.cpp b/libs/client-sdk/src/data_sources/streaming_data_source.cpp index fd8c4e29a..3f9902d66 100644 --- a/libs/client-sdk/src/data_sources/streaming_data_source.cpp +++ b/libs/client-sdk/src/data_sources/streaming_data_source.cpp @@ -12,6 +12,7 @@ #include #include +#include #include namespace launchdarkly::client_side::data_sources { diff --git a/libs/server-sent-events/src/client.cpp b/libs/server-sent-events/src/client.cpp index 44ebb3bca..5bd333905 100644 --- a/libs/server-sent-events/src/client.cpp +++ b/libs/server-sent-events/src/client.cpp @@ -44,13 +44,6 @@ auto const kDefaultInitialReconnectDelay = std::chrono::seconds(1); // Maximum duration between backoff attempts. auto const kDefaultMaxBackoffDelay = std::chrono::seconds(30); -// When we shut down the SSL stream using async_shutdown operation, it -// appears that the completion handler isn't invoked for about 5 minutes. Either -// we or the server are misbehaving here. In any case, there is no need to wait -// 5 minutes, we should give a couple seconds to receive a valid response or -// else stop waiting. -auto const kShutdownTimeout = std::chrono::seconds(10); - static boost::optional ToOptRef( std::optional& maybe_val) { if (maybe_val) { @@ -65,6 +58,7 @@ class FoxyClient : public Client, using cb = std::function; using body = launchdarkly::sse::detail::EventBody; using response = http::response; + int count; public: FoxyClient(boost::asio::any_io_executor executor, @@ -97,7 +91,8 @@ class FoxyClient : public Client, kDefaultMaxBackoffDelay), backoff_timer_(std::move(executor)), last_read_(std::nullopt), - shutting_down_(false) { + shutting_down_(false), + count(get_counter()) { create_session(); create_parser(); } @@ -292,6 +287,10 @@ class FoxyClient : public Client, // if we're shutting down, so shutting_down_ is needed to // disambiguate. if (shutting_down_) { + boost::system::error_code ec = {}; + session_->stream.plain().shutdown( + boost::asio::ip::tcp::socket::shutdown_both, ec); + session_->stream.plain().close(ec); return; } errors_(Error::ReadTimeout); @@ -336,7 +335,6 @@ class FoxyClient : public Client, } void async_shutdown(std::function completion) override { - std::cout << "shutdown requested, posting..\n"; // Get on the session's executor, otherwise the code in the completion // handler could race. boost::asio::post(session_->get_executor(), @@ -345,9 +343,7 @@ class FoxyClient : public Client, std::move(completion))); } - void on_shutdown_write() { std::cout << "shutdown write completed\n"; } void do_shutdown(std::function completion) { - std::cout << "shutdown request executing..\n"; shutting_down_ = true; backoff_timer_.cancel(); if (session_->stream.is_ssl()) { @@ -355,15 +351,6 @@ class FoxyClient : public Client, } else { session_->stream.plain().cancel(); } - session_->opts.timeout = kShutdownTimeout; - session_->async_shutdown(beast::bind_front_handler( - &FoxyClient::on_shutdown, shared_from_this(), - std::move(completion))); - // Run async_write with a single null byte: - session_->stream.async_write_some( - net::buffer("\0", 1), - beast::bind_front_handler(&FoxyClient::on_shutdown_write, - shared_from_this())); } void on_shutdown(std::function completion, diff --git a/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp b/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp index 6eba9fb7c..e76119928 100644 --- a/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp +++ b/vendor/foxy/include/foxy/impl/client_session/async_shutdown.impl.hpp @@ -1,9 +1,8 @@ // -// Copyright (c) 2018-2019 Christian Mazakas (christian dot mazakas at gmail dot -// com) +// Copyright (c) 2018-2019 Christian Mazakas (christian dot mazakas at gmail dot com) // -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) // // Official repository: https://github.com/LeonineKing1199/foxy // @@ -12,50 +11,39 @@ #define FOXY_IMPL_CLIENT_SESSION_ASYNC_SHUTDOWN_IMPL_HPP_ #include -#include -namespace launchdarkly::foxy { + +namespace launchdarkly::foxy +{ template template -auto basic_client_session::async_shutdown( - ShutdownHandler&& handler) & -> - typename boost::asio::async_result< - std::decay_t, - void(boost::system::error_code)>::return_type { - return ::launchdarkly::foxy::detail::async_timer( - [self = this, coro = boost::asio::coroutine()]( - auto& cb, boost::system::error_code ec = {}, - std::size_t bytes_transferrred = 0) mutable { - auto& s = *self; - - BOOST_ASIO_CORO_REENTER(coro) { - if (s.stream.is_ssl()) { - std::cout << "-> ssl().async_shutdown()\n"; - BOOST_ASIO_CORO_YIELD s.stream.ssl().async_shutdown( - std::move(cb)); - std::cout << "<- ssl().async_shutdown: " << ec.message() - << '\n'; - if (ec == boost::asio::ssl::error::stream_truncated) { - ec = {}; - } - if (ec) { - goto upcall; - } - } - - std::cout << "plain socket shutdown\n"; - s.stream.plain().shutdown( - boost::asio::ip::tcp::socket::shutdown_both, ec); - s.stream.plain().close(ec); - - upcall: - std::cout << "invoke completion handler\n"; - return cb.complete(ec); - } - }, - *this, std::forward(handler)); +auto +basic_client_session::async_shutdown(ShutdownHandler&& handler) & -> + typename boost::asio::async_result, + void(boost::system::error_code)>::return_type +{ + return ::launchdarkly::foxy::detail::async_timer( + [self = this, coro = boost::asio::coroutine()](auto& cb, boost::system::error_code ec = {}, + std::size_t bytes_transferrred = 0) mutable { + auto& s = *self; + + BOOST_ASIO_CORO_REENTER(coro) + { + if (s.stream.is_ssl()) { + BOOST_ASIO_CORO_YIELD s.stream.ssl().async_shutdown(std::move(cb)); + if (ec == boost::asio::ssl::error::stream_truncated) { ec = {}; } + if (ec) { goto upcall; } + } + + s.stream.plain().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + s.stream.plain().close(ec); + + upcall: + return cb.complete(ec); + } + }, + *this, std::forward(handler)); } -} // namespace launchdarkly::foxy +} // namespace launchdarkly::foxy -#endif // FOXY_IMPL_CLIENT_SESSION_ASYNC_SHUTDOWN_IMPL_HPP_ +#endif // FOXY_IMPL_CLIENT_SESSION_ASYNC_SHUTDOWN_IMPL_HPP_ From a1473e661ee4bc534a669696961d450fc489cace Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 3 Apr 2024 13:02:35 -0700 Subject: [PATCH 5/6] unclean shutdown works --- libs/client-sdk/src/client_impl.cpp | 20 +++++------ .../data_sources/streaming_data_source.cpp | 1 - libs/server-sent-events/src/client.cpp | 34 +++++++------------ 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/libs/client-sdk/src/client_impl.cpp b/libs/client-sdk/src/client_impl.cpp index 55f590b57..79ab4d63e 100644 --- a/libs/client-sdk/src/client_impl.cpp +++ b/libs/client-sdk/src/client_impl.cpp @@ -149,18 +149,14 @@ std::future ClientImpl::IdentifyAsync(Context context) { } void ClientImpl::RestartDataSource() { - if (data_source_) { - auto then = std::chrono::steady_clock::now(); - data_source_->ShutdownAsync([then]() { - std::cout << "Shutdown in " - << std::chrono::duration_cast( - std::chrono::steady_clock::now() - then) - .count() - << "ms" << std::endl; - }); + auto start_op = [this]() { + data_source_ = data_source_factory_(); + data_source_->Start(); + }; + if (!data_source_) { + return start_op(); } - data_source_ = data_source_factory_(); - data_source_->Start(); + data_source_->ShutdownAsync(start_op); } std::future ClientImpl::StartAsyncInternal( @@ -174,7 +170,7 @@ std::future ClientImpl::StartAsyncInternal( if (auto const state = status.State(); IsInitialized(state)) { init_promise->set_value(result(status.State())); return true; /* delete this change listener since the desired - state was reached */ + state was reached */ } return false; /* keep the change listener */ }); diff --git a/libs/client-sdk/src/data_sources/streaming_data_source.cpp b/libs/client-sdk/src/data_sources/streaming_data_source.cpp index 3f9902d66..fd8c4e29a 100644 --- a/libs/client-sdk/src/data_sources/streaming_data_source.cpp +++ b/libs/client-sdk/src/data_sources/streaming_data_source.cpp @@ -12,7 +12,6 @@ #include #include -#include #include namespace launchdarkly::client_side::data_sources { diff --git a/libs/server-sent-events/src/client.cpp b/libs/server-sent-events/src/client.cpp index 5bd333905..f5f730a65 100644 --- a/libs/server-sent-events/src/client.cpp +++ b/libs/server-sent-events/src/client.cpp @@ -58,7 +58,6 @@ class FoxyClient : public Client, using cb = std::function; using body = launchdarkly::sse::detail::EventBody; using response = http::response; - int count; public: FoxyClient(boost::asio::any_io_executor executor, @@ -91,8 +90,7 @@ class FoxyClient : public Client, kDefaultMaxBackoffDelay), backoff_timer_(std::move(executor)), last_read_(std::nullopt), - shutting_down_(false), - count(get_counter()) { + shutting_down_(false) { create_session(); create_parser(); } @@ -282,17 +280,10 @@ class FoxyClient : public Client, void on_read_body(boost::system::error_code ec, std::size_t amount) { boost::ignore_unused(amount); if (ec) { + if (shutting_down_) { + return; + } if (ec == boost::asio::error::operation_aborted) { - // operation_aborted can occur if the read timeout is reached or - // if we're shutting down, so shutting_down_ is needed to - // disambiguate. - if (shutting_down_) { - boost::system::error_code ec = {}; - session_->stream.plain().shutdown( - boost::asio::ip::tcp::socket::shutdown_both, ec); - session_->stream.plain().close(ec); - return; - } errors_(Error::ReadTimeout); return async_backoff( "aborting read of response body (timeout)"); @@ -344,18 +335,17 @@ class FoxyClient : public Client, } void do_shutdown(std::function completion) { + // Signal to the body reader operation that if it completes, + // it should return instead of starting another async read. shutting_down_ = true; + // If any backoff is taking place, cancel that as well. backoff_timer_.cancel(); - if (session_->stream.is_ssl()) { - session_->stream.ssl().next_layer().cancel(); - } else { - session_->stream.plain().cancel(); - } - } + + boost::system::error_code ec = {}; + session_->stream.plain().shutdown( + boost::asio::ip::tcp::socket::shutdown_both, ec); + session_->stream.plain().close(ec); - void on_shutdown(std::function completion, - boost::system::error_code ec) { - boost::ignore_unused(ec); if (completion) { completion(); } From dc087b9f83ccb84941b1a28e4cf9c91c7496f95d Mon Sep 17 00:00:00 2001 From: Casey Waldren Date: Wed, 3 Apr 2024 13:37:30 -0700 Subject: [PATCH 6/6] use simplest solution --- examples/hello-cpp-client/main.cpp | 13 ------------- libs/server-sent-events/src/client.cpp | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/examples/hello-cpp-client/main.cpp b/examples/hello-cpp-client/main.cpp index 3caa04ee5..3ac7c99ea 100644 --- a/examples/hello-cpp-client/main.cpp +++ b/examples/hello-cpp-client/main.cpp @@ -62,19 +62,6 @@ int main() { std::cout << "*** Feature flag '" << FEATURE_FLAG_KEY << "' is " << (flag_value ? "true" : "false") << " for this user\n\n"; - for (int i = 0; i < 5; i++) { - auto user_key = "example-user-key-" + std::to_string(i); - auto context2 = - ContextBuilder().Kind("user", user_key).Name("Bob").Build(); - - std::cout << "identifying " << user_key << std::endl; - auto fut = client.IdentifyAsync(context2); - fut.wait(); - std::cout << "identified " << user_key << "!" << std::endl; - - std::this_thread::sleep_for(std::chrono::seconds(5)); - } - return 0; } diff --git a/libs/server-sent-events/src/client.cpp b/libs/server-sent-events/src/client.cpp index f5f730a65..9d66c8c07 100644 --- a/libs/server-sent-events/src/client.cpp +++ b/libs/server-sent-events/src/client.cpp @@ -340,8 +340,28 @@ class FoxyClient : public Client, shutting_down_ = true; // If any backoff is taking place, cancel that as well. backoff_timer_.cancel(); - - boost::system::error_code ec = {}; + + // Cancels the outstanding read. + if (session_->stream.is_ssl()) { + session_->stream.ssl().next_layer().cancel(); + } else { + session_->stream.plain().cancel(); + } + + // Ideally we would call session_->async_shutdown() here to gracefully + // terminate the SSL session. For unknown reasons, this call appears to + // hang indefinitely and never complete until the SDK client is + // destroyed. + // + // A workaround is to set a timeout on the operation, say 1 second. This + // gives the opportunity to shutdown gracefully and then if that fails, + // we could close the socket directly. But that also doesn't seem to + // work: even with the timeout, the operation still doesn't complete. + // + // So the most robust solution appears to be closing the socket + // directly. This is not ideal because it doesn't send a close_notify to + // the server. + boost::system::error_code ec; session_->stream.plain().shutdown( boost::asio::ip::tcp::socket::shutdown_both, ec); session_->stream.plain().close(ec);