From 529e97aa816698ea64c0947c2c8a38873f17a048 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 13 Sep 2024 23:10:51 +0200 Subject: [PATCH 1/5] engine_impl now uses async_immediate --- include/boost/mysql/detail/engine_impl.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/boost/mysql/detail/engine_impl.hpp b/include/boost/mysql/detail/engine_impl.hpp index 67ee77f45..529638596 100644 --- a/include/boost/mysql/detail/engine_impl.hpp +++ b/include/boost/mysql/detail/engine_impl.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,7 @@ struct run_algo_op BOOST_MYSQL_YIELD( resume_point_, 1, - asio::post(stream_.get_executor(), std::move(self)) + asio::async_immediate(stream_.get_executor(), std::move(self)) ) } self.complete(stored_ec_); From bebf560fcf90ce050efbf180a22ee99d9402a05f Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 27 Sep 2024 18:18:33 +0200 Subject: [PATCH 2/5] engine_impl test --- .../include/test_common/network_result.hpp | 28 ++++++++- test/unit/test/detail/engine_impl.cpp | 58 ++++++++++++++++--- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/test/common/include/test_common/network_result.hpp b/test/common/include/test_common/network_result.hpp index c6fdb27b9..b569323ed 100644 --- a/test/common/include/test_common/network_result.hpp +++ b/test/common/include/test_common/network_result.hpp @@ -145,6 +145,14 @@ struct BOOST_ATTRIBUTE_NODISCARD network_result : network_result_base } }; +// The type of completion check to perform when running something with as_netresult +enum class completion_check +{ + immediate, // Op should perform an immediate completion + not_immediate, // Op should never perform an immediate completion + dont_care, // Don't check +}; + // Wraps a network_result and an executor. The result of as_netresult_t. template struct BOOST_ATTRIBUTE_NODISCARD runnable_network_result @@ -156,18 +164,32 @@ struct BOOST_ATTRIBUTE_NODISCARD runnable_network_result create_server_diag("network_result_v2 - diagnostics not cleared") }; bool done{false}; + bool was_immediate{false}; }; std::unique_ptr impl; runnable_network_result() : impl(new impl_t) {} - network_result run(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) && + network_result run(completion_check check, source_location loc = BOOST_MYSQL_CURRENT_LOCATION) && { poll_global_context(&impl->done, loc); + if (check != completion_check::dont_care) + { + BOOST_TEST_CONTEXT("Called from " << loc) + { + bool expected = check == completion_check::immediate; + BOOST_TEST(impl->was_immediate == expected); + } + } return std::move(impl->netres); } + network_result run(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) && + { + return std::move(*this).run(completion_check::dont_care, loc); + } + void validate_no_error(source_location loc = BOOST_MYSQL_CURRENT_LOCATION) && { std::move(*this).run(loc).validate_no_error(loc); @@ -277,7 +299,8 @@ class as_netres_handler }; // Expected top of the executor stack - boost::span expected_stack_top = is_initiation_function() + bool is_immediate = is_initiation_function(); + boost::span expected_stack_top = is_immediate ? boost::span(stack_data_immediate) : boost::span(stack_data_regular); @@ -297,6 +320,7 @@ class as_netres_handler target_->netres.diag = create_server_diag(""); // Mark the operation as done + target_->was_immediate = is_immediate; target_->done = true; } diff --git a/test/unit/test/detail/engine_impl.cpp b/test/unit/test/detail/engine_impl.cpp index d6f8536e0..5c2eb8b96 100644 --- a/test/unit/test/detail/engine_impl.cpp +++ b/test/unit/test/detail/engine_impl.cpp @@ -16,9 +16,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -407,18 +409,15 @@ BOOST_AUTO_TEST_CASE(stream_errors) } // Returning an error or next_action() from resume in the first call works correctly -BOOST_AUTO_TEST_CASE(resume_error_immediate) +BOOST_AUTO_TEST_CASE(resume_error_immediate_sync) { struct { const char* name; - signature_t fn; error_code ec; } test_cases[] = { - {"success_sync", sync_fn, error_code() }, - {"success_async", async_fn, error_code() }, - {"error_sync", sync_fn, asio::error::no_data}, - {"error_async", async_fn, asio::error::no_data}, + {"success", error_code() }, + {"error", asio::error::no_data}, }; for (const auto& tc : test_cases) @@ -429,13 +428,56 @@ BOOST_AUTO_TEST_CASE(resume_error_immediate) mock_algo algo(next_action(tc.ec)); test_engine eng{global_context_executor()}; - tc.fn(eng, any_resumable_ref(algo)) + sync_fn(eng, any_resumable_ref(algo)) .validate_error(tc.ec, create_server_diag("")); BOOST_TEST(eng.value.stream().calls.size() == 0u); algo.check_calls({ {error_code(), 0u} }); - // Note: the testing infrastructure already checks that we post correctly in the async versions + } + } +} + +BOOST_AUTO_TEST_CASE(resume_error_immediate_async) +{ + struct + { + const char* name; + error_code ec; + } test_cases[] = { + {"success", error_code() }, + {"error", asio::error::no_data}, + }; + + for (const auto& tc : test_cases) + { + BOOST_TEST_CONTEXT(tc.name) + { + // Setup + mock_algo algo(next_action(tc.ec)); + test_engine eng{global_context_executor()}; + bool finished = false; + + // We need to call the initiation function from a context thread + // to get immediate completions + asio::dispatch(asio::bind_executor(global_context_executor(), [&]() { + // Run the function + eng.async_run(any_resumable_ref(algo), as_netresult) + .run(completion_check::immediate) + .validate_error(tc.ec, create_server_diag("")); + + // Check + BOOST_TEST(eng.value.stream().calls.size() == 0u); + algo.check_calls({ + {error_code(), 0u} + }); + + // Mark as finished + finished = true; + })); + + // Run + poll_global_context(&finished); } } } From 1bb020a5bb51fb02647c20cca7d4cb12116efc42 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 27 Sep 2024 18:31:46 +0200 Subject: [PATCH 3/5] run_in_global_context --- .../include/test_common/tracker_executor.hpp | 6 ++++++ test/common/src/tracker_executor.cpp | 12 ++++++++++++ test/unit/test/detail/engine_impl.cpp | 19 ++++++------------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/test/common/include/test_common/tracker_executor.hpp b/test/common/include/test_common/tracker_executor.hpp index 1068c01f9..23eb84d4a 100644 --- a/test/common/include/test_common/tracker_executor.hpp +++ b/test/common/include/test_common/tracker_executor.hpp @@ -74,6 +74,12 @@ void poll_context( source_location loc = BOOST_MYSQL_CURRENT_LOCATION ); +// Run fn in the global context, then poll +void run_in_global_context( + const std::function& fn, + source_location loc = BOOST_MYSQL_CURRENT_LOCATION +); + } // namespace test } // namespace mysql } // namespace boost diff --git a/test/common/src/tracker_executor.cpp b/test/common/src/tracker_executor.cpp index 420e71811..c7190d6b6 100644 --- a/test/common/src/tracker_executor.cpp +++ b/test/common/src/tracker_executor.cpp @@ -6,6 +6,8 @@ // #include +#include +#include #include #include #include @@ -298,4 +300,14 @@ void boost::mysql::test::poll_global_context(const std::function& done, void boost::mysql::test::poll_context(asio::any_io_executor ex, const bool* done, source_location loc) { poll_context_impl(static_cast(ex.context()), [done]() { return *done; }, loc); +} + +void boost::mysql::test::run_in_global_context(const std::function& fn, source_location loc) +{ + bool finished = false; + asio::dispatch(asio::bind_executor(global_context_executor(), [&]() { + fn(); + finished = true; + })); + poll_global_context(&finished, loc); } \ No newline at end of file diff --git a/test/unit/test/detail/engine_impl.cpp b/test/unit/test/detail/engine_impl.cpp index 5c2eb8b96..2df771931 100644 --- a/test/unit/test/detail/engine_impl.cpp +++ b/test/unit/test/detail/engine_impl.cpp @@ -453,14 +453,13 @@ BOOST_AUTO_TEST_CASE(resume_error_immediate_async) { BOOST_TEST_CONTEXT(tc.name) { - // Setup - mock_algo algo(next_action(tc.ec)); - test_engine eng{global_context_executor()}; - bool finished = false; - // We need to call the initiation function from a context thread // to get immediate completions - asio::dispatch(asio::bind_executor(global_context_executor(), [&]() { + run_in_global_context([&]() { + // Setup + mock_algo algo(next_action(tc.ec)); + test_engine eng{global_context_executor()}; + // Run the function eng.async_run(any_resumable_ref(algo), as_netresult) .run(completion_check::immediate) @@ -471,13 +470,7 @@ BOOST_AUTO_TEST_CASE(resume_error_immediate_async) algo.check_calls({ {error_code(), 0u} }); - - // Mark as finished - finished = true; - })); - - // Run - poll_global_context(&finished); + }); } } } From bdbcd3ea3e5be57445083209d6ffd6c7ecf5ad5a Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 27 Sep 2024 18:35:08 +0200 Subject: [PATCH 4/5] Integ test --- test/integration/test/any_connection.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/integration/test/any_connection.cpp b/test/integration/test/any_connection.cpp index bedc63c09..fd6be1133 100644 --- a/test/integration/test/any_connection.cpp +++ b/test/integration/test/any_connection.cpp @@ -317,6 +317,24 @@ BOOST_FIXTURE_TEST_CASE(default_token_cancel_after, any_connection_fixture) }); } +// Spotcheck: immediate completions dispatched to the immediate executor +BOOST_FIXTURE_TEST_CASE(immediate_completions, any_connection_fixture) +{ + run_in_global_context([this]() { + // Setup + connect(); + results r; + + // Prepare a statement + auto stmt = conn.async_prepare_statement("SELECT 1", as_netresult).get(); + + // Executing with the wrong number of params is an immediate error + conn.async_execute(stmt.bind(0), r, as_netresult) + .run(completion_check::immediate) + .validate_error(client_errc::wrong_num_params); + }); +} + #endif BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 32a912bad2d2cc72059ca927411884be56d3e190 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 27 Sep 2024 19:37:36 +0200 Subject: [PATCH 5/5] Docs regarding executors in connection, any_connection --- include/boost/mysql/any_connection.hpp | 70 +++++++++++++ include/boost/mysql/connection.hpp | 140 +++++++++++++++++++++++++ 2 files changed, 210 insertions(+) diff --git a/include/boost/mysql/any_connection.hpp b/include/boost/mysql/any_connection.hpp index 98cad7c62..069b7b7e9 100644 --- a/include/boost/mysql/any_connection.hpp +++ b/include/boost/mysql/any_connection.hpp @@ -389,6 +389,16 @@ class any_connection * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) @@ -751,6 +761,16 @@ class any_connection * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. + * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ @@ -799,6 +819,16 @@ class any_connection * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. + * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ @@ -903,6 +933,16 @@ class any_connection * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) @@ -1017,6 +1057,16 @@ class any_connection * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(::boost::mysql::error_code)) @@ -1076,6 +1126,16 @@ class any_connection * \details * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) @@ -1143,6 +1203,16 @@ class any_connection * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. + * * \par Object lifetimes * The request and response objects must be kept alive and should not be modified * until the operation completes. diff --git a/include/boost/mysql/connection.hpp b/include/boost/mysql/connection.hpp index 183c41f29..1e2f6d272 100644 --- a/include/boost/mysql/connection.hpp +++ b/include/boost/mysql/connection.hpp @@ -247,6 +247,16 @@ class connection * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < typename EndpointType, @@ -322,6 +332,16 @@ class connection * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template @@ -393,6 +413,16 @@ class connection * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, @@ -495,6 +525,16 @@ class connection * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < BOOST_MYSQL_EXECUTION_REQUEST ExecutionRequest, @@ -576,6 +616,16 @@ class connection * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code, boost::mysql::statement)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template @@ -633,6 +683,16 @@ class connection * * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template @@ -693,6 +753,16 @@ class connection * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code, boost::mysql::rows_view)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template @@ -828,6 +898,16 @@ class connection * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. + * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ @@ -878,6 +958,16 @@ class connection * The handler signature for this operation is * `void(boost::mysql::error_code, std::size_t)`. * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. + * * \par Object lifetimes * The storage that `output` references must be kept alive until the operation completes. */ @@ -943,6 +1033,16 @@ class connection * \par Handler signature * The handler signature for this operation is * `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template < BOOST_MYSQL_EXECUTION_STATE_TYPE ExecutionStateType, @@ -999,6 +1099,16 @@ class connection * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template @@ -1071,6 +1181,16 @@ class connection * \n * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template @@ -1128,6 +1248,16 @@ class connection * \details * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template @@ -1186,6 +1316,16 @@ class connection * \details * \par Handler signature * The handler signature for this operation is `void(boost::mysql::error_code)`. + * + * \par Executor + * Intermediate completion handlers, as well as the final handler, are executed using + * `token`'s associated executor, or `this->get_executor()` if the token doesn't have an associated + * executor. + * + * If the final handler has an associated immediate executor, and the operation + * completes immediately, the final handler is dispatched to it. + * Otherwise, the final handler is called as if it was submitted using `asio::post`, + * and is never be called inline from within this function. */ template