LCOV - code coverage report
Current view: top level - src/detail/epoll - sockets.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 91.7 % 12 11
Test Date: 2026-02-06 05:04:16 Functions: 85.7 % 7 6

            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_DETAIL_EPOLL_SOCKETS_HPP
      11              : #define BOOST_COROSIO_DETAIL_EPOLL_SOCKETS_HPP
      12              : 
      13              : #include <boost/corosio/detail/platform.hpp>
      14              : 
      15              : #if BOOST_COROSIO_HAS_EPOLL
      16              : 
      17              : #include <boost/corosio/detail/config.hpp>
      18              : #include <boost/corosio/tcp_socket.hpp>
      19              : #include <boost/capy/ex/executor_ref.hpp>
      20              : #include <boost/capy/ex/execution_context.hpp>
      21              : #include "src/detail/intrusive.hpp"
      22              : #include "src/detail/socket_service.hpp"
      23              : 
      24              : #include "src/detail/cached_initiator.hpp"
      25              : #include "src/detail/epoll/op.hpp"
      26              : #include "src/detail/epoll/scheduler.hpp"
      27              : 
      28              : #include <coroutine>
      29              : #include <memory>
      30              : #include <mutex>
      31              : #include <unordered_map>
      32              : 
      33              : /*
      34              :     epoll Socket Implementation
      35              :     ===========================
      36              : 
      37              :     Each I/O operation follows the same pattern:
      38              :       1. Try the syscall immediately (non-blocking socket)
      39              :       2. If it succeeds or fails with a real error, post to completion queue
      40              :       3. If EAGAIN/EWOULDBLOCK, register with epoll and wait
      41              : 
      42              :     This "try first" approach avoids unnecessary epoll round-trips for
      43              :     operations that can complete immediately (common for small reads/writes
      44              :     on fast local connections).
      45              : 
      46              :     One-Shot Registration
      47              :     ---------------------
      48              :     We use one-shot epoll registration: each operation registers, waits for
      49              :     one event, then unregisters. This simplifies the state machine since we
      50              :     don't need to track whether an fd is currently registered or handle
      51              :     re-arming. The tradeoff is slightly more epoll_ctl calls, but the
      52              :     simplicity is worth it.
      53              : 
      54              :     Cancellation
      55              :     ------------
      56              :     See op.hpp for the completion/cancellation race handling via the
      57              :     `registered` atomic. cancel() must complete pending operations (post
      58              :     them with cancelled flag) so coroutines waiting on them can resume.
      59              :     close_socket() calls cancel() first to ensure this.
      60              : 
      61              :     Impl Lifetime with shared_ptr
      62              :     -----------------------------
      63              :     Socket impls use enable_shared_from_this. The service owns impls via
      64              :     shared_ptr maps (socket_ptrs_) keyed by raw pointer for O(1) lookup and
      65              :     removal. When a user calls close(), we call cancel() which posts pending
      66              :     ops to the scheduler.
      67              : 
      68              :     CRITICAL: The posted ops must keep the impl alive until they complete.
      69              :     Otherwise the scheduler would process a freed op (use-after-free). The
      70              :     cancel() method captures shared_from_this() into op.impl_ptr before
      71              :     posting. When the op completes, impl_ptr is cleared, allowing the impl
      72              :     to be destroyed if no other references exist.
      73              : 
      74              :     Service Ownership
      75              :     -----------------
      76              :     epoll_socket_service owns all socket impls. destroy_impl() removes the
      77              :     shared_ptr from the map, but the impl may survive if ops still hold
      78              :     impl_ptr refs. shutdown() closes all sockets and clears the map; any
      79              :     in-flight ops will complete and release their refs.
      80              : */
      81              : 
      82              : namespace boost::corosio::detail {
      83              : 
      84              : class epoll_socket_service;
      85              : class epoll_socket_impl;
      86              : 
      87              : /// Socket implementation for epoll backend.
      88              : class epoll_socket_impl
      89              :     : public tcp_socket::socket_impl
      90              :     , public std::enable_shared_from_this<epoll_socket_impl>
      91              :     , public intrusive_list<epoll_socket_impl>::node
      92              : {
      93              :     friend class epoll_socket_service;
      94              : 
      95              : public:
      96              :     explicit epoll_socket_impl(epoll_socket_service& svc) noexcept;
      97              :     ~epoll_socket_impl();
      98              : 
      99              :     void release() override;
     100              : 
     101              :     std::coroutine_handle<> connect(
     102              :         std::coroutine_handle<>,
     103              :         capy::executor_ref,
     104              :         endpoint,
     105              :         std::stop_token,
     106              :         std::error_code*) override;
     107              : 
     108              :     std::coroutine_handle<> read_some(
     109              :         std::coroutine_handle<>,
     110              :         capy::executor_ref,
     111              :         io_buffer_param,
     112              :         std::stop_token,
     113              :         std::error_code*,
     114              :         std::size_t*) override;
     115              : 
     116              :     std::coroutine_handle<> write_some(
     117              :         std::coroutine_handle<>,
     118              :         capy::executor_ref,
     119              :         io_buffer_param,
     120              :         std::stop_token,
     121              :         std::error_code*,
     122              :         std::size_t*) override;
     123              : 
     124              :     std::error_code shutdown(tcp_socket::shutdown_type what) noexcept override;
     125              : 
     126            0 :     native_handle_type native_handle() const noexcept override { return fd_; }
     127              : 
     128              :     // Socket options
     129              :     std::error_code set_no_delay(bool value) noexcept override;
     130              :     bool no_delay(std::error_code& ec) const noexcept override;
     131              : 
     132              :     std::error_code set_keep_alive(bool value) noexcept override;
     133              :     bool keep_alive(std::error_code& ec) const noexcept override;
     134              : 
     135              :     std::error_code set_receive_buffer_size(int size) noexcept override;
     136              :     int receive_buffer_size(std::error_code& ec) const noexcept override;
     137              : 
     138              :     std::error_code set_send_buffer_size(int size) noexcept override;
     139              :     int send_buffer_size(std::error_code& ec) const noexcept override;
     140              : 
     141              :     std::error_code set_linger(bool enabled, int timeout) noexcept override;
     142              :     tcp_socket::linger_options linger(std::error_code& ec) const noexcept override;
     143              : 
     144           16 :     endpoint local_endpoint() const noexcept override { return local_endpoint_; }
     145           16 :     endpoint remote_endpoint() const noexcept override { return remote_endpoint_; }
     146              :     bool is_open() const noexcept { return fd_ >= 0; }
     147              :     void cancel() noexcept override;
     148              :     void cancel_single_op(epoll_op& op) noexcept;
     149              :     void close_socket() noexcept;
     150         5017 :     void set_socket(int fd) noexcept { fd_ = fd; }
     151        10034 :     void set_endpoints(endpoint local, endpoint remote) noexcept
     152              :     {
     153        10034 :         local_endpoint_ = local;
     154        10034 :         remote_endpoint_ = remote;
     155        10034 :     }
     156              : 
     157              :     epoll_connect_op conn_;
     158              :     epoll_read_op rd_;
     159              :     epoll_write_op wr_;
     160              : 
     161              :     /// Per-descriptor state for persistent epoll registration
     162              :     descriptor_state desc_state_;
     163              : 
     164              :     cached_initiator read_initiator_;
     165              :     cached_initiator write_initiator_;
     166              : 
     167              :     /// Execute the read I/O operation (called by initiator coroutine).
     168              :     void do_read_io();
     169              : 
     170              :     /// Execute the write I/O operation (called by initiator coroutine).
     171              :     void do_write_io();
     172              : 
     173              : private:
     174              :     epoll_socket_service& svc_;
     175              :     int fd_ = -1;
     176              :     endpoint local_endpoint_;
     177              :     endpoint remote_endpoint_;
     178              : };
     179              : 
     180              : /** State for epoll socket service. */
     181              : class epoll_socket_state
     182              : {
     183              : public:
     184          189 :     explicit epoll_socket_state(epoll_scheduler& sched) noexcept
     185          189 :         : sched_(sched)
     186              :     {
     187          189 :     }
     188              : 
     189              :     epoll_scheduler& sched_;
     190              :     std::mutex mutex_;
     191              :     intrusive_list<epoll_socket_impl> socket_list_;
     192              :     std::unordered_map<epoll_socket_impl*, std::shared_ptr<epoll_socket_impl>> socket_ptrs_;
     193              : };
     194              : 
     195              : /** epoll socket service implementation.
     196              : 
     197              :     Inherits from socket_service to enable runtime polymorphism.
     198              :     Uses key_type = socket_service for service lookup.
     199              : */
     200              : class epoll_socket_service : public socket_service
     201              : {
     202              : public:
     203              :     explicit epoll_socket_service(capy::execution_context& ctx);
     204              :     ~epoll_socket_service();
     205              : 
     206              :     epoll_socket_service(epoll_socket_service const&) = delete;
     207              :     epoll_socket_service& operator=(epoll_socket_service const&) = delete;
     208              : 
     209              :     void shutdown() override;
     210              : 
     211              :     tcp_socket::socket_impl& create_impl() override;
     212              :     void destroy_impl(tcp_socket::socket_impl& impl) override;
     213              :     std::error_code open_socket(tcp_socket::socket_impl& impl) override;
     214              : 
     215        20092 :     epoll_scheduler& scheduler() const noexcept { return state_->sched_; }
     216              :     void post(epoll_op* op);
     217              :     void work_started() noexcept;
     218              :     void work_finished() noexcept;
     219              : 
     220              : private:
     221              :     std::unique_ptr<epoll_socket_state> state_;
     222              : };
     223              : 
     224              : } // namespace boost::corosio::detail
     225              : 
     226              : #endif // BOOST_COROSIO_HAS_EPOLL
     227              : 
     228              : #endif // BOOST_COROSIO_DETAIL_EPOLL_SOCKETS_HPP
        

Generated by: LCOV version 2.3