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.
@@ -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