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: 98.5 % 136 134 2
Test Date: 2026-09-06 03:35:56 Functions: 97.1 % 340 330 10

           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       20873 :     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           20873 :     ~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            7410 :     std::error_code init_and_register(int fd) noexcept
      97                 :     {
      98            7410 :         fd_ = fd;
      99            7410 :         desc_state_.fd = fd;
     100                 :         {
     101            7410 :             std::lock_guard lock(desc_state_.mutex);
     102            7410 :             desc_state_.read_op    = nullptr;
     103            7410 :             desc_state_.write_op   = nullptr;
     104            7410 :             desc_state_.connect_op = nullptr;
     105            7410 :         }
     106            7410 :         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               3 :             fd_ = -1;
     111               3 :             desc_state_.fd = -1;
     112               3 :             desc_state_.registered_events = 0;
     113               3 :             return ec;
     114                 :         }
     115            7407 :         return {};
     116                 :     }
     117                 : 
     118                 :     /** Register an op with the reactor.
     119                 : 
     120                 :         Handles cached edge events. Called on the EAGAIN/EINPROGRESS
     121                 :         path when speculative I/O failed.
     122                 :     */
     123                 :     template<class Op>
     124                 :     void register_op(
     125                 :         Op& op,
     126                 :         reactor_op_base*& desc_slot,
     127                 :         bool& ready_flag,
     128                 :         bool is_write_direction = false) noexcept;
     129                 : 
     130                 :     /** Cancel a single pending operation.
     131                 : 
     132                 :         Claims the operation from its descriptor_state slot under
     133                 :         the mutex and posts it to the scheduler as cancelled.
     134                 :         Derived must implement:
     135                 :           op_to_desc_slot(Op&) -> reactor_op_base**
     136                 :     */
     137                 :     template<class Op>
     138                 :     void cancel_single_op(Op& op) noexcept;
     139                 : 
     140                 :     /** Cancel all pending operations.
     141                 : 
     142                 :         Invoked by the derived class's cancel() override.
     143                 :         Derived must implement:
     144                 :           for_each_op(auto fn)
     145                 :           for_each_desc_entry(auto fn)
     146                 :     */
     147                 :     void do_cancel() noexcept;
     148                 : 
     149                 :     /** Close the socket and cancel pending operations.
     150                 : 
     151                 :         Invoked by the derived class's close_socket(). The
     152                 :         derived class may add backend-specific cleanup after
     153                 :         calling this method.
     154                 :         Derived must implement:
     155                 :           for_each_op(auto fn)
     156                 :           for_each_desc_entry(auto fn)
     157                 :     */
     158                 :     void do_close_socket() noexcept;
     159                 : 
     160                 :     /** Release the socket without closing the fd.
     161                 : 
     162                 :         Like do_close_socket() but does not call ::close().
     163                 :         Returns the fd so the caller can take ownership.
     164                 :     */
     165                 :     native_handle_type do_release_socket() noexcept;
     166                 : };
     167                 : 
     168                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     169                 : template<class Op>
     170                 : void
     171            7832 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::register_op(
     172                 :     Op& op,
     173                 :     reactor_op_base*& desc_slot,
     174                 :     bool& ready_flag,
     175                 :     bool is_write_direction) noexcept
     176                 : {
     177            7832 :     svc_.work_started();
     178                 : 
     179            7832 :     std::lock_guard lock(desc_state_.mutex);
     180            7832 :     bool io_done = false;
     181            7832 :     if (ready_flag)
     182                 :     {
     183             301 :         ready_flag = false;
     184             301 :         op.perform_io();
     185             301 :         io_done = (op.errn != EAGAIN && op.errn != EWOULDBLOCK);
     186             301 :         if (!io_done)
     187             295 :             op.errn = 0;
     188                 :     }
     189                 : 
     190                 : 
     191            7832 :     if (io_done || op.cancelled.load(std::memory_order_acquire))
     192                 :     {
     193              52 :         svc_.post(&op);
     194              52 :         svc_.work_finished();
     195                 :     }
     196                 :     else
     197                 :     {
     198            7780 :         desc_slot = &op;
     199                 : 
     200                 :         // Select must rebuild its fd_sets when a write-direction op
     201                 :         // is parked, so select() watches for writability. Compiled
     202                 :         // away to nothing for epoll and kqueue.
     203                 :         if constexpr (requires { Service::needs_write_notification; })
     204                 :         {
     205                 :             if constexpr (Service::needs_write_notification)
     206                 :             {
     207            2859 :                 if (is_write_direction)
     208            2351 :                     svc_.scheduler().notify_reactor();
     209                 :             }
     210                 :         }
     211                 :     }
     212            7832 : }
     213                 : 
     214                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     215                 : template<class Op>
     216                 : void
     217             297 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::cancel_single_op(
     218                 :     Op& op) noexcept
     219                 : {
     220             297 :     auto self = this->weak_from_this().lock();
     221             297 :     if (!self)
     222 MIS           0 :         return;
     223                 : 
     224 HIT         297 :     op.request_cancel();
     225                 : 
     226             297 :     auto* d                       = static_cast<Derived*>(this);
     227             297 :     reactor_op_base** desc_op_ptr = d->op_to_desc_slot(op);
     228                 : 
     229             297 :     if (desc_op_ptr)
     230                 :     {
     231             297 :         reactor_op_base* claimed = nullptr;
     232                 :         {
     233             297 :             std::lock_guard lock(desc_state_.mutex);
     234             297 :             if (*desc_op_ptr == &op)
     235             227 :                 claimed = std::exchange(*desc_op_ptr, nullptr);
     236                 :             // Not in the slot: request_cancel() above already set
     237                 :             // op.cancelled, which register_op consults before parking
     238                 :             // and the completion decode consults on delivery. Latching
     239                 :             // a descriptor flag here instead would outlive this op and
     240                 :             // cancel the next wait in the same direction.
     241             297 :         }
     242             297 :         if (claimed)
     243                 :         {
     244             227 :             op.impl_ptr = self;
     245             227 :             svc_.post(&op);
     246             227 :             svc_.work_finished();
     247                 :         }
     248                 :     }
     249             297 : }
     250                 : 
     251                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     252                 : void
     253             277 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
     254                 :     do_cancel() noexcept
     255                 : {
     256             277 :     auto self = this->weak_from_this().lock();
     257             277 :     if (!self)
     258 MIS           0 :         return;
     259                 : 
     260 HIT         277 :     auto* d = static_cast<Derived*>(this);
     261                 : 
     262            2055 :     d->for_each_op([](auto& op) { op.request_cancel(); });
     263                 : 
     264                 :     // Claim ops under a single lock acquisition
     265                 :     struct claimed_entry
     266                 :     {
     267                 :         reactor_op_base* op   = nullptr;
     268                 :         reactor_op_base* base = nullptr;
     269                 :     };
     270                 :     // Max 8 ops: conn, rd, wr, wait_rd, wait_wr, wait_er, recv_rd, send_wr
     271             277 :     claimed_entry claimed[8];
     272             277 :     int count = 0;
     273                 : 
     274                 :     {
     275             277 :         std::lock_guard lock(desc_state_.mutex);
     276            3833 :         d->for_each_desc_entry([&](auto& op, reactor_op_base*& desc_slot) {
     277            1778 :             if (desc_slot == &op)
     278                 :             {
     279             188 :                 claimed[count].op   = std::exchange(desc_slot, nullptr);
     280             188 :                 claimed[count].base = &op;
     281             188 :                 ++count;
     282                 :             }
     283                 :         });
     284             277 :     }
     285                 : 
     286             465 :     for (int i = 0; i < count; ++i)
     287                 :     {
     288             188 :         claimed[i].base->impl_ptr = self;
     289             188 :         svc_.post(claimed[i].base);
     290             188 :         svc_.work_finished();
     291                 :     }
     292             277 : }
     293                 : 
     294                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     295                 : void
     296           63099 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
     297                 :     do_close_socket() noexcept
     298                 : {
     299           63099 :     auto self = this->weak_from_this().lock();
     300           63099 :     if (self)
     301                 :     {
     302           63099 :         auto* d = static_cast<Derived*>(this);
     303                 : 
     304          446323 :         d->for_each_op([](auto& op) { op.request_cancel(); });
     305                 : 
     306                 :         struct claimed_entry
     307                 :         {
     308                 :             reactor_op_base* base = nullptr;
     309                 :         };
     310           63099 :         claimed_entry claimed[8];
     311           63099 :         int count = 0;
     312                 : 
     313                 :         {
     314           63099 :             std::lock_guard lock(desc_state_.mutex);
     315           63099 :             d->for_each_desc_entry(
     316          766448 :                 [&](auto& /*op*/, reactor_op_base*& desc_slot) {
     317          383224 :                     auto* c = std::exchange(desc_slot, nullptr);
     318          383224 :                     if (c)
     319                 :                     {
     320              56 :                         claimed[count].base = c;
     321              56 :                         ++count;
     322                 :                     }
     323                 :                 });
     324           63099 :             desc_state_.read_ready             = false;
     325           63099 :             desc_state_.write_ready            = false;
     326                 : 
     327           63099 :             if (desc_state_.is_enqueued_.load(std::memory_order_acquire))
     328             958 :                 desc_state_.impl_ref_ = self;
     329           63099 :         }
     330                 : 
     331           63155 :         for (int i = 0; i < count; ++i)
     332                 :         {
     333              56 :             claimed[i].base->impl_ptr = self;
     334              56 :             svc_.post(claimed[i].base);
     335              56 :             svc_.work_finished();
     336                 :         }
     337                 :     }
     338                 : 
     339           63099 :     if (fd_ >= 0)
     340                 :     {
     341           13950 :         if (desc_state_.registered_events != 0)
     342           13948 :             svc_.scheduler().deregister_descriptor(fd_);
     343           13950 :         ::close(fd_);
     344           13950 :         fd_ = -1;
     345                 :     }
     346                 : 
     347           63099 :     desc_state_.fd                = -1;
     348           63099 :     desc_state_.registered_events = 0;
     349                 : 
     350           63099 :     local_endpoint_ = Endpoint{};
     351           63099 : }
     352                 : 
     353                 : template<class Derived, class ImplBase, class Service, class DescState, class Endpoint>
     354                 : native_handle_type
     355              16 : reactor_basic_socket<Derived, ImplBase, Service, DescState, Endpoint>::
     356                 :     do_release_socket() noexcept
     357                 : {
     358                 :     // Cancel pending ops (same as do_close_socket)
     359              16 :     auto self = this->weak_from_this().lock();
     360              16 :     if (self)
     361                 :     {
     362              16 :         auto* d = static_cast<Derived*>(this);
     363                 : 
     364             128 :         d->for_each_op([](auto& op) { op.request_cancel(); });
     365                 : 
     366                 :         struct claimed_entry
     367                 :         {
     368                 :             reactor_op_base* base = nullptr;
     369                 :         };
     370              16 :         claimed_entry claimed[8];
     371              16 :         int count = 0;
     372                 : 
     373                 :         {
     374              16 :             std::lock_guard lock(desc_state_.mutex);
     375              16 :             d->for_each_desc_entry(
     376             224 :                 [&](auto& /*op*/, reactor_op_base*& desc_slot) {
     377             112 :                     auto* c = std::exchange(desc_slot, nullptr);
     378             112 :                     if (c)
     379                 :                     {
     380              12 :                         claimed[count].base = c;
     381              12 :                         ++count;
     382                 :                     }
     383                 :                 });
     384              16 :             desc_state_.read_ready             = false;
     385              16 :             desc_state_.write_ready            = false;
     386                 : 
     387              16 :             if (desc_state_.is_enqueued_.load(std::memory_order_acquire))
     388               3 :                 desc_state_.impl_ref_ = self;
     389              16 :         }
     390                 : 
     391              28 :         for (int i = 0; i < count; ++i)
     392                 :         {
     393              12 :             claimed[i].base->impl_ptr = self;
     394              12 :             svc_.post(claimed[i].base);
     395              12 :             svc_.work_finished();
     396                 :         }
     397                 :     }
     398                 : 
     399              16 :     native_handle_type released = fd_;
     400                 : 
     401              16 :     if (fd_ >= 0)
     402                 :     {
     403              16 :         if (desc_state_.registered_events != 0)
     404              16 :             svc_.scheduler().deregister_descriptor(fd_);
     405                 :         // Do NOT close -- caller takes ownership
     406              16 :         fd_ = -1;
     407                 :     }
     408                 : 
     409              16 :     desc_state_.fd                = -1;
     410              16 :     desc_state_.registered_events = 0;
     411                 : 
     412              16 :     local_endpoint_ = Endpoint{};
     413                 : 
     414              32 :     return released;
     415              16 : }
     416                 : 
     417                 : } // namespace boost::corosio::detail
     418                 : 
     419                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_BASIC_SOCKET_HPP
        

Generated by: LCOV version 2.3