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

Generated by: LCOV version 2.3