LCOV - code coverage report
Current view: top level - corosio/native/detail/reactor - reactor_basic_socket.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 97.4 % 151 147 4
Test Date: 2026-08-21 20:48:07 Functions: 94.8 % 344 326 18

           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_BASIC_SOCKET_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_BASIC_SOCKET_HPP
      12                 : 
      13                 : #include <boost/corosio/detail/intrusive.hpp>
      14                 : #include <boost/corosio/detail/native_handle.hpp>
      15                 : #include <boost/corosio/endpoint.hpp>
      16                 : #include <boost/corosio/native/detail/native_socket_base.hpp>
      17                 : #include <boost/corosio/native/detail/reactor/reactor_op_base.hpp>
      18                 : #include <boost/corosio/native/detail/make_err.hpp>
      19                 : #include <boost/corosio/native/detail/endpoint_convert.hpp>
      20                 : 
      21                 : #include <memory>
      22                 : #include <mutex>
      23                 : #include <utility>
      24                 : 
      25                 : #include <errno.h>
      26                 : #include <netinet/in.h>
      27                 : #include <sys/socket.h>
      28                 : #include <unistd.h>
      29                 : 
      30                 : namespace boost::corosio::detail {
      31                 : 
      32                 : /** CRTP base for reactor-backed socket implementations.
      33                 : 
      34                 :     Extracts the shared data members, virtual overrides, and
      35                 :     cancel/close/register logic that is identical across TCP
      36                 :     (reactor_stream_socket) and UDP (reactor_datagram_socket).
      37                 : 
      38                 :     Derived classes provide CRTP callbacks that enumerate their
      39                 :     specific op slots so cancel/close can iterate them generically.
      40                 : 
      41                 :     @tparam Derived   The concrete socket type (CRTP).
      42                 :     @tparam ImplBase  The public vtable base (tcp_socket::implementation
      43                 :                       or udp_socket::implementation).
      44                 :     @tparam Service   The backend's service type.
      45                 :     @tparam DescState The backend's descriptor_state type.
      46                 :     @tparam Endpoint  The endpoint type (endpoint or local_endpoint).
      47                 : */
      48                 : template<
      49                 :     class Derived,
      50                 :     class ImplBase,
      51                 :     class Service,
      52                 :     class DescState,
      53                 :     class Endpoint = endpoint>
      54                 : class reactor_basic_socket
      55                 :     : public native_socket_base<Derived, ImplBase, Endpoint>
      56                 :     , public intrusive_list<Derived>::node
      57                 : {
      58                 :     friend Derived;
      59                 : 
      60                 :     template<class, class, class, class, class, class, class, class, class>
      61                 :     friend class reactor_stream_socket;
      62                 : 
      63                 :     template<class, class, class, class, class, class, class, class, class, class, class>
      64                 :     friend class reactor_datagram_socket;
      65                 : 
      66 HIT       21975 :     explicit reactor_basic_socket(Service& svc) noexcept : svc_(svc) {}
      67                 : 
      68                 : protected:
      69                 :     // fd_ / local_endpoint_ and the synchronous accessors (native_handle,
      70                 :     // is_open, set_option/get_option, set_socket/set_local_endpoint, do_bind)
      71                 :     // live in native_socket_base — the readiness/completion-agnostic base
      72                 :     // shared with io_uring's sockets. The using-declarations make the
      73                 :     // inherited members visible to this template's own unqualified
      74                 :     // references below (two-phase lookup).
      75                 :     using native_socket_base<Derived, ImplBase, Endpoint>::fd_;
      76                 :     using native_socket_base<Derived, ImplBase, Endpoint>::local_endpoint_;
      77                 : 
      78                 :     Service& svc_;
      79                 : 
      80                 : public:
      81                 :     /// Per-descriptor state for persistent reactor registration.
      82                 :     DescState desc_state_;
      83                 : 
      84           21975 :     ~reactor_basic_socket() override = default;
      85                 : 
      86                 :     /** Assign the fd, initialize descriptor state, and register with
      87                 :         the reactor.
      88                 : 
      89                 :         @param fd The descriptor to adopt.
      90                 : 
      91                 :         @return The error if the reactor rejects the descriptor, in
      92                 :         which case the implementation is left closed and the caller
      93                 :         retains ownership of @a fd; otherwise a default constructed
      94                 :         error code.
      95                 :     */
      96            7648 :     std::error_code init_and_register(int fd) noexcept
      97                 :     {
      98            7648 :         fd_ = fd;
      99            7648 :         desc_state_.fd = fd;
     100                 :         {
     101            7648 :             std::lock_guard lock(desc_state_.mutex);
     102            7648 :             desc_state_.read_op    = nullptr;
     103            7648 :             desc_state_.write_op   = nullptr;
     104            7648 :             desc_state_.connect_op = nullptr;
     105            7648 :         }
     106            7648 :         if (auto ec = svc_.scheduler().register_descriptor(fd, &desc_state_))
     107                 :         {
     108                 :             // Undo the partial state so a failed adopt is
     109                 :             // indistinguishable from a closed implementation.
     110               1 :             fd_ = -1;
     111               1 :             desc_state_.fd = -1;
     112               1 :             desc_state_.registered_events = 0;
     113               1 :             return ec;
     114                 :         }
     115            7647 :         return {};
     116                 :     }
     117                 : 
     118                 :     /** Register an op with the reactor.
     119                 : 
     120                 :         Handles cached edge events and deferred cancellation.
     121                 :         Called on the EAGAIN/EINPROGRESS path when speculative
     122                 :         I/O failed.
     123                 :     */
     124                 :     template<class Op>
     125                 :     void register_op(
     126                 :         Op& op,
     127                 :         reactor_op_base*& desc_slot,
     128                 :         bool& ready_flag,
     129                 :         bool& cancel_flag,
     130                 :         bool is_write_direction = false) noexcept;
     131                 : 
     132                 :     /** Cancel a single pending operation.
     133                 : 
     134                 :         Claims the operation from its descriptor_state slot under
     135                 :         the mutex and posts it to the scheduler as cancelled.
     136                 :         Derived must implement:
     137                 :           op_to_desc_slot(Op&) -> reactor_op_base**
     138                 :           op_to_cancel_flag(Op&) -> bool*
     139                 :     */
     140                 :     template<class Op>
     141                 :     void cancel_single_op(Op& op) noexcept;
     142                 : 
     143                 :     /** Cancel all pending operations.
     144                 : 
     145                 :         Invoked by the derived class's cancel() override.
     146                 :         Derived must implement:
     147                 :           for_each_op(auto fn)
     148                 :           for_each_desc_entry(auto fn)
     149                 :     */
     150                 :     void do_cancel() noexcept;
     151                 : 
     152                 :     /** Close the socket and cancel pending operations.
     153                 : 
     154                 :         Invoked by the derived class's close_socket(). The
     155                 :         derived class may add backend-specific cleanup after
     156                 :         calling this method.
     157                 :         Derived must implement:
     158                 :           for_each_op(auto fn)
     159                 :           for_each_desc_entry(auto fn)
     160                 :     */
     161                 :     void do_close_socket() noexcept;
     162                 : 
     163                 :     /** Release the socket without closing the fd.
     164                 : 
     165                 :         Like do_close_socket() but does not call ::close().
     166                 :         Returns the fd so the caller can take ownership.
     167                 :     */
     168                 :     native_handle_type do_release_socket() noexcept;
     169                 : };
     170                 : 
     171                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     172                 : template<class Op>
     173                 : void
     174            8151 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::register_op(
     175                 :     Op& op,
     176                 :     reactor_op_base*& desc_slot,
     177                 :     bool& ready_flag,
     178                 :     bool& cancel_flag,
     179                 :     bool is_write_direction) noexcept
     180                 : {
     181            8151 :     svc_.work_started();
     182                 : 
     183            8151 :     std::lock_guard lock(desc_state_.mutex);
     184            8151 :     bool io_done = false;
     185            8151 :     if (ready_flag)
     186                 :     {
     187             268 :         ready_flag = false;
     188             268 :         op.perform_io();
     189             268 :         io_done = (op.errn != EAGAIN && op.errn != EWOULDBLOCK);
     190             268 :         if (!io_done)
     191             268 :             op.errn = 0;
     192                 :     }
     193                 : 
     194            8151 :     if (cancel_flag)
     195                 :     {
     196 MIS           0 :         cancel_flag = false;
     197               0 :         op.cancelled.store(true, std::memory_order_relaxed);
     198                 :     }
     199                 : 
     200 HIT        8151 :     if (io_done || op.cancelled.load(std::memory_order_acquire))
     201                 :     {
     202              26 :         svc_.post(&op);
     203              26 :         svc_.work_finished();
     204                 :     }
     205                 :     else
     206                 :     {
     207            8125 :         desc_slot = &op;
     208                 : 
     209                 :         // Select must rebuild its fd_sets when a write-direction op
     210                 :         // is parked, so select() watches for writability. Compiled
     211                 :         // away to nothing for epoll and kqueue.
     212                 :         if constexpr (requires { Service::needs_write_notification; })
     213                 :         {
     214                 :             if constexpr (Service::needs_write_notification)
     215                 :             {
     216            3841 :                 if (is_write_direction)
     217            3392 :                     svc_.scheduler().notify_reactor();
     218                 :             }
     219                 :         }
     220                 :     }
     221            8151 : }
     222                 : 
     223                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     224                 : template<class Op>
     225                 : void
     226             278 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::cancel_single_op(
     227                 :     Op& op) noexcept
     228                 : {
     229             278 :     auto self = this->weak_from_this().lock();
     230             278 :     if (!self)
     231 MIS           0 :         return;
     232                 : 
     233 HIT         278 :     op.request_cancel();
     234                 : 
     235             278 :     auto* d                       = static_cast<Derived*>(this);
     236             278 :     reactor_op_base** desc_op_ptr = d->op_to_desc_slot(op);
     237                 : 
     238             278 :     if (desc_op_ptr)
     239                 :     {
     240             278 :         reactor_op_base* claimed = nullptr;
     241                 :         {
     242             278 :             std::lock_guard lock(desc_state_.mutex);
     243             278 :             if (*desc_op_ptr == &op)
     244             234 :                 claimed = std::exchange(*desc_op_ptr, nullptr);
     245                 :             // Not in the slot: request_cancel() above already set
     246                 :             // op.cancelled, which register_op consults before parking
     247                 :             // and the completion decode consults on delivery. Latching
     248                 :             // a descriptor flag here instead would outlive this op and
     249                 :             // cancel the next wait in the same direction.
     250             278 :         }
     251             278 :         if (claimed)
     252                 :         {
     253             234 :             op.impl_ptr = self;
     254             234 :             svc_.post(&op);
     255             234 :             svc_.work_finished();
     256                 :         }
     257                 :     }
     258             278 : }
     259                 : 
     260                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     261                 : void
     262             233 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
     263                 :     do_cancel() noexcept
     264                 : {
     265             233 :     auto self = this->weak_from_this().lock();
     266             233 :     if (!self)
     267 MIS           0 :         return;
     268                 : 
     269 HIT         233 :     auto* d = static_cast<Derived*>(this);
     270                 : 
     271            1665 :     d->for_each_op([](auto& op) { op.request_cancel(); });
     272                 : 
     273                 :     // Claim ops under a single lock acquisition
     274                 :     struct claimed_entry
     275                 :     {
     276                 :         reactor_op_base* op   = nullptr;
     277                 :         reactor_op_base* base = nullptr;
     278                 :     };
     279                 :     // Max 8 ops: conn, rd, wr, wait_rd, wait_wr, wait_er, recv_rd, send_wr
     280             233 :     claimed_entry claimed[8];
     281             233 :     int count = 0;
     282                 : 
     283                 :     {
     284             233 :         std::lock_guard lock(desc_state_.mutex);
     285            3097 :         d->for_each_desc_entry([&](auto& op, reactor_op_base*& desc_slot) {
     286            1432 :             if (desc_slot == &op)
     287                 :             {
     288             137 :                 claimed[count].op   = std::exchange(desc_slot, nullptr);
     289             137 :                 claimed[count].base = &op;
     290             137 :                 ++count;
     291                 :             }
     292                 :         });
     293             233 :     }
     294                 : 
     295             370 :     for (int i = 0; i < count; ++i)
     296                 :     {
     297             137 :         claimed[i].base->impl_ptr = self;
     298             137 :         svc_.post(claimed[i].base);
     299             137 :         svc_.work_finished();
     300                 :     }
     301             233 : }
     302                 : 
     303                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     304                 : void
     305           66252 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
     306                 :     do_close_socket() noexcept
     307                 : {
     308           66252 :     auto self = this->weak_from_this().lock();
     309           66252 :     if (self)
     310                 :     {
     311           66252 :         auto* d = static_cast<Derived*>(this);
     312                 : 
     313          467236 :         d->for_each_op([](auto& op) { op.request_cancel(); });
     314                 : 
     315                 :         struct claimed_entry
     316                 :         {
     317                 :             reactor_op_base* base = nullptr;
     318                 :         };
     319           66252 :         claimed_entry claimed[8];
     320           66252 :         int count = 0;
     321                 : 
     322                 :         {
     323           66252 :             std::lock_guard lock(desc_state_.mutex);
     324           66252 :             d->for_each_desc_entry(
     325          801968 :                 [&](auto& /*op*/, reactor_op_base*& desc_slot) {
     326          400984 :                     auto* c = std::exchange(desc_slot, nullptr);
     327          400984 :                     if (c)
     328                 :                     {
     329              28 :                         claimed[count].base = c;
     330              28 :                         ++count;
     331                 :                     }
     332                 :                 });
     333           66252 :             desc_state_.read_ready             = false;
     334           66252 :             desc_state_.write_ready            = false;
     335           66252 :             desc_state_.read_cancel_pending       = false;
     336           66252 :             desc_state_.write_cancel_pending      = false;
     337           66252 :             desc_state_.connect_cancel_pending    = false;
     338           66252 :             desc_state_.wait_read_cancel_pending  = false;
     339           66252 :             desc_state_.wait_write_cancel_pending = false;
     340           66252 :             desc_state_.wait_error_cancel_pending = false;
     341                 : 
     342           66252 :             if (desc_state_.is_enqueued_.load(std::memory_order_acquire))
     343             694 :                 desc_state_.impl_ref_ = self;
     344           66252 :         }
     345                 : 
     346           66280 :         for (int i = 0; i < count; ++i)
     347                 :         {
     348              28 :             claimed[i].base->impl_ptr = self;
     349              28 :             svc_.post(claimed[i].base);
     350              28 :             svc_.work_finished();
     351                 :         }
     352                 :     }
     353                 : 
     354           66252 :     if (fd_ >= 0)
     355                 :     {
     356           14662 :         if (desc_state_.registered_events != 0)
     357           14662 :             svc_.scheduler().deregister_descriptor(fd_);
     358           14662 :         ::close(fd_);
     359           14662 :         fd_ = -1;
     360                 :     }
     361                 : 
     362           66252 :     desc_state_.fd                = -1;
     363           66252 :     desc_state_.registered_events = 0;
     364                 : 
     365           66252 :     local_endpoint_ = Endpoint{};
     366           66252 : }
     367                 : 
     368                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     369                 : native_handle_type
     370              10 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
     371                 :     do_release_socket() noexcept
     372                 : {
     373                 :     // Cancel pending ops (same as do_close_socket)
     374              10 :     auto self = this->weak_from_this().lock();
     375              10 :     if (self)
     376                 :     {
     377              10 :         auto* d = static_cast<Derived*>(this);
     378                 : 
     379              78 :         d->for_each_op([](auto& op) { op.request_cancel(); });
     380                 : 
     381                 :         struct claimed_entry
     382                 :         {
     383                 :             reactor_op_base* base = nullptr;
     384                 :         };
     385              10 :         claimed_entry claimed[8];
     386              10 :         int count = 0;
     387                 : 
     388                 :         {
     389              10 :             std::lock_guard lock(desc_state_.mutex);
     390              10 :             d->for_each_desc_entry(
     391             136 :                 [&](auto& /*op*/, reactor_op_base*& desc_slot) {
     392              68 :                     auto* c = std::exchange(desc_slot, nullptr);
     393              68 :                     if (c)
     394                 :                     {
     395               6 :                         claimed[count].base = c;
     396               6 :                         ++count;
     397                 :                     }
     398                 :                 });
     399              10 :             desc_state_.read_ready             = false;
     400              10 :             desc_state_.write_ready            = false;
     401              10 :             desc_state_.read_cancel_pending       = false;
     402              10 :             desc_state_.write_cancel_pending      = false;
     403              10 :             desc_state_.connect_cancel_pending    = false;
     404              10 :             desc_state_.wait_read_cancel_pending  = false;
     405              10 :             desc_state_.wait_write_cancel_pending = false;
     406              10 :             desc_state_.wait_error_cancel_pending = false;
     407                 : 
     408              10 :             if (desc_state_.is_enqueued_.load(std::memory_order_acquire))
     409               3 :                 desc_state_.impl_ref_ = self;
     410              10 :         }
     411                 : 
     412              16 :         for (int i = 0; i < count; ++i)
     413                 :         {
     414               6 :             claimed[i].base->impl_ptr = self;
     415               6 :             svc_.post(claimed[i].base);
     416               6 :             svc_.work_finished();
     417                 :         }
     418                 :     }
     419                 : 
     420              10 :     native_handle_type released = fd_;
     421                 : 
     422              10 :     if (fd_ >= 0)
     423                 :     {
     424              10 :         if (desc_state_.registered_events != 0)
     425              10 :             svc_.scheduler().deregister_descriptor(fd_);
     426                 :         // Do NOT close -- caller takes ownership
     427              10 :         fd_ = -1;
     428                 :     }
     429                 : 
     430              10 :     desc_state_.fd                = -1;
     431              10 :     desc_state_.registered_events = 0;
     432                 : 
     433              10 :     local_endpoint_ = Endpoint{};
     434                 : 
     435              20 :     return released;
     436              10 : }
     437                 : 
     438                 : } // namespace boost::corosio::detail
     439                 : 
     440                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_BASIC_SOCKET_HPP
        

Generated by: LCOV version 2.3