1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
12  

12  

13  
#include <boost/corosio/detail/platform.hpp>
13  
#include <boost/corosio/detail/platform.hpp>
14  

14  

15  
#if BOOST_COROSIO_HAS_SELECT
15  
#if BOOST_COROSIO_HAS_SELECT
16  

16  

17  
#include <boost/corosio/native/detail/reactor/reactor_op.hpp>
17  
#include <boost/corosio/native/detail/reactor/reactor_op.hpp>
18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19  

19  

20  
#include <errno.h>
20  
#include <errno.h>
21  
#include <fcntl.h>
21  
#include <fcntl.h>
22  
#include <sys/socket.h>
22  
#include <sys/socket.h>
23  
#include <unistd.h>
23  
#include <unistd.h>
24  

24  

25  
/*
25  
/*
26  
    File descriptors are registered with the select scheduler once (via
26  
    File descriptors are registered with the select scheduler once (via
27  
    select_descriptor_state) and stay registered until closed.
27  
    select_descriptor_state) and stay registered until closed.
28  

28  

29  
    select() is level-triggered but the descriptor_state pattern
29  
    select() is level-triggered but the descriptor_state pattern
30  
    (designed for edge-triggered) works correctly: is_enqueued_ CAS
30  
    (designed for edge-triggered) works correctly: is_enqueued_ CAS
31  
    prevents double-enqueue, add_ready_events is idempotent, and
31  
    prevents double-enqueue, add_ready_events is idempotent, and
32  
    EAGAIN ops stay parked until the next select() re-reports readiness.
32  
    EAGAIN ops stay parked until the next select() re-reports readiness.
33  

33  

34  
    cancel() captures shared_from_this() into op.impl_ptr to prevent
34  
    cancel() captures shared_from_this() into op.impl_ptr to prevent
35  
    use-after-free when the socket is closed with pending ops.
35  
    use-after-free when the socket is closed with pending ops.
36  

36  

37  
    Writes use sendmsg(MSG_NOSIGNAL) on Linux. On macOS/BSD where
37  
    Writes use sendmsg(MSG_NOSIGNAL) on Linux. On macOS/BSD where
38  
    MSG_NOSIGNAL may be absent, SO_NOSIGPIPE is set at socket creation
38  
    MSG_NOSIGNAL may be absent, SO_NOSIGPIPE is set at socket creation
39  
    and accepted-socket setup instead.
39  
    and accepted-socket setup instead.
40  
*/
40  
*/
41  

41  

42  
namespace boost::corosio::detail {
42  
namespace boost::corosio::detail {
43  

43  

44  
// Forward declarations
44  
// Forward declarations
45  
class select_tcp_socket;
45  
class select_tcp_socket;
46  
class select_tcp_acceptor;
46  
class select_tcp_acceptor;
47  
struct select_op;
47  
struct select_op;
48  

48  

49  
// Forward declaration
49  
// Forward declaration
50  
class select_scheduler;
50  
class select_scheduler;
51  

51  

52  
/// Per-descriptor state for persistent select registration.
52  
/// Per-descriptor state for persistent select registration.
53  
struct select_descriptor_state final : reactor_descriptor_state
53  
struct select_descriptor_state final : reactor_descriptor_state
54  
{};
54  
{};
55  

55  

56  
/// select base operation — thin wrapper over reactor_op.
56  
/// select base operation — thin wrapper over reactor_op.
57  
struct select_op : reactor_op<select_tcp_socket, select_tcp_acceptor>
57  
struct select_op : reactor_op<select_tcp_socket, select_tcp_acceptor>
58  
{
58  
{
59  
    void operator()() override;
59  
    void operator()() override;
60  
};
60  
};
61  

61  

62  
/// select connect operation.
62  
/// select connect operation.
63  
struct select_connect_op final : reactor_connect_op<select_op>
63  
struct select_connect_op final : reactor_connect_op<select_op>
64  
{
64  
{
65  
    void operator()() override;
65  
    void operator()() override;
66  
    void cancel() noexcept override;
66  
    void cancel() noexcept override;
67  
};
67  
};
68  

68  

69  
/// select scatter-read operation.
69  
/// select scatter-read operation.
70  
struct select_read_op final : reactor_read_op<select_op>
70  
struct select_read_op final : reactor_read_op<select_op>
71  
{
71  
{
72  
    void cancel() noexcept override;
72  
    void cancel() noexcept override;
73  
};
73  
};
74  

74  

75  
/** Provides sendmsg() with EINTR retry for select writes.
75  
/** Provides sendmsg() with EINTR retry for select writes.
76  

76  

77  
    Uses MSG_NOSIGNAL where available (Linux). On platforms without
77  
    Uses MSG_NOSIGNAL where available (Linux). On platforms without
78  
    it (macOS/BSD), SO_NOSIGPIPE is set at socket creation time
78  
    it (macOS/BSD), SO_NOSIGPIPE is set at socket creation time
79  
    and flags=0 is used here.
79  
    and flags=0 is used here.
80  
*/
80  
*/
81  
struct select_write_policy
81  
struct select_write_policy
82  
{
82  
{
83  
    static ssize_t write(int fd, iovec* iovecs, int count) noexcept
83  
    static ssize_t write(int fd, iovec* iovecs, int count) noexcept
84  
    {
84  
    {
85  
        msghdr msg{};
85  
        msghdr msg{};
86  
        msg.msg_iov    = iovecs;
86  
        msg.msg_iov    = iovecs;
87  
        msg.msg_iovlen = static_cast<std::size_t>(count);
87  
        msg.msg_iovlen = static_cast<std::size_t>(count);
88  

88  

89  
#ifdef MSG_NOSIGNAL
89  
#ifdef MSG_NOSIGNAL
90  
        constexpr int send_flags = MSG_NOSIGNAL;
90  
        constexpr int send_flags = MSG_NOSIGNAL;
91  
#else
91  
#else
92  
        constexpr int send_flags = 0;
92  
        constexpr int send_flags = 0;
93  
#endif
93  
#endif
94  

94  

95  
        ssize_t n;
95  
        ssize_t n;
96  
        do
96  
        do
97  
        {
97  
        {
98  
            n = ::sendmsg(fd, &msg, send_flags);
98  
            n = ::sendmsg(fd, &msg, send_flags);
99  
        }
99  
        }
100  
        while (n < 0 && errno == EINTR);
100  
        while (n < 0 && errno == EINTR);
101  
        return n;
101  
        return n;
102  
    }
102  
    }
103  
};
103  
};
104  

104  

105  
/// select gather-write operation.
105  
/// select gather-write operation.
106  
struct select_write_op final : reactor_write_op<select_op, select_write_policy>
106  
struct select_write_op final : reactor_write_op<select_op, select_write_policy>
107  
{
107  
{
108  
    void cancel() noexcept override;
108  
    void cancel() noexcept override;
109  
};
109  
};
110  

110  

111  
/** Provides accept() + fcntl(O_NONBLOCK|FD_CLOEXEC) with FD_SETSIZE check.
111  
/** Provides accept() + fcntl(O_NONBLOCK|FD_CLOEXEC) with FD_SETSIZE check.
112  

112  

113  
    Uses accept() instead of accept4() for broader POSIX compatibility.
113  
    Uses accept() instead of accept4() for broader POSIX compatibility.
114  
*/
114  
*/
115  
struct select_accept_policy
115  
struct select_accept_policy
116  
{
116  
{
117  
    static int do_accept(int fd, sockaddr_storage& peer) noexcept
117  
    static int do_accept(int fd, sockaddr_storage& peer) noexcept
118  
    {
118  
    {
119  
        socklen_t addrlen = sizeof(peer);
119  
        socklen_t addrlen = sizeof(peer);
120  
        int new_fd;
120  
        int new_fd;
121  
        do
121  
        do
122  
        {
122  
        {
123  
            new_fd = ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
123  
            new_fd = ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
124  
        }
124  
        }
125  
        while (new_fd < 0 && errno == EINTR);
125  
        while (new_fd < 0 && errno == EINTR);
126  

126  

127  
        if (new_fd < 0)
127  
        if (new_fd < 0)
128  
            return new_fd;
128  
            return new_fd;
129  

129  

130  
        if (new_fd >= FD_SETSIZE)
130  
        if (new_fd >= FD_SETSIZE)
131  
        {
131  
        {
132  
            ::close(new_fd);
132  
            ::close(new_fd);
133  
            errno = EINVAL;
133  
            errno = EINVAL;
134  
            return -1;
134  
            return -1;
135  
        }
135  
        }
136  

136  

137  
        int flags = ::fcntl(new_fd, F_GETFL, 0);
137  
        int flags = ::fcntl(new_fd, F_GETFL, 0);
138  
        if (flags == -1)
138  
        if (flags == -1)
139  
        {
139  
        {
140  
            int err = errno;
140  
            int err = errno;
141  
            ::close(new_fd);
141  
            ::close(new_fd);
142  
            errno = err;
142  
            errno = err;
143  
            return -1;
143  
            return -1;
144  
        }
144  
        }
145  

145  

146  
        if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
146  
        if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
147  
        {
147  
        {
148  
            int err = errno;
148  
            int err = errno;
149  
            ::close(new_fd);
149  
            ::close(new_fd);
150  
            errno = err;
150  
            errno = err;
151  
            return -1;
151  
            return -1;
152  
        }
152  
        }
153  

153  

154  
        if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
154  
        if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
155  
        {
155  
        {
156  
            int err = errno;
156  
            int err = errno;
157  
            ::close(new_fd);
157  
            ::close(new_fd);
158  
            errno = err;
158  
            errno = err;
159  
            return -1;
159  
            return -1;
160  
        }
160  
        }
161  

161  

162  
#ifdef SO_NOSIGPIPE
162  
#ifdef SO_NOSIGPIPE
163  
        int one = 1;
163  
        int one = 1;
164  
        if (::setsockopt(new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) ==
164  
        if (::setsockopt(new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) ==
165  
            -1)
165  
            -1)
166  
        {
166  
        {
167  
            int err = errno;
167  
            int err = errno;
168  
            ::close(new_fd);
168  
            ::close(new_fd);
169  
            errno = err;
169  
            errno = err;
170  
            return -1;
170  
            return -1;
171  
        }
171  
        }
172  
#endif
172  
#endif
173  

173  

174  
        return new_fd;
174  
        return new_fd;
175  
    }
175  
    }
176  
};
176  
};
177  

177  

178  
/// select accept operation.
178  
/// select accept operation.
179  
struct select_accept_op final
179  
struct select_accept_op final
180  
    : reactor_accept_op<select_op, select_accept_policy>
180  
    : reactor_accept_op<select_op, select_accept_policy>
181  
{
181  
{
182  
    void operator()() override;
182  
    void operator()() override;
183  
    void cancel() noexcept override;
183  
    void cancel() noexcept override;
184  
};
184  
};
185  

185  

186  
} // namespace boost::corosio::detail
186  
} // namespace boost::corosio::detail
187  

187  

188  
#endif // BOOST_COROSIO_HAS_SELECT
188  
#endif // BOOST_COROSIO_HAS_SELECT
189  

189  

190  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
190  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP