LCOV - code coverage report
Current view: top level - corosio/native/detail/reactor - reactor_descriptor_state.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 78.9 % 90 71 19
Test Date: 2026-07-17 17:14:45 Functions: 100.0 % 4 4

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Steve Gerbino
       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_DESCRIPTOR_STATE_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_DESCRIPTOR_STATE_HPP
      12                 : 
      13                 : #include <boost/corosio/native/detail/reactor/reactor_op_base.hpp>
      14                 : #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp>
      15                 : #include <boost/corosio/detail/ready_queue.hpp>
      16                 : 
      17                 : #include <boost/corosio/detail/conditionally_enabled_mutex.hpp>
      18                 : 
      19                 : #include <atomic>
      20                 : #include <cstdint>
      21                 : #include <memory>
      22                 : 
      23                 : #include <errno.h>
      24                 : #include <sys/socket.h>
      25                 : 
      26                 : namespace boost::corosio::detail {
      27                 : 
      28                 : /// Shared reactor event constants.
      29                 : /// These match epoll numeric values; kqueue maps its events to the same.
      30                 : static constexpr std::uint32_t reactor_event_read  = 0x001;
      31                 : static constexpr std::uint32_t reactor_event_write = 0x004;
      32                 : static constexpr std::uint32_t reactor_event_error = 0x008;
      33                 : 
      34                 : /** Per-descriptor state shared across reactor backends.
      35                 : 
      36                 :     Tracks pending operations for a file descriptor. The fd is registered
      37                 :     once with the reactor and stays registered until closed. Uses deferred
      38                 :     I/O: the reactor sets ready_events atomically, then enqueues this state.
      39                 :     When popped by the scheduler, invoke_deferred_io() performs I/O under
      40                 :     the mutex and queues completed ops.
      41                 : 
      42                 :     Non-template: uses reactor_op_base pointers so the scheduler and
      43                 :     descriptor_state code exist as a single copy in the binary regardless
      44                 :     of how many backends are compiled in.
      45                 : 
      46                 :     @par Thread Safety
      47                 :     The mutex protects operation pointers and ready flags. ready_events_
      48                 :     and is_enqueued_ are atomic for lock-free reactor access.
      49                 : */
      50                 : struct reactor_descriptor_state : scheduler_op
      51                 : {
      52                 :     /// Protects operation pointers and ready/cancel flags.
      53                 :     /// Becomes a no-op in single-threaded mode.
      54                 :     conditionally_enabled_mutex mutex{true};
      55                 : 
      56                 :     /// Pending read operation (guarded by `mutex`).
      57                 :     reactor_op_base* read_op = nullptr;
      58                 : 
      59                 :     /// Pending write operation (guarded by `mutex`).
      60                 :     reactor_op_base* write_op = nullptr;
      61                 : 
      62                 :     /// Pending connect operation (guarded by `mutex`).
      63                 :     reactor_op_base* connect_op = nullptr;
      64                 : 
      65                 :     /// Pending wait-for-read operation (guarded by `mutex`).
      66                 :     reactor_op_base* wait_read_op = nullptr;
      67                 : 
      68                 :     /// Pending wait-for-write operation (guarded by `mutex`).
      69                 :     reactor_op_base* wait_write_op = nullptr;
      70                 : 
      71                 :     /// Pending wait-for-error operation (guarded by `mutex`).
      72                 :     reactor_op_base* wait_error_op = nullptr;
      73                 : 
      74                 :     /// True if a read edge event arrived before an op was registered.
      75                 :     bool read_ready = false;
      76                 : 
      77                 :     /// True if a write edge event arrived before an op was registered.
      78                 :     bool write_ready = false;
      79                 : 
      80                 :     /// Deferred read cancellation (IOCP-style cancel semantics).
      81                 :     bool read_cancel_pending = false;
      82                 : 
      83                 :     /// Deferred write cancellation (IOCP-style cancel semantics).
      84                 :     bool write_cancel_pending = false;
      85                 : 
      86                 :     /// Deferred connect cancellation (IOCP-style cancel semantics).
      87                 :     bool connect_cancel_pending = false;
      88                 : 
      89                 :     /// Deferred wait-read cancellation (IOCP-style cancel semantics).
      90                 :     bool wait_read_cancel_pending = false;
      91                 : 
      92                 :     /// Deferred wait-write cancellation (IOCP-style cancel semantics).
      93                 :     bool wait_write_cancel_pending = false;
      94                 : 
      95                 :     /// Deferred wait-error cancellation (IOCP-style cancel semantics).
      96                 :     bool wait_error_cancel_pending = false;
      97                 : 
      98                 :     /// Event mask set during registration (no mutex needed).
      99                 :     std::uint32_t registered_events = 0;
     100                 : 
     101                 :     /// File descriptor this state tracks.
     102                 :     int fd = -1;
     103                 : 
     104                 :     /// Accumulated ready events (set by reactor, read by scheduler).
     105                 :     std::atomic<std::uint32_t> ready_events_{0};
     106                 : 
     107                 :     /// True while this state is queued in the scheduler's completed_ops.
     108                 :     std::atomic<bool> is_enqueued_{false};
     109                 : 
     110                 :     /// Owning scheduler for posting completions.
     111                 :     reactor_scheduler const* scheduler_ = nullptr;
     112                 : 
     113                 :     /// Prevents impl destruction while queued in the scheduler.
     114                 :     std::shared_ptr<void> impl_ref_;
     115                 : 
     116                 :     /// Add ready events atomically.
     117                 :     /// Release pairs with the consumer's acquire exchange on
     118                 :     /// ready_events_ so the consumer sees all flags. On x86 (TSO)
     119                 :     /// this compiles to the same LOCK OR as relaxed.
     120 HIT      261745 :     void add_ready_events(std::uint32_t ev) noexcept
     121                 :     {
     122          261745 :         ready_events_.fetch_or(ev, std::memory_order_release);
     123          261745 :     }
     124                 : 
     125                 :     /// Invoke deferred I/O and dispatch completions.
     126          261592 :     void operator()() override
     127                 :     {
     128          261592 :         invoke_deferred_io();
     129          261592 :     }
     130                 : 
     131                 :     /// Destroy without invoking.
     132                 :     /// Called during scheduler::shutdown() drain. Clear impl_ref_ to break
     133                 :     /// the self-referential cycle set by close_socket().
     134             153 :     void destroy() override
     135                 :     {
     136             153 :         impl_ref_.reset();
     137             153 :     }
     138                 : 
     139                 :     /** Perform deferred I/O and queue completions.
     140                 : 
     141                 :         Performs I/O under the mutex and queues completed ops. EAGAIN
     142                 :         ops stay parked in their slot for re-delivery on the next
     143                 :         edge event.
     144                 :     */
     145                 :     void invoke_deferred_io();
     146                 : };
     147                 : 
     148                 : inline void
     149          261592 : reactor_descriptor_state::invoke_deferred_io()
     150                 : {
     151          261592 :     std::shared_ptr<void> prevent_impl_destruction;
     152          261592 :     ready_queue local_ops;
     153                 : 
     154                 :     {
     155          261592 :         conditionally_enabled_mutex::scoped_lock lock(mutex);
     156                 : 
     157                 :         // Must clear is_enqueued_ and move impl_ref_ under the same
     158                 :         // lock that processes I/O. close_socket() checks is_enqueued_
     159                 :         // under this mutex — without atomicity between the flag store
     160                 :         // and the ref move, close_socket() could see is_enqueued_==false,
     161                 :         // skip setting impl_ref_, and destroy the impl under us.
     162          261592 :         prevent_impl_destruction = std::move(impl_ref_);
     163          261592 :         is_enqueued_.store(false, std::memory_order_release);
     164                 : 
     165          261592 :         std::uint32_t ev = ready_events_.exchange(0, std::memory_order_acquire);
     166          261592 :         if (ev == 0)
     167                 :         {
     168                 :             // Mutex unlocks here; compensate for work_cleanup's decrement
     169 MIS           0 :             scheduler_->compensating_work_started();
     170               0 :             return;
     171                 :         }
     172                 : 
     173 HIT      261592 :         int err = 0;
     174          261592 :         if (ev & reactor_event_error)
     175                 :         {
     176               9 :             socklen_t len = sizeof(err);
     177               9 :             if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
     178 MIS           0 :                 err = errno;
     179 HIT           9 :             if (err == 0)
     180 MIS           0 :                 err = EIO;
     181                 :         }
     182                 : 
     183 HIT      261592 :         if (ev & reactor_event_read)
     184                 :         {
     185          232711 :             if (read_op)
     186                 :             {
     187            6574 :                 auto* rd = read_op;
     188            6574 :                 if (err)
     189               1 :                     rd->complete(err, 0);
     190                 :                 else
     191            6573 :                     rd->perform_io();
     192                 : 
     193            6574 :                 if (rd->errn == EAGAIN || rd->errn == EWOULDBLOCK)
     194                 :                 {
     195             350 :                     rd->errn = 0;
     196                 :                 }
     197                 :                 else
     198                 :                 {
     199            6224 :                     read_op = nullptr;
     200            6224 :                     local_ops.push(rd);
     201                 :                 }
     202                 :             }
     203                 :             else
     204                 :             {
     205          226137 :                 read_ready = true;
     206                 :             }
     207                 : 
     208                 :             // Complete any parked wait-for-read regardless of read_op presence.
     209          232711 :             if (wait_read_op)
     210                 :             {
     211              16 :                 wait_read_op->complete(err, 0);
     212              16 :                 local_ops.push(std::exchange(wait_read_op, nullptr));
     213                 :             }
     214                 :         }
     215          261592 :         if (ev & reactor_event_write)
     216                 :         {
     217           37457 :             bool had_write_op = (connect_op || write_op);
     218           37457 :             if (connect_op)
     219                 :             {
     220            5767 :                 auto* cn = connect_op;
     221            5767 :                 if (err)
     222               7 :                     cn->complete(err, 0);
     223                 :                 else
     224            5760 :                     cn->perform_io();
     225            5767 :                 connect_op = nullptr;
     226            5767 :                 local_ops.push(cn);
     227                 :             }
     228           37457 :             if (write_op)
     229                 :             {
     230             190 :                 auto* wr = write_op;
     231             190 :                 if (err)
     232 MIS           0 :                     wr->complete(err, 0);
     233                 :                 else
     234 HIT         190 :                     wr->perform_io();
     235                 : 
     236             190 :                 if (wr->errn == EAGAIN || wr->errn == EWOULDBLOCK)
     237                 :                 {
     238               1 :                     wr->errn = 0;
     239                 :                 }
     240                 :                 else
     241                 :                 {
     242             189 :                     write_op = nullptr;
     243             189 :                     local_ops.push(wr);
     244                 :                 }
     245                 :             }
     246           37457 :             if (!had_write_op)
     247           31500 :                 write_ready = true;
     248                 : 
     249                 :             // Complete any parked wait-for-write regardless of write_op presence.
     250           37457 :             if (wait_write_op)
     251                 :             {
     252 MIS           0 :                 wait_write_op->complete(err, 0);
     253               0 :                 local_ops.push(std::exchange(wait_write_op, nullptr));
     254                 :             }
     255                 :         }
     256                 :         // Complete a parked wait-for-error on any error condition.
     257 HIT      261592 :         if ((ev & reactor_event_error) || err)
     258                 :         {
     259               9 :             if (wait_error_op)
     260                 :             {
     261 MIS           0 :                 wait_error_op->complete(err, 0);
     262               0 :                 local_ops.push(std::exchange(wait_error_op, nullptr));
     263                 :             }
     264                 :         }
     265 HIT      261592 :         if (err)
     266                 :         {
     267               9 :             if (read_op)
     268                 :             {
     269 MIS           0 :                 read_op->complete(err, 0);
     270               0 :                 local_ops.push(std::exchange(read_op, nullptr));
     271                 :             }
     272 HIT           9 :             if (write_op)
     273                 :             {
     274 MIS           0 :                 write_op->complete(err, 0);
     275               0 :                 local_ops.push(std::exchange(write_op, nullptr));
     276                 :             }
     277 HIT           9 :             if (connect_op)
     278                 :             {
     279 MIS           0 :                 connect_op->complete(err, 0);
     280               0 :                 local_ops.push(std::exchange(connect_op, nullptr));
     281                 :             }
     282 HIT           9 :             if (wait_read_op)
     283                 :             {
     284 MIS           0 :                 wait_read_op->complete(err, 0);
     285               0 :                 local_ops.push(std::exchange(wait_read_op, nullptr));
     286                 :             }
     287 HIT           9 :             if (wait_write_op)
     288                 :             {
     289 MIS           0 :                 wait_write_op->complete(err, 0);
     290               0 :                 local_ops.push(std::exchange(wait_write_op, nullptr));
     291                 :             }
     292                 :         }
     293 HIT      261592 :     }
     294                 : 
     295                 :     // Execute first handler inline — the scheduler's work_cleanup
     296                 :     // accounts for this as the "consumed" work item. local_ops holds
     297                 :     // only ops, so the popped entry decodes directly.
     298          261592 :     scheduler_op* first = ready_as_op(local_ops.pop());
     299          261592 :     if (first)
     300                 :     {
     301           12196 :         scheduler_->post_deferred_completions(local_ops);
     302           12196 :         (*first)();
     303                 :     }
     304                 :     else
     305                 :     {
     306          249396 :         scheduler_->compensating_work_started();
     307                 :     }
     308          261592 : }
     309                 : 
     310                 : } // namespace boost::corosio::detail
     311                 : 
     312                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_DESCRIPTOR_STATE_HPP
        

Generated by: LCOV version 2.3