// // Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 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) // #ifndef BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_RUN_WITH_TIMEOUT_HPP #define BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_RUN_WITH_TIMEOUT_HPP #include #include #include #include #include #include #include #include #include #include #include // Runs a certain operation with a timeout. This is a lightweight replacement // for parallel_group, since the latter has bugs (https://github.com/chriskohlhoff/asio/issues/1397) // that make it unsuitable for us. namespace boost { namespace mysql { namespace detail { // Shared state, between the timer and the op. // Not thread-safe - should only be used within the pool's executor. template struct run_with_timeout_state { using this_type = run_with_timeout_state; // A cancellation signal to cancel the op if the timer fires first. asio::cancellation_signal op_signal; // The number of ops remaining. We won't call the handler until timer and op finish. std::size_t remaining; // The error code to call the handler with error_code final_ec; // The final handler Handler handler; // The timer that provides our timeout Timer& timer; run_with_timeout_state(Handler&& handler, Timer& timer) : remaining(2), handler(std::move(handler)), timer(timer) { } // Used by handlers. Ensures that memory is released before calling the handler static void complete_one_op(std::shared_ptr&& ptr) { // All finished if (ptr->remaining == 0u) { // Save members required to call the handler auto h = std::move(ptr->handler); error_code ec = ptr->final_ec; // Free memory ptr.reset(); // Call the handler std::move(h)(ec); } } // A specialized handler for the timer struct timer_handler { std::shared_ptr st; void operator()(error_code ec) { // If the op has already completed, we don't care about the timer's result // Emitting the signal may call the handler inline, so we decrement first if (st->remaining-- == 2u) { st->final_ec = ec ? client_errc::cancelled : client_errc::timeout; st->op_signal.emit(asio::cancellation_type::terminal); } // Notify complete_one_op(std::move(st)); } }; // A specialized handler for the op. Ensures that the op is // run with the timer's executor and with the adequate cancellation slot struct op_handler { std::shared_ptr st; void operator()(error_code ec) { // If the timer finished first, we don't care about the result if (st->remaining-- == 2u) { st->final_ec = ec; st->timer.cancel(); } // Notify complete_one_op(std::move(st)); } // Executor binding using executor_type = asio::any_io_executor; executor_type get_executor() const { return st->timer.get_executor(); } // Cancellation slot binding using cancellation_slot_type = asio::cancellation_slot; cancellation_slot_type get_cancellation_slot() const noexcept { return st->op_signal.slot(); } }; }; // Runs op in parallel with a timer. op must be a deferred operation with void(error_code) signature. // Handler must be a suitable completion handler. Arbitrary completion tokens are not supported. // Handler is called with the following error code: // - If the op finishes first, with op's error code. // - If the timer finishes first, without interruptions, with client_errc::timeout. // - If the timer finishes first because it was cancelled, with client_errc::cancelled. // Both op and timer are run within the timer's executor. // If timeout == 0, the timeout is disabled. template void run_with_timeout(Op&& op, Timer& timer, std::chrono::steady_clock::duration timeout, Handler&& handler) { if (timeout.count() > 0) { using state_t = run_with_timeout_state::type>; // Allocate the shared state auto alloc = asio::get_associated_allocator(handler); using alloc_t = typename std::allocator_traits::template rebind_alloc; auto st = std::allocate_shared(alloc_t(alloc), std::move(handler), timer); // Launch the timer timer.expires_after(timeout); timer.async_wait(typename state_t::timer_handler{st}); // Launch the op std::move(op)(typename state_t::op_handler{std::move(st)}); } else { std::forward(op)(asio::bind_executor(timer.get_executor(), std::move(handler))); } } } // namespace detail } // namespace mysql } // namespace boost #endif