librats 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/native-src/CMakeLists.txt +10 -2
- package/native-src/src/bt_network.cpp +296 -183
- package/native-src/src/bt_network.h +25 -5
- package/native-src/src/io_poller.cpp +910 -0
- package/native-src/src/io_poller.h +138 -0
- package/native-src/src/librats.cpp +809 -1236
- package/native-src/src/librats.h +146 -54
- package/native-src/src/librats_bittorrent.cpp +1 -5
- package/native-src/src/librats_c.cpp +1 -1
- package/native-src/src/librats_discovery.cpp +309 -0
- package/native-src/src/librats_encryption.cpp +130 -283
- package/native-src/src/librats_file_transfer.cpp +2 -6
- package/native-src/src/librats_gossipsub.cpp +1 -5
- package/native-src/src/librats_ice.cpp +1 -1
- package/native-src/src/librats_log_macros.h +36 -0
- package/native-src/src/librats_logging.cpp +1 -7
- package/native-src/src/librats_mdns.cpp +1 -11
- package/native-src/src/librats_persistence.cpp +1 -11
- package/native-src/src/librats_reconnection.cpp +2 -13
- package/native-src/src/librats_statistic.cpp +97 -0
- package/package.json +1 -1
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
#include "io_poller.h"
|
|
2
|
+
#include "logger.h"
|
|
3
|
+
|
|
4
|
+
#include <cstring>
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
#include <set>
|
|
7
|
+
|
|
8
|
+
//=============================================================================
|
|
9
|
+
// Platform detection
|
|
10
|
+
//=============================================================================
|
|
11
|
+
|
|
12
|
+
#if defined(__linux__)
|
|
13
|
+
#define POLLER_USE_EPOLL 1
|
|
14
|
+
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
|
15
|
+
#define POLLER_USE_KQUEUE 1
|
|
16
|
+
#elif defined(_WIN32)
|
|
17
|
+
#define POLLER_USE_IOCP 1
|
|
18
|
+
#else
|
|
19
|
+
// Fallback: use poll() on other POSIX systems
|
|
20
|
+
#define POLLER_USE_POLL 1
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
//=============================================================================
|
|
24
|
+
// Platform includes
|
|
25
|
+
//=============================================================================
|
|
26
|
+
|
|
27
|
+
#if defined(POLLER_USE_EPOLL)
|
|
28
|
+
#include <sys/epoll.h>
|
|
29
|
+
#include <unistd.h>
|
|
30
|
+
#include <errno.h>
|
|
31
|
+
#elif defined(POLLER_USE_KQUEUE)
|
|
32
|
+
#include <sys/types.h>
|
|
33
|
+
#include <sys/event.h>
|
|
34
|
+
#include <sys/time.h>
|
|
35
|
+
#include <unistd.h>
|
|
36
|
+
#include <errno.h>
|
|
37
|
+
#elif defined(POLLER_USE_IOCP)
|
|
38
|
+
#include <winsock2.h>
|
|
39
|
+
#include <ws2tcpip.h>
|
|
40
|
+
#include <mutex>
|
|
41
|
+
#include <unordered_map>
|
|
42
|
+
#include <vector>
|
|
43
|
+
#include <memory>
|
|
44
|
+
#elif defined(POLLER_USE_POLL)
|
|
45
|
+
#include <poll.h>
|
|
46
|
+
#include <errno.h>
|
|
47
|
+
#endif
|
|
48
|
+
|
|
49
|
+
// Logging macros
|
|
50
|
+
#define LOG_POLLER_DEBUG(msg) LOG_DEBUG("IOPoller", msg)
|
|
51
|
+
#define LOG_POLLER_INFO(msg) LOG_INFO("IOPoller", msg)
|
|
52
|
+
#define LOG_POLLER_WARN(msg) LOG_WARN("IOPoller", msg)
|
|
53
|
+
#define LOG_POLLER_ERROR(msg) LOG_ERROR("IOPoller", msg)
|
|
54
|
+
|
|
55
|
+
namespace librats {
|
|
56
|
+
|
|
57
|
+
//=============================================================================
|
|
58
|
+
// Linux: epoll implementation
|
|
59
|
+
//=============================================================================
|
|
60
|
+
|
|
61
|
+
#if defined(POLLER_USE_EPOLL)
|
|
62
|
+
|
|
63
|
+
class EpollPoller final : public IOPoller {
|
|
64
|
+
public:
|
|
65
|
+
EpollPoller() {
|
|
66
|
+
epfd_ = epoll_create1(EPOLL_CLOEXEC);
|
|
67
|
+
if (epfd_ < 0) {
|
|
68
|
+
LOG_POLLER_ERROR("epoll_create1 failed: " + std::string(strerror(errno)));
|
|
69
|
+
} else {
|
|
70
|
+
LOG_POLLER_INFO("Created epoll instance (fd=" + std::to_string(epfd_) + ")");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
~EpollPoller() override {
|
|
75
|
+
if (epfd_ >= 0) {
|
|
76
|
+
::close(epfd_);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
bool add(socket_t fd, uint32_t events) override {
|
|
81
|
+
struct epoll_event ev;
|
|
82
|
+
std::memset(&ev, 0, sizeof(ev));
|
|
83
|
+
ev.data.fd = fd;
|
|
84
|
+
ev.events = to_epoll_events(events);
|
|
85
|
+
|
|
86
|
+
if (epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
|
87
|
+
LOG_POLLER_ERROR("epoll_ctl ADD failed for fd " + std::to_string(fd) +
|
|
88
|
+
": " + std::string(strerror(errno)));
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
bool modify(socket_t fd, uint32_t events) override {
|
|
95
|
+
struct epoll_event ev;
|
|
96
|
+
std::memset(&ev, 0, sizeof(ev));
|
|
97
|
+
ev.data.fd = fd;
|
|
98
|
+
ev.events = to_epoll_events(events);
|
|
99
|
+
|
|
100
|
+
if (epoll_ctl(epfd_, EPOLL_CTL_MOD, fd, &ev) < 0) {
|
|
101
|
+
LOG_POLLER_ERROR("epoll_ctl MOD failed for fd " + std::to_string(fd) +
|
|
102
|
+
": " + std::string(strerror(errno)));
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
bool remove(socket_t fd) override {
|
|
109
|
+
if (epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, nullptr) < 0) {
|
|
110
|
+
// ENOENT is expected if fd was already closed/removed
|
|
111
|
+
if (errno != ENOENT) {
|
|
112
|
+
LOG_POLLER_ERROR("epoll_ctl DEL failed for fd " + std::to_string(fd) +
|
|
113
|
+
": " + std::string(strerror(errno)));
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
int wait(PollResult* results, int max_results, int timeout_ms) override {
|
|
121
|
+
struct epoll_event events[max_results];
|
|
122
|
+
|
|
123
|
+
int n = epoll_wait(epfd_, events, max_results, timeout_ms);
|
|
124
|
+
|
|
125
|
+
if (n < 0) {
|
|
126
|
+
if (errno != EINTR) {
|
|
127
|
+
LOG_POLLER_ERROR("epoll_wait failed: " + std::string(strerror(errno)));
|
|
128
|
+
}
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (int i = 0; i < n; ++i) {
|
|
133
|
+
results[i].fd = static_cast<socket_t>(events[i].data.fd);
|
|
134
|
+
results[i].events = from_epoll_events(events[i].events);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return n;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const char* name() const override { return "epoll"; }
|
|
141
|
+
|
|
142
|
+
private:
|
|
143
|
+
int epfd_ = -1;
|
|
144
|
+
|
|
145
|
+
static uint32_t to_epoll_events(uint32_t flags) {
|
|
146
|
+
uint32_t e = 0;
|
|
147
|
+
if (flags & PollIn) e |= EPOLLIN;
|
|
148
|
+
if (flags & PollOut) e |= EPOLLOUT;
|
|
149
|
+
// EPOLLERR and EPOLLHUP are always reported, no need to set
|
|
150
|
+
return e;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static uint32_t from_epoll_events(uint32_t epoll_events) {
|
|
154
|
+
uint32_t flags = 0;
|
|
155
|
+
if (epoll_events & EPOLLIN) flags |= PollIn;
|
|
156
|
+
if (epoll_events & EPOLLOUT) flags |= PollOut;
|
|
157
|
+
if (epoll_events & EPOLLERR) flags |= PollErr;
|
|
158
|
+
if (epoll_events & EPOLLHUP) flags |= PollHup;
|
|
159
|
+
return flags;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
#endif // POLLER_USE_EPOLL
|
|
164
|
+
|
|
165
|
+
//=============================================================================
|
|
166
|
+
// macOS/BSD: kqueue implementation
|
|
167
|
+
//=============================================================================
|
|
168
|
+
|
|
169
|
+
#if defined(POLLER_USE_KQUEUE)
|
|
170
|
+
|
|
171
|
+
class KqueuePoller final : public IOPoller {
|
|
172
|
+
public:
|
|
173
|
+
KqueuePoller() {
|
|
174
|
+
kqfd_ = kqueue();
|
|
175
|
+
if (kqfd_ < 0) {
|
|
176
|
+
LOG_POLLER_ERROR("kqueue() failed: " + std::string(strerror(errno)));
|
|
177
|
+
} else {
|
|
178
|
+
LOG_POLLER_INFO("Created kqueue instance (fd=" + std::to_string(kqfd_) + ")");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
~KqueuePoller() override {
|
|
183
|
+
if (kqfd_ >= 0) {
|
|
184
|
+
::close(kqfd_);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
bool add(socket_t fd, uint32_t events) override {
|
|
189
|
+
if (!apply_changes(fd, events, EV_ADD | EV_CLEAR))
|
|
190
|
+
return false;
|
|
191
|
+
registered_.insert(fd);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
bool modify(socket_t fd, uint32_t events) override {
|
|
196
|
+
if (registered_.find(fd) == registered_.end()) return false;
|
|
197
|
+
|
|
198
|
+
// kqueue: adding a filter that already exists replaces it.
|
|
199
|
+
// We also need to delete filters that are no longer wanted.
|
|
200
|
+
struct kevent changes[4];
|
|
201
|
+
int nchanges = 0;
|
|
202
|
+
|
|
203
|
+
if (events & PollIn) {
|
|
204
|
+
EV_SET(&changes[nchanges++], fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr);
|
|
205
|
+
} else {
|
|
206
|
+
EV_SET(&changes[nchanges++], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (events & PollOut) {
|
|
210
|
+
EV_SET(&changes[nchanges++], fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, nullptr);
|
|
211
|
+
} else {
|
|
212
|
+
EV_SET(&changes[nchanges++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Apply changes (ignore ENOENT errors from deleting non-existent filters)
|
|
216
|
+
int ret = kevent(kqfd_, changes, nchanges, nullptr, 0, nullptr);
|
|
217
|
+
if (ret < 0 && errno != ENOENT) {
|
|
218
|
+
LOG_POLLER_ERROR("kevent modify failed for fd " + std::to_string(fd) +
|
|
219
|
+
": " + std::string(strerror(errno)));
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
bool remove(socket_t fd) override {
|
|
226
|
+
if (registered_.erase(fd) == 0) return false;
|
|
227
|
+
|
|
228
|
+
struct kevent changes[2];
|
|
229
|
+
// Delete both read and write filters. Ignore errors (filter may not exist).
|
|
230
|
+
EV_SET(&changes[0], fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr);
|
|
231
|
+
EV_SET(&changes[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr);
|
|
232
|
+
kevent(kqfd_, changes, 2, nullptr, 0, nullptr);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
int wait(PollResult* results, int max_results, int timeout_ms) override {
|
|
237
|
+
struct timespec ts;
|
|
238
|
+
struct timespec* ts_ptr = nullptr;
|
|
239
|
+
|
|
240
|
+
if (timeout_ms >= 0) {
|
|
241
|
+
ts.tv_sec = timeout_ms / 1000;
|
|
242
|
+
ts.tv_nsec = (timeout_ms % 1000) * 1000000L;
|
|
243
|
+
ts_ptr = &ts;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// kqueue may return separate events for read and write on the same fd.
|
|
247
|
+
// We fetch at most max_results raw events — after merging we may get
|
|
248
|
+
// fewer results, but we never lose events (un-fetched events stay in
|
|
249
|
+
// the kqueue for the next call). Fetching more than max_results would
|
|
250
|
+
// risk silently discarding events with EV_CLEAR (edge-triggered).
|
|
251
|
+
struct kevent kevents[max_results > 256 ? 256 : max_results];
|
|
252
|
+
int kevents_size = (max_results > 256) ? 256 : max_results;
|
|
253
|
+
|
|
254
|
+
int n = kevent(kqfd_, nullptr, 0, kevents, kevents_size, ts_ptr);
|
|
255
|
+
|
|
256
|
+
if (n < 0) {
|
|
257
|
+
if (errno != EINTR) {
|
|
258
|
+
LOG_POLLER_ERROR("kevent wait failed: " + std::string(strerror(errno)));
|
|
259
|
+
}
|
|
260
|
+
return -1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Merge events for the same fd
|
|
264
|
+
int count = 0;
|
|
265
|
+
for (int i = 0; i < n && count < max_results; ++i) {
|
|
266
|
+
socket_t fd = static_cast<socket_t>(kevents[i].ident);
|
|
267
|
+
uint32_t flags = 0;
|
|
268
|
+
|
|
269
|
+
if (kevents[i].filter == EVFILT_READ) {
|
|
270
|
+
flags |= PollIn;
|
|
271
|
+
if (kevents[i].flags & EV_EOF) flags |= PollHup;
|
|
272
|
+
}
|
|
273
|
+
if (kevents[i].filter == EVFILT_WRITE) {
|
|
274
|
+
flags |= PollOut;
|
|
275
|
+
}
|
|
276
|
+
if (kevents[i].flags & EV_ERROR) {
|
|
277
|
+
flags |= PollErr;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check if we already have an entry for this fd
|
|
281
|
+
bool merged = false;
|
|
282
|
+
for (int j = 0; j < count; ++j) {
|
|
283
|
+
if (results[j].fd == fd) {
|
|
284
|
+
results[j].events |= flags;
|
|
285
|
+
merged = true;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!merged) {
|
|
291
|
+
results[count].fd = fd;
|
|
292
|
+
results[count].events = flags;
|
|
293
|
+
++count;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return count;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const char* name() const override { return "kqueue"; }
|
|
301
|
+
|
|
302
|
+
private:
|
|
303
|
+
int kqfd_ = -1;
|
|
304
|
+
std::set<socket_t> registered_; ///< Track registered fds
|
|
305
|
+
|
|
306
|
+
bool apply_changes(socket_t fd, uint32_t events, uint16_t kq_flags) {
|
|
307
|
+
struct kevent changes[2];
|
|
308
|
+
int nchanges = 0;
|
|
309
|
+
|
|
310
|
+
if (events & PollIn) {
|
|
311
|
+
EV_SET(&changes[nchanges++], fd, EVFILT_READ, kq_flags, 0, 0, nullptr);
|
|
312
|
+
}
|
|
313
|
+
if (events & PollOut) {
|
|
314
|
+
EV_SET(&changes[nchanges++], fd, EVFILT_WRITE, kq_flags, 0, 0, nullptr);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (nchanges == 0) return true;
|
|
318
|
+
|
|
319
|
+
if (kevent(kqfd_, changes, nchanges, nullptr, 0, nullptr) < 0) {
|
|
320
|
+
LOG_POLLER_ERROR("kevent add/modify failed for fd " + std::to_string(fd) +
|
|
321
|
+
": " + std::string(strerror(errno)));
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
#endif // POLLER_USE_KQUEUE
|
|
329
|
+
|
|
330
|
+
//=============================================================================
|
|
331
|
+
// Windows: IOCP (I/O Completion Ports) implementation
|
|
332
|
+
//=============================================================================
|
|
333
|
+
//
|
|
334
|
+
// Architecture:
|
|
335
|
+
// Connected sockets use zero-byte overlapped WSARecv/WSASend to get
|
|
336
|
+
// readiness notifications through the IOCP completion port (proactor
|
|
337
|
+
// model adapted to reactor semantics).
|
|
338
|
+
//
|
|
339
|
+
// Listen sockets and connecting sockets (where overlapped recv/send
|
|
340
|
+
// cannot be used) fall back to a non-blocking WSAPoll check done
|
|
341
|
+
// inside wait() — typically only 1 listen + ~30 connecting sockets,
|
|
342
|
+
// so this is negligible overhead.
|
|
343
|
+
//
|
|
344
|
+
// When send_to_peer() on another thread arms a write via modify(),
|
|
345
|
+
// the zero-byte WSASend completes and wakes GQCS immediately —
|
|
346
|
+
// the I/O thread gets notified with zero latency.
|
|
347
|
+
//
|
|
348
|
+
// Memory safety: SocketState objects are never freed while IOCP
|
|
349
|
+
// operations may reference them (they are kept alive in all_states_).
|
|
350
|
+
// Removed sockets are marked with removed=true and their completions
|
|
351
|
+
// are silently discarded in wait().
|
|
352
|
+
//=============================================================================
|
|
353
|
+
|
|
354
|
+
#if defined(POLLER_USE_IOCP)
|
|
355
|
+
|
|
356
|
+
class IocpPoller final : public IOPoller {
|
|
357
|
+
public:
|
|
358
|
+
IocpPoller() {
|
|
359
|
+
iocp_ = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
|
|
360
|
+
if (!iocp_) {
|
|
361
|
+
LOG_POLLER_ERROR("CreateIoCompletionPort failed: " +
|
|
362
|
+
std::to_string(GetLastError()));
|
|
363
|
+
} else {
|
|
364
|
+
LOG_POLLER_INFO("Created IOCP poller");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
~IocpPoller() override {
|
|
369
|
+
{
|
|
370
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
371
|
+
// Cancel all pending I/O before destroying
|
|
372
|
+
for (auto& [fd, state] : active_) {
|
|
373
|
+
if (state->read_pending || state->write_pending) {
|
|
374
|
+
CancelIoEx(reinterpret_cast<HANDLE>(fd), nullptr);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
active_.clear();
|
|
378
|
+
all_states_.clear();
|
|
379
|
+
}
|
|
380
|
+
if (iocp_) {
|
|
381
|
+
CloseHandle(iocp_);
|
|
382
|
+
iocp_ = nullptr;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
bool add(socket_t fd, uint32_t events) override {
|
|
387
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
388
|
+
|
|
389
|
+
auto state_ptr = std::make_unique<IocpSocketState>(fd);
|
|
390
|
+
auto* state = state_ptr.get();
|
|
391
|
+
state->desired_events = events;
|
|
392
|
+
|
|
393
|
+
// Associate socket with IOCP (needed for overlapped I/O)
|
|
394
|
+
CreateIoCompletionPort(reinterpret_cast<HANDLE>(fd), iocp_, 0, 0);
|
|
395
|
+
|
|
396
|
+
// Determine socket mode:
|
|
397
|
+
// - Try zero-byte WSARecv to detect if socket is connected (IOCP mode)
|
|
398
|
+
// - Listen sockets and connecting sockets use WSAPoll fallback
|
|
399
|
+
if (events & PollIn) {
|
|
400
|
+
if (arm_read(state)) {
|
|
401
|
+
state->mode = SocketMode::Iocp;
|
|
402
|
+
} else {
|
|
403
|
+
// WSARecv failed (WSAENOTCONN) — listen socket or not yet connected
|
|
404
|
+
state->mode = SocketMode::WsaPoll;
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
// PollOut only → connecting socket → WSAPoll mode
|
|
408
|
+
state->mode = SocketMode::WsaPoll;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Arm write notification if IOCP mode and interested
|
|
412
|
+
if (state->mode == SocketMode::Iocp && (events & PollOut)) {
|
|
413
|
+
arm_write(state);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
active_[fd] = state;
|
|
417
|
+
all_states_.push_back(std::move(state_ptr));
|
|
418
|
+
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
bool modify(socket_t fd, uint32_t events) override {
|
|
423
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
424
|
+
|
|
425
|
+
auto it = active_.find(fd);
|
|
426
|
+
if (it == active_.end()) return false;
|
|
427
|
+
auto* state = it->second;
|
|
428
|
+
if (state->removed) return false;
|
|
429
|
+
|
|
430
|
+
state->desired_events = events;
|
|
431
|
+
|
|
432
|
+
// Transition WSAPoll → IOCP when PollIn is added
|
|
433
|
+
// (connecting socket completed its TCP handshake and became connected)
|
|
434
|
+
if (state->mode == SocketMode::WsaPoll && (events & PollIn)) {
|
|
435
|
+
if (arm_read(state)) {
|
|
436
|
+
state->mode = SocketMode::Iocp;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Arm overlapped ops as needed for IOCP-mode sockets
|
|
441
|
+
if (state->mode == SocketMode::Iocp) {
|
|
442
|
+
if ((events & PollIn) && !state->read_pending) {
|
|
443
|
+
arm_read(state);
|
|
444
|
+
}
|
|
445
|
+
if ((events & PollOut) && !state->write_pending) {
|
|
446
|
+
arm_write(state);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
bool remove(socket_t fd) override {
|
|
454
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
455
|
+
|
|
456
|
+
auto it = active_.find(fd);
|
|
457
|
+
if (it == active_.end()) return false;
|
|
458
|
+
|
|
459
|
+
auto* state = it->second;
|
|
460
|
+
state->removed = true;
|
|
461
|
+
state->desired_events = 0;
|
|
462
|
+
|
|
463
|
+
// Cancel pending I/O — completions will arrive with error status
|
|
464
|
+
// and will be discarded because state->removed is true.
|
|
465
|
+
if (state->read_pending || state->write_pending) {
|
|
466
|
+
CancelIoEx(reinterpret_cast<HANDLE>(fd), nullptr);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
active_.erase(it);
|
|
470
|
+
++removed_count_;
|
|
471
|
+
// Note: SocketState is NOT freed yet — it stays in all_states_ so
|
|
472
|
+
// that the OVERLAPPED pointers remain valid until cancelled
|
|
473
|
+
// completions are dequeued from GQCS. gc_removed_states() will
|
|
474
|
+
// free it once no I/O is in flight.
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
int wait(PollResult* results, int max_results, int timeout_ms) override {
|
|
479
|
+
int count = 0;
|
|
480
|
+
|
|
481
|
+
//------------------------------------------------------------------
|
|
482
|
+
// Step 1: Check WSAPoll-mode sockets (listen + connecting)
|
|
483
|
+
// Non-blocking check (timeout=0), typically <= 31 sockets
|
|
484
|
+
//------------------------------------------------------------------
|
|
485
|
+
{
|
|
486
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
487
|
+
count = check_wsapoll_sockets(results, max_results);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
//------------------------------------------------------------------
|
|
491
|
+
// Step 2: Wait for IOCP completions (connected data sockets)
|
|
492
|
+
// If Step 1 already found events, don't block (timeout=0)
|
|
493
|
+
//------------------------------------------------------------------
|
|
494
|
+
static constexpr ULONG MAX_ENTRIES = 128;
|
|
495
|
+
OVERLAPPED_ENTRY entries[MAX_ENTRIES];
|
|
496
|
+
ULONG iocp_count = 0;
|
|
497
|
+
|
|
498
|
+
DWORD iocp_timeout = (count > 0) ? 0 : static_cast<DWORD>(timeout_ms);
|
|
499
|
+
ULONG max_dequeue = static_cast<ULONG>(
|
|
500
|
+
(std::min)(static_cast<int>(MAX_ENTRIES), max_results - count));
|
|
501
|
+
|
|
502
|
+
if (max_dequeue > 0 && iocp_) {
|
|
503
|
+
BOOL ok = GetQueuedCompletionStatusEx(
|
|
504
|
+
iocp_, entries, max_dequeue, &iocp_count, iocp_timeout, FALSE);
|
|
505
|
+
|
|
506
|
+
if (!ok) {
|
|
507
|
+
DWORD err = GetLastError();
|
|
508
|
+
if (err != WAIT_TIMEOUT && err != ERROR_ABANDONED_WAIT_0) {
|
|
509
|
+
LOG_POLLER_ERROR("GQCS failed: " + std::to_string(err));
|
|
510
|
+
}
|
|
511
|
+
iocp_count = 0;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
//------------------------------------------------------------------
|
|
516
|
+
// Step 3: Process IOCP completions into PollResults
|
|
517
|
+
// Also GC removed states once their I/O has drained.
|
|
518
|
+
//------------------------------------------------------------------
|
|
519
|
+
if (iocp_count > 0) {
|
|
520
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
521
|
+
|
|
522
|
+
for (ULONG i = 0; i < iocp_count && count < max_results; ++i) {
|
|
523
|
+
if (!entries[i].lpOverlapped) continue;
|
|
524
|
+
|
|
525
|
+
auto* io = reinterpret_cast<IocpOverlapped*>(entries[i].lpOverlapped);
|
|
526
|
+
auto* state = io->state;
|
|
527
|
+
if (!state) continue;
|
|
528
|
+
|
|
529
|
+
// Mark the overlapped operation as completed (even for removed states,
|
|
530
|
+
// so GC can later free them once no I/O is in flight)
|
|
531
|
+
if (io->event_type & PollIn) state->read_pending = false;
|
|
532
|
+
if (io->event_type & PollOut) state->write_pending = false;
|
|
533
|
+
|
|
534
|
+
if (state->removed) continue;
|
|
535
|
+
|
|
536
|
+
// Check the NTSTATUS from the overlapped result
|
|
537
|
+
// 0 = STATUS_SUCCESS, non-zero = error (e.g. connection reset)
|
|
538
|
+
ULONG_PTR internal_status = io->overlapped.Internal;
|
|
539
|
+
|
|
540
|
+
uint32_t reported_events = 0;
|
|
541
|
+
if (internal_status == 0) {
|
|
542
|
+
// Success — report readiness for the event type we were watching
|
|
543
|
+
reported_events = io->event_type & state->desired_events;
|
|
544
|
+
|
|
545
|
+
// Re-arm overlapped I/O for next notification.
|
|
546
|
+
// Without this, the socket becomes deaf after the first
|
|
547
|
+
// completion because sync_poller() only calls modify()
|
|
548
|
+
// when desired_events change — which they don't for a
|
|
549
|
+
// socket that stays PollIn-only.
|
|
550
|
+
if ((io->event_type & PollIn) && (state->desired_events & PollIn)) {
|
|
551
|
+
arm_read(state);
|
|
552
|
+
}
|
|
553
|
+
if ((io->event_type & PollOut) && (state->desired_events & PollOut)) {
|
|
554
|
+
arm_write(state);
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
// I/O error (connection reset, cancelled, etc.)
|
|
558
|
+
reported_events = PollErr;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (reported_events == 0) continue;
|
|
562
|
+
|
|
563
|
+
// Merge events for the same fd (read + write may fire together)
|
|
564
|
+
bool merged = false;
|
|
565
|
+
for (int j = 0; j < count; ++j) {
|
|
566
|
+
if (results[j].fd == state->fd) {
|
|
567
|
+
results[j].events |= reported_events;
|
|
568
|
+
merged = true;
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (!merged) {
|
|
573
|
+
results[count].fd = state->fd;
|
|
574
|
+
results[count].events = reported_events;
|
|
575
|
+
++count;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// GC removed states whose cancelled I/O has fully drained.
|
|
580
|
+
// Threshold: at least 64 removed, or removed > half of total —
|
|
581
|
+
// avoids running the sweep on every wait() call.
|
|
582
|
+
if (removed_count_ >= 64 ||
|
|
583
|
+
(removed_count_ > 0 && removed_count_ * 2 >= all_states_.size())) {
|
|
584
|
+
gc_removed_states();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return count;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const char* name() const override { return "IOCP"; }
|
|
592
|
+
|
|
593
|
+
private:
|
|
594
|
+
HANDLE iocp_ = nullptr;
|
|
595
|
+
|
|
596
|
+
/// Socket operating mode
|
|
597
|
+
enum class SocketMode {
|
|
598
|
+
WsaPoll, ///< Listen/connecting socket — checked via WSAPoll
|
|
599
|
+
Iocp ///< Connected socket — uses overlapped zero-byte I/O
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
struct IocpSocketState; // Forward declaration
|
|
603
|
+
|
|
604
|
+
/// Extended OVERLAPPED that carries back-pointers for GQCS dispatch
|
|
605
|
+
struct IocpOverlapped {
|
|
606
|
+
OVERLAPPED overlapped; ///< Must be first member (cast compatibility)
|
|
607
|
+
IocpSocketState* state; ///< Owning socket state
|
|
608
|
+
uint32_t event_type; ///< PollIn or PollOut
|
|
609
|
+
|
|
610
|
+
IocpOverlapped() : state(nullptr), event_type(0) {
|
|
611
|
+
std::memset(&overlapped, 0, sizeof(overlapped));
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
/// Per-socket tracking state
|
|
616
|
+
struct IocpSocketState {
|
|
617
|
+
socket_t fd;
|
|
618
|
+
uint32_t desired_events;
|
|
619
|
+
SocketMode mode;
|
|
620
|
+
bool read_pending; ///< Zero-byte WSARecv in flight
|
|
621
|
+
bool write_pending; ///< Zero-byte WSASend in flight
|
|
622
|
+
bool removed; ///< Marked for removal
|
|
623
|
+
IocpOverlapped read_io; ///< Overlapped for read notification
|
|
624
|
+
IocpOverlapped write_io; ///< Overlapped for write notification
|
|
625
|
+
|
|
626
|
+
explicit IocpSocketState(socket_t f)
|
|
627
|
+
: fd(f), desired_events(0), mode(SocketMode::WsaPoll),
|
|
628
|
+
read_pending(false), write_pending(false), removed(false) {
|
|
629
|
+
read_io.state = this;
|
|
630
|
+
read_io.event_type = PollIn;
|
|
631
|
+
write_io.state = this;
|
|
632
|
+
write_io.event_type = PollOut;
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
std::mutex mutex_;
|
|
637
|
+
std::unordered_map<socket_t, IocpSocketState*> active_; ///< fd → state lookup
|
|
638
|
+
std::vector<std::unique_ptr<IocpSocketState>> all_states_; ///< Owns all states
|
|
639
|
+
std::vector<WSAPOLLFD> wsapoll_fds_; ///< Reusable WSAPoll buffer
|
|
640
|
+
size_t removed_count_ = 0; ///< Number of removed states awaiting GC
|
|
641
|
+
|
|
642
|
+
//----------------------------------------------------------------------
|
|
643
|
+
// Zero-byte overlapped I/O: readiness notification via IOCP
|
|
644
|
+
//----------------------------------------------------------------------
|
|
645
|
+
|
|
646
|
+
/// Post a zero-byte WSARecv. Completes when data is available to read.
|
|
647
|
+
/// Returns false if the socket is not connected (listen socket).
|
|
648
|
+
bool arm_read(IocpSocketState* state) {
|
|
649
|
+
if (state->read_pending) return true;
|
|
650
|
+
|
|
651
|
+
std::memset(&state->read_io.overlapped, 0, sizeof(OVERLAPPED));
|
|
652
|
+
|
|
653
|
+
WSABUF buf;
|
|
654
|
+
buf.buf = nullptr;
|
|
655
|
+
buf.len = 0;
|
|
656
|
+
DWORD flags = 0;
|
|
657
|
+
DWORD bytes = 0;
|
|
658
|
+
|
|
659
|
+
int ret = WSARecv(state->fd, &buf, 1, &bytes, &flags,
|
|
660
|
+
&state->read_io.overlapped, nullptr);
|
|
661
|
+
|
|
662
|
+
if (ret == 0) {
|
|
663
|
+
// Completed immediately — completion still posted to IOCP
|
|
664
|
+
state->read_pending = true;
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
int err = WSAGetLastError();
|
|
669
|
+
if (err == WSA_IO_PENDING) {
|
|
670
|
+
state->read_pending = true;
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// WSAENOTCONN (10057) = listen socket, can't do WSARecv
|
|
675
|
+
// Other errors also mean we can't use IOCP mode for this socket
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/// Post a zero-byte WSASend. Completes when send buffer has space.
|
|
680
|
+
/// Also wakes GQCS when called from another thread (via modify/send_to_peer).
|
|
681
|
+
bool arm_write(IocpSocketState* state) {
|
|
682
|
+
if (state->write_pending) return true;
|
|
683
|
+
|
|
684
|
+
std::memset(&state->write_io.overlapped, 0, sizeof(OVERLAPPED));
|
|
685
|
+
|
|
686
|
+
WSABUF buf;
|
|
687
|
+
buf.buf = nullptr;
|
|
688
|
+
buf.len = 0;
|
|
689
|
+
DWORD bytes = 0;
|
|
690
|
+
|
|
691
|
+
int ret = WSASend(state->fd, &buf, 1, &bytes, 0,
|
|
692
|
+
&state->write_io.overlapped, nullptr);
|
|
693
|
+
|
|
694
|
+
if (ret == 0) {
|
|
695
|
+
state->write_pending = true;
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
int err = WSAGetLastError();
|
|
700
|
+
if (err == WSA_IO_PENDING) {
|
|
701
|
+
state->write_pending = true;
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
//----------------------------------------------------------------------
|
|
709
|
+
// GC: free removed states whose I/O has fully drained. Called under mutex.
|
|
710
|
+
//----------------------------------------------------------------------
|
|
711
|
+
|
|
712
|
+
void gc_removed_states() {
|
|
713
|
+
if (removed_count_ == 0) return;
|
|
714
|
+
|
|
715
|
+
size_t before = all_states_.size();
|
|
716
|
+
all_states_.erase(
|
|
717
|
+
std::remove_if(all_states_.begin(), all_states_.end(),
|
|
718
|
+
[](const std::unique_ptr<IocpSocketState>& s) {
|
|
719
|
+
return s->removed && !s->read_pending && !s->write_pending;
|
|
720
|
+
}),
|
|
721
|
+
all_states_.end());
|
|
722
|
+
|
|
723
|
+
size_t freed = before - all_states_.size();
|
|
724
|
+
if (freed > 0) {
|
|
725
|
+
removed_count_ -= freed;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
//----------------------------------------------------------------------
|
|
730
|
+
// WSAPoll fallback for listen + connecting sockets
|
|
731
|
+
//----------------------------------------------------------------------
|
|
732
|
+
|
|
733
|
+
/// Non-blocking check of WSAPoll-mode sockets. Called under mutex.
|
|
734
|
+
int check_wsapoll_sockets(PollResult* results, int max_results) {
|
|
735
|
+
wsapoll_fds_.clear();
|
|
736
|
+
|
|
737
|
+
for (auto& [fd, state] : active_) {
|
|
738
|
+
if (state->removed || state->mode != SocketMode::WsaPoll) continue;
|
|
739
|
+
|
|
740
|
+
WSAPOLLFD pfd;
|
|
741
|
+
pfd.fd = fd;
|
|
742
|
+
pfd.events = 0;
|
|
743
|
+
if (state->desired_events & PollIn) pfd.events |= POLLIN;
|
|
744
|
+
if (state->desired_events & PollOut) pfd.events |= POLLOUT;
|
|
745
|
+
pfd.revents = 0;
|
|
746
|
+
|
|
747
|
+
wsapoll_fds_.push_back(pfd);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (wsapoll_fds_.empty()) return 0;
|
|
751
|
+
|
|
752
|
+
// Non-blocking poll (timeout = 0)
|
|
753
|
+
int n = WSAPoll(wsapoll_fds_.data(),
|
|
754
|
+
static_cast<ULONG>(wsapoll_fds_.size()), 0);
|
|
755
|
+
if (n <= 0) return 0;
|
|
756
|
+
|
|
757
|
+
int count = 0;
|
|
758
|
+
for (auto& pfd : wsapoll_fds_) {
|
|
759
|
+
if (count >= max_results) break;
|
|
760
|
+
if (pfd.revents == 0) continue;
|
|
761
|
+
|
|
762
|
+
uint32_t events = 0;
|
|
763
|
+
if (pfd.revents & POLLIN) events |= PollIn;
|
|
764
|
+
if (pfd.revents & POLLOUT) events |= PollOut;
|
|
765
|
+
if (pfd.revents & POLLERR) events |= PollErr;
|
|
766
|
+
if (pfd.revents & POLLHUP) events |= PollHup;
|
|
767
|
+
|
|
768
|
+
results[count].fd = pfd.fd;
|
|
769
|
+
results[count].events = events;
|
|
770
|
+
++count;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return count;
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
#endif // POLLER_USE_IOCP
|
|
778
|
+
|
|
779
|
+
//=============================================================================
|
|
780
|
+
// POSIX fallback: poll() implementation
|
|
781
|
+
//=============================================================================
|
|
782
|
+
|
|
783
|
+
#if defined(POLLER_USE_POLL)
|
|
784
|
+
|
|
785
|
+
#include <mutex>
|
|
786
|
+
#include <unordered_map>
|
|
787
|
+
#include <vector>
|
|
788
|
+
|
|
789
|
+
class PollPoller final : public IOPoller {
|
|
790
|
+
public:
|
|
791
|
+
PollPoller() {
|
|
792
|
+
LOG_POLLER_INFO("Created poll() poller");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
~PollPoller() override = default;
|
|
796
|
+
|
|
797
|
+
bool add(socket_t fd, uint32_t events) override {
|
|
798
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
799
|
+
registered_[fd] = events;
|
|
800
|
+
dirty_ = true;
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
bool modify(socket_t fd, uint32_t events) override {
|
|
805
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
806
|
+
auto it = registered_.find(fd);
|
|
807
|
+
if (it == registered_.end()) return false;
|
|
808
|
+
it->second = events;
|
|
809
|
+
dirty_ = true;
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
bool remove(socket_t fd) override {
|
|
814
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
815
|
+
auto erased = registered_.erase(fd);
|
|
816
|
+
dirty_ = true;
|
|
817
|
+
return erased > 0;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
int wait(PollResult* results, int max_results, int timeout_ms) override {
|
|
821
|
+
{
|
|
822
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
823
|
+
if (dirty_) {
|
|
824
|
+
rebuild_pollfd_array();
|
|
825
|
+
dirty_ = false;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (pollfds_.empty()) {
|
|
830
|
+
if (timeout_ms > 0) {
|
|
831
|
+
struct timespec ts;
|
|
832
|
+
ts.tv_sec = timeout_ms / 1000;
|
|
833
|
+
ts.tv_nsec = (timeout_ms % 1000) * 1000000L;
|
|
834
|
+
nanosleep(&ts, nullptr);
|
|
835
|
+
}
|
|
836
|
+
return 0;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
int n = poll(pollfds_.data(), static_cast<nfds_t>(pollfds_.size()), timeout_ms);
|
|
840
|
+
|
|
841
|
+
if (n < 0) {
|
|
842
|
+
if (errno != EINTR) {
|
|
843
|
+
LOG_POLLER_ERROR("poll() failed: " + std::string(strerror(errno)));
|
|
844
|
+
}
|
|
845
|
+
return -1;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (n == 0) return 0;
|
|
849
|
+
|
|
850
|
+
int count = 0;
|
|
851
|
+
for (size_t i = 0; i < pollfds_.size() && count < max_results; ++i) {
|
|
852
|
+
if (pollfds_[i].revents == 0) continue;
|
|
853
|
+
|
|
854
|
+
results[count].fd = pollfds_[i].fd;
|
|
855
|
+
results[count].events = 0;
|
|
856
|
+
if (pollfds_[i].revents & POLLIN) results[count].events |= PollIn;
|
|
857
|
+
if (pollfds_[i].revents & POLLOUT) results[count].events |= PollOut;
|
|
858
|
+
if (pollfds_[i].revents & POLLERR) results[count].events |= PollErr;
|
|
859
|
+
if (pollfds_[i].revents & POLLHUP) results[count].events |= PollHup;
|
|
860
|
+
++count;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
return count;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const char* name() const override { return "poll"; }
|
|
867
|
+
|
|
868
|
+
private:
|
|
869
|
+
std::mutex mutex_;
|
|
870
|
+
std::unordered_map<socket_t, uint32_t> registered_;
|
|
871
|
+
std::vector<struct pollfd> pollfds_;
|
|
872
|
+
bool dirty_ = false;
|
|
873
|
+
|
|
874
|
+
void rebuild_pollfd_array() {
|
|
875
|
+
pollfds_.clear();
|
|
876
|
+
pollfds_.reserve(registered_.size());
|
|
877
|
+
|
|
878
|
+
for (auto& [fd, events] : registered_) {
|
|
879
|
+
struct pollfd pfd;
|
|
880
|
+
pfd.fd = fd;
|
|
881
|
+
pfd.events = 0;
|
|
882
|
+
if (events & PollIn) pfd.events |= POLLIN;
|
|
883
|
+
if (events & PollOut) pfd.events |= POLLOUT;
|
|
884
|
+
pfd.revents = 0;
|
|
885
|
+
pollfds_.push_back(pfd);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
#endif // POLLER_USE_POLL
|
|
891
|
+
|
|
892
|
+
//=============================================================================
|
|
893
|
+
// Factory
|
|
894
|
+
//=============================================================================
|
|
895
|
+
|
|
896
|
+
std::unique_ptr<IOPoller> IOPoller::create() {
|
|
897
|
+
#if defined(POLLER_USE_EPOLL)
|
|
898
|
+
return std::make_unique<EpollPoller>();
|
|
899
|
+
#elif defined(POLLER_USE_KQUEUE)
|
|
900
|
+
return std::make_unique<KqueuePoller>();
|
|
901
|
+
#elif defined(POLLER_USE_IOCP)
|
|
902
|
+
return std::make_unique<IocpPoller>();
|
|
903
|
+
#elif defined(POLLER_USE_POLL)
|
|
904
|
+
return std::make_unique<PollPoller>();
|
|
905
|
+
#else
|
|
906
|
+
#error "No I/O multiplexer available for this platform"
|
|
907
|
+
#endif
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
} // namespace librats
|