LCOV - code coverage report
Current view: top level - corosio/native/detail/reactor - reactor_op.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 93.3 % 165 154 11
Test Date: 2026-07-17 17:14:45 Functions: 78.8 % 160 126 34

           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_OP_HPP
      11                 : #define BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_OP_HPP
      12                 : 
      13                 : #include <boost/corosio/native/detail/reactor/reactor_op_base.hpp>
      14                 : #include <boost/corosio/io/io_object.hpp>
      15                 : #include <boost/corosio/endpoint.hpp>
      16                 : #include <boost/capy/ex/executor_ref.hpp>
      17                 : 
      18                 : #include <atomic>
      19                 : #include <cstddef>
      20                 : #include <optional>
      21                 : #include <stop_token>
      22                 : 
      23                 : #include <errno.h>
      24                 : 
      25                 : #include <netinet/in.h>
      26                 : #include <sys/socket.h>
      27                 : #include <sys/uio.h>
      28                 : 
      29                 : namespace boost::corosio::detail {
      30                 : 
      31                 : /** Base operation for reactor-based backends.
      32                 : 
      33                 :     Holds per-operation state that depends on the concrete backend
      34                 :     socket/acceptor types: coroutine handle, executor, output
      35                 :     pointers, file descriptor, stop_callback, and type-specific
      36                 :     impl pointers.
      37                 : 
      38                 :     Fields shared across all backends (errn, bytes_transferred,
      39                 :     cancelled, impl_ptr, perform_io, complete) live in
      40                 :     reactor_op_base so the scheduler and descriptor_state can
      41                 :     access them without template instantiation.
      42                 : 
      43                 :     @tparam Socket The backend socket impl type (forward-declared).
      44                 :     @tparam Acceptor The backend acceptor impl type (forward-declared).
      45                 : */
      46                 : template<class Socket, class Acceptor>
      47                 : struct reactor_op : reactor_op_base
      48                 : {
      49                 :     // The op envelope — coroutine handle h, cont, executor ex, ec_out,
      50                 :     // bytes_out, cancelled, stop_cb (+ its canceller), impl_ptr — lives in
      51                 :     // coro_op (via reactor_op_base) and is shared with io_uring/IOCP.
      52                 :     // reactor_op adds only the reactor-specific routing state below.
      53                 : 
      54                 :     /// File descriptor this operation targets.
      55                 :     int fd = -1;
      56                 : 
      57                 :     /// Owning socket impl (for stop_token cancellation routing).
      58                 :     Socket* socket_impl_ = nullptr;
      59                 : 
      60                 :     /// Owning acceptor impl (for stop_token cancellation routing).
      61                 :     Acceptor* acceptor_impl_ = nullptr;
      62                 : 
      63 HIT      110278 :     reactor_op() = default;
      64                 : 
      65                 :     /// Reset operation state for reuse.
      66          431498 :     void reset() noexcept
      67                 :     {
      68          431498 :         fd                = -1;
      69          431498 :         errn              = 0;
      70          431498 :         bytes_transferred = 0;
      71          431498 :         cancelled.store(false, std::memory_order_relaxed);
      72          431498 :         impl_ptr.reset();
      73          431498 :         socket_impl_   = nullptr;
      74          431498 :         acceptor_impl_ = nullptr;
      75          431498 :     }
      76                 : 
      77                 :     /// Return true if this is a read-direction operation.
      78           42119 :     virtual bool is_read_operation() const noexcept
      79                 :     {
      80           42119 :         return false;
      81                 :     }
      82                 : 
      83                 :     /// Cancel this operation via the owning impl.
      84                 :     virtual void cancel() noexcept = 0;
      85                 : 
      86                 :     /// coro_op cancellation hook (fired by the shared canceller when the
      87                 :     /// stop_token requests cancellation): route to the impl-specific cancel().
      88             289 :     void on_cancel() noexcept override
      89                 :     {
      90             289 :         cancel();
      91             289 :     }
      92                 : 
      93                 :     /// Destroy without invoking.
      94              12 :     void destroy() override
      95                 :     {
      96              12 :         stop_cb.reset();
      97              12 :         reactor_op_base::destroy();
      98              12 :     }
      99                 : 
     100                 :     /// Arm the stop-token callback for a socket operation.
     101           90637 :     void start(std::stop_token const& token, Socket* impl)
     102                 :     {
     103           90637 :         socket_impl_   = impl;
     104           90637 :         acceptor_impl_ = nullptr;
     105           90637 :         coro_op::start(token);
     106           90637 :     }
     107                 : 
     108                 :     /// Arm the stop-token callback for an acceptor operation.
     109            5821 :     void start(std::stop_token const& token, Acceptor* impl)
     110                 :     {
     111            5821 :         socket_impl_   = nullptr;
     112            5821 :         acceptor_impl_ = impl;
     113            5821 :         coro_op::start(token);
     114            5821 :     }
     115                 : };
     116                 : 
     117                 : /** Shared connect operation.
     118                 : 
     119                 :     Checks SO_ERROR for connect completion status. The operator()()
     120                 :     and cancel() are provided by the concrete backend type.
     121                 : 
     122                 :     @tparam Base The backend's base op type.
     123                 :     @tparam Endpoint The endpoint type (endpoint or local_endpoint).
     124                 : */
     125                 : template<class Base, class Endpoint = endpoint>
     126                 : struct reactor_connect_op : Base
     127                 : {
     128                 :     /// Endpoint to connect to.
     129                 :     Endpoint target_endpoint;
     130                 : 
     131                 :     /// Reset operation state for reuse.
     132            5825 :     void reset() noexcept
     133                 :     {
     134            5825 :         Base::reset();
     135            5825 :         target_endpoint = Endpoint{};
     136            5825 :     }
     137                 : 
     138            5760 :     void perform_io() noexcept override
     139                 :     {
     140            5760 :         int err       = 0;
     141            5760 :         socklen_t len = sizeof(err);
     142            5760 :         if (::getsockopt(this->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
     143 MIS           0 :             err = errno;
     144 HIT        5760 :         this->complete(err, 0);
     145            5760 :     }
     146                 : };
     147                 : 
     148                 : /** Readiness-only wait operation.
     149                 : 
     150                 :     Does not perform any I/O syscall. Completion is signalled by
     151                 :     the reactor delivering the requested edge event; reactor_descriptor_state
     152                 :     calls complete() directly and never invokes perform_io().
     153                 : 
     154                 :     @tparam Base The backend's base op type.
     155                 : */
     156                 : template<class Base>
     157                 : struct reactor_wait_op : Base
     158                 : {
     159                 :     /* Mirror of reactor_event_read from reactor_descriptor_state.hpp.
     160                 :        Including that header from here would create an include cycle
     161                 :        (descriptor_state -> reactor_op_base; reactor_op -> reactor_op_base),
     162                 :        so we carry the value locally. Both must stay in sync. */
     163                 :     static constexpr std::uint32_t read_event = 0x001;
     164                 : 
     165                 :     /// Which event bit this wait targets (reactor_event_read/write/error).
     166                 :     std::uint32_t wait_event = 0;
     167                 : 
     168              72 :     void reset() noexcept
     169                 :     {
     170              72 :         Base::reset();
     171              72 :         wait_event = 0;
     172              72 :     }
     173                 : 
     174 MIS           0 :     bool is_read_operation() const noexcept override
     175                 :     {
     176               0 :         return wait_event == read_event;
     177                 :     }
     178                 : 
     179                 :     /* perform_io() should never be called for a wait op — readiness
     180                 :        IS the completion. Overridden here to satisfy the virtual and
     181                 :        produce a safe result if called defensively. */
     182               0 :     void perform_io() noexcept override
     183                 :     {
     184               0 :         this->complete(0, 0);
     185               0 :     }
     186                 : };
     187                 : 
     188                 : /** Shared scatter-read operation.
     189                 : 
     190                 :     Uses readv() with an EINTR retry loop.
     191                 : 
     192                 :     @tparam Base The backend's base op type.
     193                 : */
     194                 : template<class Base>
     195                 : struct reactor_read_op : Base
     196                 : {
     197                 :     /// Maximum scatter-gather buffer count.
     198                 :     static constexpr std::size_t max_buffers = 16;
     199                 : 
     200                 :     /// Scatter-gather I/O vectors.
     201                 :     iovec iovecs[max_buffers];
     202                 : 
     203                 :     /// Number of active I/O vectors.
     204                 :     int iovec_count = 0;
     205                 : 
     206                 :     /// True for zero-length reads (completed immediately).
     207                 :     bool empty_buffer_read = false;
     208                 : 
     209                 :     /// Return true (this is a read-direction operation).
     210 HIT       42501 :     bool is_read_operation() const noexcept override
     211                 :     {
     212           42501 :         return !empty_buffer_read;
     213                 :     }
     214                 : 
     215          209965 :     void reset() noexcept
     216                 :     {
     217          209965 :         Base::reset();
     218          209965 :         iovec_count       = 0;
     219          209965 :         empty_buffer_read = false;
     220          209965 :     }
     221                 : 
     222             640 :     void perform_io() noexcept override
     223                 :     {
     224                 :         ssize_t n;
     225                 :         do
     226                 :         {
     227             640 :             n = ::readv(this->fd, iovecs, iovec_count);
     228                 :         }
     229             640 :         while (n < 0 && errno == EINTR);
     230                 : 
     231             640 :         if (n >= 0)
     232             404 :             this->complete(0, static_cast<std::size_t>(n));
     233                 :         else
     234             236 :             this->complete(errno, 0);
     235             640 :     }
     236                 : };
     237                 : 
     238                 : /** Shared gather-write operation.
     239                 : 
     240                 :     Delegates the actual syscall to WritePolicy::write(fd, iovecs, count),
     241                 :     which returns ssize_t (bytes written or -1 with errno set).
     242                 : 
     243                 :     @tparam Base The backend's base op type.
     244                 :     @tparam WritePolicy Provides `static ssize_t write(int, iovec*, int)`.
     245                 : */
     246                 : template<class Base, class WritePolicy>
     247                 : struct reactor_write_op : Base
     248                 : {
     249                 :     /// The write syscall policy type.
     250                 :     using write_policy = WritePolicy;
     251                 : 
     252                 :     /// Maximum scatter-gather buffer count.
     253                 :     static constexpr std::size_t max_buffers = 16;
     254                 : 
     255                 :     /// Scatter-gather I/O vectors.
     256                 :     iovec iovecs[max_buffers];
     257                 : 
     258                 :     /// Number of active I/O vectors.
     259                 :     int iovec_count = 0;
     260                 : 
     261          209361 :     void reset() noexcept
     262                 :     {
     263          209361 :         Base::reset();
     264          209361 :         iovec_count = 0;
     265          209361 :     }
     266                 : 
     267             132 :     void perform_io() noexcept override
     268                 :     {
     269             132 :         ssize_t n = WritePolicy::write(this->fd, iovecs, iovec_count);
     270             132 :         if (n >= 0)
     271             129 :             this->complete(0, static_cast<std::size_t>(n));
     272                 :         else
     273               3 :             this->complete(errno, 0);
     274             132 :     }
     275                 : };
     276                 : 
     277                 : /** Shared accept operation.
     278                 : 
     279                 :     Delegates the actual syscall to AcceptPolicy::do_accept(fd, peer_storage),
     280                 :     which returns the accepted fd or -1 with errno set.
     281                 : 
     282                 :     @tparam Base The backend's base op type.
     283                 :     @tparam AcceptPolicy Provides `static int do_accept(int, sockaddr_storage&)`.
     284                 : */
     285                 : template<class Base, class AcceptPolicy>
     286                 : struct reactor_accept_op : Base
     287                 : {
     288                 :     /// File descriptor of the accepted connection.
     289                 :     int accepted_fd = -1;
     290                 : 
     291                 :     /// Pointer to the peer socket implementation.
     292                 :     io_object::implementation* peer_impl = nullptr;
     293                 : 
     294                 :     /// Output pointer for the accepted implementation.
     295                 :     io_object::implementation** impl_out = nullptr;
     296                 : 
     297                 :     /// Peer address storage filled by accept.
     298                 :     sockaddr_storage peer_storage{};
     299                 : 
     300                 :     /// Peer address length returned by accept.
     301                 :     socklen_t peer_addrlen = 0;
     302                 : 
     303            5809 :     void reset() noexcept
     304                 :     {
     305            5809 :         Base::reset();
     306            5809 :         accepted_fd   = -1;
     307            5809 :         peer_impl     = nullptr;
     308            5809 :         impl_out      = nullptr;
     309            5809 :         peer_storage  = {};
     310            5809 :         peer_addrlen  = 0;
     311            5809 :     }
     312                 : 
     313            5748 :     void perform_io() noexcept override
     314                 :     {
     315            5748 :         int new_fd = AcceptPolicy::do_accept(
     316            5748 :             this->fd, peer_storage, peer_addrlen);
     317            5748 :         if (new_fd >= 0)
     318                 :         {
     319            5748 :             accepted_fd = new_fd;
     320            5748 :             this->complete(0, 0);
     321                 :         }
     322                 :         else
     323                 :         {
     324 MIS           0 :             this->complete(errno, 0);
     325                 :         }
     326 HIT        5748 :     }
     327                 : };
     328                 : 
     329                 : /** Shared connected send operation for datagram sockets.
     330                 : 
     331                 :     Uses sendmsg() with msg_name=nullptr (connected mode).
     332                 : 
     333                 :     @tparam Base The backend's base op type.
     334                 : */
     335                 : template<class Base>
     336                 : struct reactor_send_op : Base
     337                 : {
     338                 :     /// Maximum scatter-gather buffer count.
     339                 :     static constexpr std::size_t max_buffers = 16;
     340                 : 
     341                 :     /// Scatter-gather I/O vectors.
     342                 :     iovec iovecs[max_buffers];
     343                 : 
     344                 :     /// Number of active I/O vectors.
     345                 :     int iovec_count = 0;
     346                 : 
     347                 :     /// User-supplied message flags.
     348                 :     int msg_flags = 0;
     349                 : 
     350             106 :     void reset() noexcept
     351                 :     {
     352             106 :         Base::reset();
     353             106 :         iovec_count = 0;
     354             106 :         msg_flags   = 0;
     355             106 :     }
     356                 : 
     357              32 :     void perform_io() noexcept override
     358                 :     {
     359              32 :         msghdr msg{};
     360              32 :         msg.msg_iov    = iovecs;
     361              32 :         msg.msg_iovlen = static_cast<std::size_t>(iovec_count);
     362                 : 
     363                 : #ifdef MSG_NOSIGNAL
     364              32 :         int send_flags = msg_flags | MSG_NOSIGNAL;
     365                 : #else
     366                 :         int send_flags = msg_flags;
     367                 : #endif
     368                 : 
     369                 :         ssize_t n;
     370                 :         do
     371                 :         {
     372              32 :             n = ::sendmsg(this->fd, &msg, send_flags);
     373                 :         }
     374              32 :         while (n < 0 && errno == EINTR);
     375                 : 
     376              32 :         if (n >= 0)
     377              30 :             this->complete(0, static_cast<std::size_t>(n));
     378                 :         else
     379               2 :             this->complete(errno, 0);
     380              32 :     }
     381                 : };
     382                 : 
     383                 : /** Shared connected recv operation for datagram sockets.
     384                 : 
     385                 :     Uses recvmsg() with msg_name=nullptr (connected mode).
     386                 :     Unlike reactor_read_op, does not map n==0 to EOF
     387                 :     (zero-length datagrams are valid).
     388                 : 
     389                 :     @tparam Base The backend's base op type.
     390                 : */
     391                 : template<class Base>
     392                 : struct reactor_recv_op : Base
     393                 : {
     394                 :     /// Maximum scatter-gather buffer count.
     395                 :     static constexpr std::size_t max_buffers = 16;
     396                 : 
     397                 :     /// Scatter-gather I/O vectors.
     398                 :     iovec iovecs[max_buffers];
     399                 : 
     400                 :     /// Number of active I/O vectors.
     401                 :     int iovec_count = 0;
     402                 : 
     403                 :     /// User-supplied message flags.
     404                 :     int msg_flags = 0;
     405                 : 
     406                 :     /// Return true (this is a read-direction operation).
     407 MIS           0 :     bool is_read_operation() const noexcept override
     408                 :     {
     409               0 :         return true;
     410                 :     }
     411                 : 
     412 HIT         104 :     void reset() noexcept
     413                 :     {
     414             104 :         Base::reset();
     415             104 :         iovec_count = 0;
     416             104 :         msg_flags   = 0;
     417             104 :     }
     418                 : 
     419              37 :     void perform_io() noexcept override
     420                 :     {
     421              37 :         msghdr msg{};
     422              37 :         msg.msg_iov    = iovecs;
     423              37 :         msg.msg_iovlen = static_cast<std::size_t>(iovec_count);
     424                 : 
     425                 :         ssize_t n;
     426                 :         do
     427                 :         {
     428              37 :             n = ::recvmsg(this->fd, &msg, msg_flags);
     429                 :         }
     430              37 :         while (n < 0 && errno == EINTR);
     431                 : 
     432              37 :         if (n >= 0)
     433              34 :             this->complete(0, static_cast<std::size_t>(n));
     434                 :         else
     435               3 :             this->complete(errno, 0);
     436              37 :     }
     437                 : };
     438                 : 
     439                 : /** Shared send_to operation for datagram sockets.
     440                 : 
     441                 :     Uses sendmsg() with the destination endpoint in msg_name.
     442                 : 
     443                 :     @tparam Base The backend's base op type.
     444                 : */
     445                 : template<class Base>
     446                 : struct reactor_send_to_op : Base
     447                 : {
     448                 :     /// Maximum scatter-gather buffer count.
     449                 :     static constexpr std::size_t max_buffers = 16;
     450                 : 
     451                 :     /// Scatter-gather I/O vectors.
     452                 :     iovec iovecs[max_buffers];
     453                 : 
     454                 :     /// Number of active I/O vectors.
     455                 :     int iovec_count = 0;
     456                 : 
     457                 :     /// Destination address storage.
     458                 :     sockaddr_storage dest_storage{};
     459                 : 
     460                 :     /// Destination address length.
     461                 :     socklen_t dest_len = 0;
     462                 : 
     463                 :     /// User-supplied message flags.
     464                 :     int msg_flags = 0;
     465                 : 
     466             122 :     void reset() noexcept
     467                 :     {
     468             122 :         Base::reset();
     469             122 :         iovec_count  = 0;
     470             122 :         dest_storage = {};
     471             122 :         dest_len     = 0;
     472             122 :         msg_flags    = 0;
     473             122 :     }
     474                 : 
     475              32 :     void perform_io() noexcept override
     476                 :     {
     477              32 :         msghdr msg{};
     478              32 :         msg.msg_name    = &dest_storage;
     479              32 :         msg.msg_namelen = dest_len;
     480              32 :         msg.msg_iov     = iovecs;
     481              32 :         msg.msg_iovlen  = static_cast<std::size_t>(iovec_count);
     482                 : 
     483                 : #ifdef MSG_NOSIGNAL
     484              32 :         int send_flags = msg_flags | MSG_NOSIGNAL;
     485                 : #else
     486                 :         int send_flags = msg_flags;
     487                 : #endif
     488                 : 
     489                 :         ssize_t n;
     490                 :         do
     491                 :         {
     492              32 :             n = ::sendmsg(this->fd, &msg, send_flags);
     493                 :         }
     494              32 :         while (n < 0 && errno == EINTR);
     495                 : 
     496              32 :         if (n >= 0)
     497              30 :             this->complete(0, static_cast<std::size_t>(n));
     498                 :         else
     499               2 :             this->complete(errno, 0);
     500              32 :     }
     501                 : };
     502                 : 
     503                 : /** Shared recv_from operation for datagram sockets.
     504                 : 
     505                 :     Uses recvmsg() with msg_name to capture the source endpoint.
     506                 : 
     507                 :     @tparam Base The backend's base op type.
     508                 :     @tparam Endpoint The endpoint type (endpoint or local_endpoint).
     509                 : */
     510                 : template<class Base, class Endpoint = endpoint>
     511                 : struct reactor_recv_from_op : Base
     512                 : {
     513                 :     /// Maximum scatter-gather buffer count.
     514                 :     static constexpr std::size_t max_buffers = 16;
     515                 : 
     516                 :     /// Scatter-gather I/O vectors.
     517                 :     iovec iovecs[max_buffers];
     518                 : 
     519                 :     /// Number of active I/O vectors.
     520                 :     int iovec_count = 0;
     521                 : 
     522                 :     /// Source address storage filled by recvmsg.
     523                 :     sockaddr_storage source_storage{};
     524                 : 
     525                 :     /// Actual source address length returned by recvmsg.
     526                 :     socklen_t source_addrlen = 0;
     527                 : 
     528                 :     /// Output pointer for the source endpoint (set by do_recv_from).
     529                 :     Endpoint* source_out = nullptr;
     530                 : 
     531                 :     /// User-supplied message flags.
     532                 :     int msg_flags = 0;
     533                 : 
     534                 :     /// Return true (this is a read-direction operation).
     535 MIS           0 :     bool is_read_operation() const noexcept override
     536                 :     {
     537               0 :         return true;
     538                 :     }
     539                 : 
     540 HIT         134 :     void reset() noexcept
     541                 :     {
     542             134 :         Base::reset();
     543             134 :         iovec_count    = 0;
     544             134 :         source_storage = {};
     545             134 :         source_addrlen = 0;
     546             134 :         source_out     = nullptr;
     547             134 :         msg_flags      = 0;
     548             134 :     }
     549                 : 
     550              39 :     void perform_io() noexcept override
     551                 :     {
     552              39 :         msghdr msg{};
     553              39 :         msg.msg_name    = &source_storage;
     554              39 :         msg.msg_namelen = sizeof(source_storage);
     555              39 :         msg.msg_iov     = iovecs;
     556              39 :         msg.msg_iovlen  = static_cast<std::size_t>(iovec_count);
     557                 : 
     558                 :         ssize_t n;
     559                 :         do
     560                 :         {
     561              39 :             n = ::recvmsg(this->fd, &msg, msg_flags);
     562                 :         }
     563              39 :         while (n < 0 && errno == EINTR);
     564                 : 
     565              39 :         if (n >= 0)
     566                 :         {
     567              36 :             source_addrlen = msg.msg_namelen;
     568              36 :             this->complete(0, static_cast<std::size_t>(n));
     569                 :         }
     570                 :         else
     571               3 :             this->complete(errno, 0);
     572              39 :     }
     573                 : };
     574                 : 
     575                 : } // namespace boost::corosio::detail
     576                 : 
     577                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_REACTOR_REACTOR_OP_HPP
        

Generated by: LCOV version 2.3