LCOV - code coverage report
Current view: top level - corosio/native/detail/reactor - reactor_io_core.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 97.0 % 133 129 4
Test Date: 2026-10-17 04:01:32 Functions: 99.3 % 536 532 4

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Michael Vandeberg
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/corosio
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_IO_CORE_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_IO_CORE_HPP
      12                 : 
      13                 : #include <boost/corosio/detail/config.hpp>
      14                 : #include <boost/corosio/detail/object_ref.hpp>
      15                 : #include <boost/corosio/native/detail/reactor/reactor_op_base.hpp>
      16                 : 
      17                 : #include <atomic>
      18                 : #include <cstring>
      19                 : #include <mutex>
      20                 : #include <system_error>
      21                 : #include <utility>
      22                 : 
      23                 : #include <errno.h>
      24                 : 
      25                 : /* Shared reactor I/O protocol.
      26                 : 
      27                 :    One implementation of the register/park/cancel/teardown protocol
      28                 :    for every reactor-backed object -- stream and datagram sockets,
      29                 :    acceptors, and posix descriptors -- over a backend's
      30                 :    descriptor_state. Asio keeps the same protocol in its reactor
      31                 :    (start_op, cancel_ops, deregister_descriptor); services and the
      32                 :    objects' own verbs stay per type.
      33                 : 
      34                 :    Derived supplies its op slots through for_each_op,
      35                 :    for_each_desc_entry and op_to_desc_slot, and is an
      36                 :    io_object::implementation whose callers hold a reference on it.
      37                 : */
      38                 : 
      39                 : namespace boost::corosio::detail {
      40                 : 
      41                 : /** CRTP base holding the reactor parking and cancel protocol.
      42                 : 
      43                 :     @tparam Derived   The concrete object type (CRTP).
      44                 :     @tparam Service   The backend service that owns Derived.
      45                 :     @tparam DescState The backend's descriptor_state type.
      46                 : */
      47                 : template<class Derived, class Service, class DescState>
      48                 : class reactor_io_core
      49                 : {
      50 HIT      229287 :     Derived* self_ptr() noexcept
      51                 :     {
      52          229287 :         return static_cast<Derived*>(this);
      53                 :     }
      54                 : 
      55                 : protected:
      56                 :     // NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility)
      57            7572 :     explicit reactor_io_core(Service& svc) noexcept : svc_(svc) {}
      58                 : 
      59                 :     Service& svc_;
      60                 : 
      61                 : public:
      62                 :     /// Per-descriptor state for persistent reactor registration.
      63                 :     DescState desc_state_;
      64                 : 
      65                 :     /** Cancel a single pending operation.
      66                 : 
      67                 :         Claims the operation from its descriptor_state slot under
      68                 :         the mutex and posts it to the scheduler as cancelled.
      69                 :     */
      70                 :     template<class Op>
      71             676 :     void cancel_single_op(Op& op) noexcept
      72                 :     {
      73             676 :         op.request_cancel();
      74                 : 
      75             676 :         reactor_op_base** desc_op_ptr = self_ptr()->op_to_desc_slot(op);
      76             676 :         if (!desc_op_ptr)
      77 MIS           0 :             return;
      78                 : 
      79 HIT         676 :         reactor_op_base* claimed = nullptr;
      80                 :         {
      81             676 :             std::lock_guard lock(desc_state_.mutex);
      82             676 :             if (*desc_op_ptr == &op)
      83                 :             {
      84             544 :                 claimed = std::exchange(*desc_op_ptr, nullptr);
      85             544 :                 svc_.scheduler().cancel_posting_begin();
      86                 :             }
      87                 :             // Not in the slot: request_cancel() above already set
      88                 :             // op.cancelled, which register_op consults before parking
      89                 :             // and the completion decode consults on delivery. Latching
      90                 :             // a descriptor flag here instead would outlive this op and
      91                 :             // cancel the next wait in the same direction.
      92             676 :         }
      93             676 :         if (claimed)
      94                 :         {
      95             544 :             op.object_ref_ = detail::object_ref(self_ptr());
      96             544 :             svc_.scheduler().post_counted(&op);
      97             544 :             svc_.scheduler().cancel_posting_end();
      98                 :         }
      99                 :     }
     100                 : 
     101                 : protected:
     102                 :     /** Clear every slot and register @a fd with the reactor.
     103                 : 
     104                 :         @return The reactor's refusal, in which case the state is left
     105                 :             unregistered.
     106                 :     */
     107           14253 :     std::error_code register_fd(int fd) noexcept
     108                 :     {
     109                 :         {
     110                 :             // A completion from an earlier registration may still read
     111                 :             // these under the mutex.
     112           14253 :             std::lock_guard lock(desc_state_.mutex);
     113           14253 :             desc_state_.fd = fd;
     114           14253 :             self_ptr()->for_each_desc_entry(
     115           86460 :                 [](auto&, reactor_op_base*& slot) { slot = nullptr; });
     116           14253 :         }
     117           14253 :         if (auto ec = svc_.scheduler().register_descriptor(fd, &desc_state_))
     118                 :         {
     119               4 :             std::lock_guard lock(desc_state_.mutex);
     120               4 :             desc_state_.fd                = -1;
     121               4 :             desc_state_.registered_events = 0;
     122               4 :             return ec;
     123               4 :         }
     124           14249 :         return {};
     125                 :     }
     126                 : 
     127                 :     /** Register an op with the reactor.
     128                 : 
     129                 :         Handles cached edge events. Called on the EAGAIN/EINPROGRESS
     130                 :         path when speculative I/O failed.
     131                 :     */
     132                 :     template<class Op>
     133           10346 :     void register_op(
     134                 :         Op& op,
     135                 :         reactor_op_base*& desc_slot,
     136                 :         bool& ready_flag,
     137                 :         bool is_write_direction = false) noexcept
     138                 :     {
     139           10346 :         svc_.work_started();
     140           10346 :         if (park_op(op, desc_slot, ready_flag, is_write_direction))
     141           10339 :             return;
     142                 :         // Posted after the descriptor lock is released: the scheduler
     143                 :         // takes its own lock before descriptor locks.
     144               7 :         svc_.scheduler().post_counted(&op);
     145                 :     }
     146                 : 
     147                 :     /// Cancel every pending operation.
     148             367 :     void cancel_all() noexcept
     149                 :     {
     150            2588 :         self_ptr()->for_each_op([](auto& op) { op.request_cancel(); });
     151                 : 
     152                 :         reactor_op_base* claimed[max_claimed];
     153             367 :         int count = 0;
     154                 :         {
     155             367 :             std::lock_guard lock(desc_state_.mutex);
     156             367 :             self_ptr()->for_each_desc_entry(
     157            4442 :                 [&](auto& op, reactor_op_base*& desc_slot) {
     158            2221 :                     if (desc_slot == &op)
     159                 :                     {
     160             269 :                         BOOST_COROSIO_ASSERT(count < max_claimed);
     161             269 :                         claimed[count++] = std::exchange(desc_slot, nullptr);
     162                 :                     }
     163                 :                 });
     164             367 :         }
     165             367 :         post_claimed(claimed, count);
     166             367 :     }
     167                 : 
     168                 :     /** Cancel every operation, drop the reactor registration, and
     169                 :         claim every parked op for teardown.
     170                 : 
     171                 :         Deregistration hands a reference to the scheduler, which keeps
     172                 :         the object alive while a reactor pass may still deliver an
     173                 :         event for the descriptor. Afterwards the descriptor state reads
     174                 :         as closed, so the caller may close or release the fd.
     175                 :     */
     176           94331 :     void abandon_all() noexcept
     177                 :     {
     178          645613 :         self_ptr()->for_each_op([](auto& op) { op.request_cancel(); });
     179                 : 
     180           94331 :         int fd          = -1;
     181           94331 :         bool registered = false;
     182                 :         {
     183           94331 :             std::lock_guard lock(desc_state_.mutex);
     184           94331 :             fd         = desc_state_.fd;
     185           94331 :             registered = desc_state_.registered_events != 0;
     186           94331 :         }
     187           94331 :         if (fd >= 0 && registered)
     188                 :         {
     189           22997 :             svc_.scheduler().deregister_descriptor(fd);
     190           45994 :             svc_.scheduler().retire_descriptor(
     191           45994 :                 desc_state_, detail::object_ref(self_ptr()));
     192                 :         }
     193                 : 
     194                 :         reactor_op_base* claimed[max_claimed];
     195           94331 :         int count = 0;
     196                 :         {
     197           94331 :             std::lock_guard lock(desc_state_.mutex);
     198           94331 :             self_ptr()->for_each_desc_entry(
     199         1102564 :                 [&](auto& /*op*/, reactor_op_base*& desc_slot) {
     200          551282 :                     if (auto* c = std::exchange(desc_slot, nullptr))
     201                 :                     {
     202            1152 :                         BOOST_COROSIO_ASSERT(count < max_claimed);
     203            1152 :                         claimed[count++] = c;
     204                 :                     }
     205                 :                 });
     206           94331 :             desc_state_.read_ready  = false;
     207           94331 :             desc_state_.write_ready = false;
     208                 :             // Cleared before the fd is closed: a queued completion reads
     209                 :             // it under this mutex, and the number can be reused once
     210                 :             // closed.
     211           94331 :             desc_state_.fd                = -1;
     212           94331 :             desc_state_.registered_events = 0;
     213           94331 :             desc_state_.unpollable        = false;
     214           94331 :         }
     215           94331 :         post_claimed(claimed, count);
     216           94331 :     }
     217                 : 
     218                 :     /** Reset the descriptor state a recycled object starts from.
     219                 : 
     220                 :         Every field is reset rather than asserted so a debug build may
     221                 :         poison them on retirement. References and the queued flag are
     222                 :         asserted: nothing may hold one once the count reached zero.
     223                 :     */
     224           21977 :     void reset_desc_state() noexcept
     225                 :     {
     226           21977 :         desc_state_.fd                = -1;
     227           21977 :         desc_state_.registered_events = 0;
     228           21977 :         desc_state_.unpollable        = false;
     229           21977 :         desc_state_.read_op           = nullptr;
     230           21977 :         desc_state_.write_op          = nullptr;
     231           21977 :         desc_state_.connect_op        = nullptr;
     232           21977 :         desc_state_.wait_read_op      = nullptr;
     233           21977 :         desc_state_.wait_write_op     = nullptr;
     234           21977 :         desc_state_.wait_error_op     = nullptr;
     235           21977 :         desc_state_.read_ready        = false;
     236           21977 :         desc_state_.write_ready       = false;
     237           21977 :         BOOST_COROSIO_ASSERT(!desc_state_.object_ref_);
     238           21977 :         BOOST_COROSIO_ASSERT(!desc_state_.retired_ref_);
     239           21977 :         BOOST_COROSIO_ASSERT(!desc_state_.dropped_ref_);
     240           21977 :         BOOST_COROSIO_ASSERT(
     241                 :             !desc_state_.is_enqueued_.load(std::memory_order_relaxed));
     242           21977 :     }
     243                 : 
     244                 : #if !defined(NDEBUG)
     245                 :     /** Poison the descriptor fields `reset_desc_state()` re-initializes.
     246                 : 
     247                 :         Leaves the mutex, the atomics, `scheduler_`, the references and
     248                 :         the retirement links alone: the scheduler and `recycle()` may
     249                 :         still read them.
     250                 :     */
     251           29549 :     void poison_desc_state() noexcept
     252                 :     {
     253          325039 :         auto smash = [](auto& field) {
     254          325039 :             std::memset(static_cast<void*>(&field), 0xDB, sizeof(field));
     255                 :         };
     256           29549 :         smash(desc_state_.fd);
     257           29549 :         smash(desc_state_.registered_events);
     258           29549 :         smash(desc_state_.unpollable);
     259           29549 :         smash(desc_state_.read_op);
     260           29549 :         smash(desc_state_.write_op);
     261           29549 :         smash(desc_state_.connect_op);
     262           29549 :         smash(desc_state_.wait_read_op);
     263           29549 :         smash(desc_state_.wait_write_op);
     264           29549 :         smash(desc_state_.wait_error_op);
     265           29549 :         smash(desc_state_.read_ready);
     266           29549 :         smash(desc_state_.write_ready);
     267           29549 :     }
     268                 : #endif
     269                 : 
     270                 : private:
     271                 :     // A claim empties its slot, so no more ops are claimed than
     272                 :     // descriptor_state has slots (read, write, connect, wait_read,
     273                 :     // wait_write, wait_error), however many ops share them.
     274                 :     static constexpr int max_claimed = 6;
     275                 : 
     276                 :     /** Park @a op unless it can complete now.
     277                 : 
     278                 :         @return True if parked; false if the caller must post it.
     279                 :     */
     280                 :     template<class Op>
     281           10346 :     bool park_op(
     282                 :         Op& op,
     283                 :         reactor_op_base*& desc_slot,
     284                 :         bool& ready_flag,
     285                 :         bool is_write_direction) noexcept
     286                 :     {
     287           10346 :         std::lock_guard lock(desc_state_.mutex);
     288           10346 :         if (ready_flag)
     289                 :         {
     290             228 :             ready_flag = false;
     291             228 :             op.perform_io();
     292             228 :             if (op.errn != EAGAIN && op.errn != EWOULDBLOCK)
     293               6 :                 return false;
     294             222 :             op.errn = 0;
     295                 :         }
     296                 : 
     297           10340 :         if (op.cancelled.load(std::memory_order_acquire))
     298 MIS           0 :             return false;
     299                 : 
     300 HIT       10340 :         if (desc_state_.unpollable)
     301                 :         {
     302                 :             // Nothing will ever report readiness for this fd.
     303               1 :             op.complete(EOPNOTSUPP, 0);
     304               1 :             return false;
     305                 :         }
     306                 : 
     307           10339 :         if (is_write_direction)
     308                 :         {
     309            8362 :             if (auto ec = svc_.scheduler().ensure_write_registered(
     310                 :                     desc_state_.fd, &desc_state_))
     311                 :             {
     312 MIS           0 :                 op.complete(ec.value(), 0);
     313               0 :                 return false;
     314                 :             }
     315                 :         }
     316                 : 
     317 HIT       10339 :         desc_slot = &op;
     318                 :         // Select rebuilds its fd_sets from parked ops only, so parking
     319                 :         // must wake it, under the lock: once released, the op can
     320                 :         // complete and its context be destroyed. Compiled away for
     321                 :         // epoll and kqueue.
     322                 :         if constexpr (Service::needs_park_notification)
     323            3778 :             svc_.scheduler().notify_reactor();
     324           10339 :         return true;
     325           10346 :     }
     326                 : 
     327           94698 :     void post_claimed(reactor_op_base** claimed, int count) noexcept
     328                 :     {
     329           96119 :         for (int i = 0; i < count; ++i)
     330                 :         {
     331            1421 :             claimed[i]->object_ref_ = detail::object_ref(self_ptr());
     332            1421 :             svc_.scheduler().post_counted(claimed[i]);
     333                 :         }
     334           94698 :     }
     335                 : };
     336                 : 
     337                 : } // namespace boost::corosio::detail
     338                 : 
     339                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_IO_CORE_HPP
        

Generated by: LCOV version 2.3