Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-04-19 08:33:50

0001 //
0002 // Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
0003 //
0004 // Distributed under the Boost Software License, Version 1.0. (See accompanying
0005 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
0006 //
0007 
0008 #ifndef BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_RUN_WITH_TIMEOUT_HPP
0009 #define BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_RUN_WITH_TIMEOUT_HPP
0010 
0011 #include <boost/mysql/client_errc.hpp>
0012 #include <boost/mysql/error_code.hpp>
0013 
0014 #include <boost/asio/any_io_executor.hpp>
0015 #include <boost/asio/associated_allocator.hpp>
0016 #include <boost/asio/bind_executor.hpp>
0017 #include <boost/asio/cancellation_signal.hpp>
0018 
0019 #include <chrono>
0020 #include <cstddef>
0021 #include <memory>
0022 #include <type_traits>
0023 #include <utility>
0024 
0025 // Runs a certain operation with a timeout. This is a lightweight replacement
0026 // for parallel_group, since the latter has bugs (https://github.com/chriskohlhoff/asio/issues/1397)
0027 // that make it unsuitable for us.
0028 
0029 namespace boost {
0030 namespace mysql {
0031 namespace detail {
0032 
0033 // Shared state, between the timer and the op.
0034 // Not thread-safe - should only be used within the pool's executor.
0035 template <class Timer, class Handler>
0036 struct run_with_timeout_state
0037 {
0038     using this_type = run_with_timeout_state<Timer, Handler>;
0039 
0040     // A cancellation signal to cancel the op if the timer fires first.
0041     asio::cancellation_signal op_signal;
0042 
0043     // The number of ops remaining. We won't call the handler until timer and op finish.
0044     std::size_t remaining;
0045 
0046     // The error code to call the handler with
0047     error_code final_ec;
0048 
0049     // The final handler
0050     Handler handler;
0051 
0052     // The timer that provides our timeout
0053     Timer& timer;
0054 
0055     run_with_timeout_state(Handler&& handler, Timer& timer)
0056         : remaining(2), handler(std::move(handler)), timer(timer)
0057     {
0058     }
0059 
0060     // Used by handlers. Ensures that memory is released before calling the handler
0061     static void complete_one_op(std::shared_ptr<this_type>&& ptr)
0062     {
0063         // All finished
0064         if (ptr->remaining == 0u)
0065         {
0066             // Save members required to call the handler
0067             auto h = std::move(ptr->handler);
0068             error_code ec = ptr->final_ec;
0069 
0070             // Free memory
0071             ptr.reset();
0072 
0073             // Call the handler
0074             std::move(h)(ec);
0075         }
0076     }
0077 
0078     // A specialized handler for the timer
0079     struct timer_handler
0080     {
0081         std::shared_ptr<this_type> st;
0082 
0083         void operator()(error_code ec)
0084         {
0085             // If the op has already completed, we don't care about the timer's result
0086             // Emitting the signal may call the handler inline, so we decrement first
0087             if (st->remaining-- == 2u)
0088             {
0089                 st->final_ec = ec ? client_errc::cancelled : client_errc::timeout;
0090                 st->op_signal.emit(asio::cancellation_type::terminal);
0091             }
0092 
0093             // Notify
0094             complete_one_op(std::move(st));
0095         }
0096     };
0097 
0098     // A specialized handler for the op. Ensures that the op is
0099     // run with the timer's executor and with the adequate cancellation slot
0100     struct op_handler
0101     {
0102         std::shared_ptr<this_type> st;
0103 
0104         void operator()(error_code ec)
0105         {
0106             // If the timer finished first, we don't care about the result
0107             if (st->remaining-- == 2u)
0108             {
0109                 st->final_ec = ec;
0110                 st->timer.cancel();
0111             }
0112 
0113             // Notify
0114             complete_one_op(std::move(st));
0115         }
0116 
0117         // Executor binding
0118         using executor_type = asio::any_io_executor;
0119         executor_type get_executor() const { return st->timer.get_executor(); }
0120 
0121         // Cancellation slot binding
0122         using cancellation_slot_type = asio::cancellation_slot;
0123         cancellation_slot_type get_cancellation_slot() const noexcept { return st->op_signal.slot(); }
0124     };
0125 };
0126 
0127 // Runs op in parallel with a timer. op must be a deferred operation with void(error_code) signature.
0128 // Handler must be a suitable completion handler. Arbitrary completion tokens are not supported.
0129 // Handler is called with the following error code:
0130 //   - If the op finishes first, with op's error code.
0131 //   - If the timer finishes first, without interruptions, with client_errc::timeout.
0132 //   - If the timer finishes first because it was cancelled, with client_errc::cancelled.
0133 // Both op and timer are run within the timer's executor.
0134 // If timeout == 0, the timeout is disabled.
0135 template <class Op, class Timer, class Handler>
0136 void run_with_timeout(Op&& op, Timer& timer, std::chrono::steady_clock::duration timeout, Handler&& handler)
0137 {
0138     if (timeout.count() > 0)
0139     {
0140         using state_t = run_with_timeout_state<Timer, typename std::decay<Handler>::type>;
0141 
0142         // Allocate the shared state
0143         auto alloc = asio::get_associated_allocator(handler);
0144         using alloc_t = typename std::allocator_traits<decltype(alloc)>::template rebind_alloc<state_t>;
0145         auto st = std::allocate_shared<state_t>(alloc_t(alloc), std::move(handler), timer);
0146 
0147         // Launch the timer
0148         timer.expires_after(timeout);
0149         timer.async_wait(typename state_t::timer_handler{st});
0150 
0151         // Launch the op
0152         std::move(op)(typename state_t::op_handler{std::move(st)});
0153     }
0154     else
0155     {
0156         std::forward<Op>(op)(asio::bind_executor(timer.get_executor(), std::move(handler)));
0157     }
0158 }
0159 
0160 }  // namespace detail
0161 }  // namespace mysql
0162 }  // namespace boost
0163 
0164 #endif