Loading src/event/kqueue.cpp +23 −7 Original line number Diff line number Diff line Loading @@ -367,6 +367,29 @@ namespace netplus { // accept() receives packets and either routes to existing children // or creates new ones (owned by parent). We don't register QUIC // children in kqueue/CONNECTIONS since they share the parent fd. // // Rearm on scope exit no matter what -- even if accept() // throws (e.g. NetException::Note from a blocked sendto() // while replying to a new connection's Initial packet). // Mirrors epoll.cpp's RearmOnExit guard for the identical // reason: the listener fd is EV_ONESHOT'd, so without an // exception-safe rearm here, an exception propagating out // of accept() skips the old plain post-loop rearm entirely // -- permanently disabling this fd (no kevent ever fires // for it again) until the whole process restarts, which is // exactly the production symptom this fixes (this is also // why the bug never reproduced on the epoll/Linux side). int qfd = _ServerSocket->fd(); struct RearmOnExit { KeventBatch* batch; int fd; ~RearmOnExit() { try { batch->add_change(fd, EVFILT_READ, EV_ADD | EV_ONESHOT); batch->flush(); } catch (...) {} } } rearm{&_batch, qfd}; do { std::unique_ptr<socket> quic_csock; _ServerSocket->accept(quic_csock, true); Loading @@ -379,13 +402,6 @@ namespace netplus { break; } while (true); // Re-arm the EV_ONESHOT listener registration (see the // ADD in runEventloop) now that this drain round is // done, so the next datagram wakes a thread again — // mirrors epoll.cpp's EpollArmGuard rearm for the same // listener fd. _batch.add_change(_ServerSocket->fd(), EVFILT_READ, EV_ADD | EV_ONESHOT); _batch.flush(); return; } Loading Loading
src/event/kqueue.cpp +23 −7 Original line number Diff line number Diff line Loading @@ -367,6 +367,29 @@ namespace netplus { // accept() receives packets and either routes to existing children // or creates new ones (owned by parent). We don't register QUIC // children in kqueue/CONNECTIONS since they share the parent fd. // // Rearm on scope exit no matter what -- even if accept() // throws (e.g. NetException::Note from a blocked sendto() // while replying to a new connection's Initial packet). // Mirrors epoll.cpp's RearmOnExit guard for the identical // reason: the listener fd is EV_ONESHOT'd, so without an // exception-safe rearm here, an exception propagating out // of accept() skips the old plain post-loop rearm entirely // -- permanently disabling this fd (no kevent ever fires // for it again) until the whole process restarts, which is // exactly the production symptom this fixes (this is also // why the bug never reproduced on the epoll/Linux side). int qfd = _ServerSocket->fd(); struct RearmOnExit { KeventBatch* batch; int fd; ~RearmOnExit() { try { batch->add_change(fd, EVFILT_READ, EV_ADD | EV_ONESHOT); batch->flush(); } catch (...) {} } } rearm{&_batch, qfd}; do { std::unique_ptr<socket> quic_csock; _ServerSocket->accept(quic_csock, true); Loading @@ -379,13 +402,6 @@ namespace netplus { break; } while (true); // Re-arm the EV_ONESHOT listener registration (see the // ADD in runEventloop) now that this drain round is // done, so the next datagram wakes a thread again — // mirrors epoll.cpp's EpollArmGuard rearm for the same // listener fd. _batch.add_change(_ServerSocket->fd(), EVFILT_READ, EV_ADD | EV_ONESHOT); _batch.flush(); return; } Loading