Loading src/event/epoll.cpp +24 −6 Original line number Diff line number Diff line Loading @@ -809,14 +809,32 @@ namespace netplus { // triggered + shared epoll fd meant every datagram woke all // `threads` workers, and only one could ever do anything // useful while the rest contended on that single mutex for // nothing. Each connection now has its own quic_mtx(), and // nothing. // // Each connection now has its own quic_mtx(), and // _child_connections lookups/mutations go through their own // short-lived _registry_mutex (see quic::accept()), so // multiple threads calling accept() concurrently on this fd // now actually process different connections in parallel // instead of serializing on one lock — recvmmsg on the same // fd from multiple threads is safe (the kernel hands out // disjoint datagrams, never duplicates). // different connections CAN be processed by different threads // in parallel instead of serializing on one lock. But plain // EPOLLIN without ONESHOT still wakes every worker per event // (thundering herd): measured under concurrent load (24 // simultaneous new connections, see quic_concurrent_test) this // made things drastically worse, not better — `threads` // workers (one per core) all piling into accept()/recvmmsg for // the same handful of datagrams starved the CPU time actual // packet processing needed, cascading into handshake timeouts. // EPOLLEXCLUSIVE (Linux >= 4.5) is the fix purpose-built for // this: only one waiter is woken per readiness event instead // of all of them, but — unlike EPOLLONESHOT — no explicit // rearm is needed and a different thread is free to pick up // the next event, so cross-connection parallelism is kept // without the herd. (Not recommended to combine with // EPOLLONESHOT, which is why this is the only flag added.) // Scoped to QUIC only — TCP/SSL listeners weren't part of what // this was measured against, so left as before. if (ss->_Type == sockettype::QUIC) { setevent.events |= EPOLLEXCLUSIVE; } setevent.data.fd = ss->fd(); if (epoll_ctl(_pollFD, EPOLL_CTL_ADD, ss->fd(), &setevent) < 0) { Loading Loading
src/event/epoll.cpp +24 −6 Original line number Diff line number Diff line Loading @@ -809,14 +809,32 @@ namespace netplus { // triggered + shared epoll fd meant every datagram woke all // `threads` workers, and only one could ever do anything // useful while the rest contended on that single mutex for // nothing. Each connection now has its own quic_mtx(), and // nothing. // // Each connection now has its own quic_mtx(), and // _child_connections lookups/mutations go through their own // short-lived _registry_mutex (see quic::accept()), so // multiple threads calling accept() concurrently on this fd // now actually process different connections in parallel // instead of serializing on one lock — recvmmsg on the same // fd from multiple threads is safe (the kernel hands out // disjoint datagrams, never duplicates). // different connections CAN be processed by different threads // in parallel instead of serializing on one lock. But plain // EPOLLIN without ONESHOT still wakes every worker per event // (thundering herd): measured under concurrent load (24 // simultaneous new connections, see quic_concurrent_test) this // made things drastically worse, not better — `threads` // workers (one per core) all piling into accept()/recvmmsg for // the same handful of datagrams starved the CPU time actual // packet processing needed, cascading into handshake timeouts. // EPOLLEXCLUSIVE (Linux >= 4.5) is the fix purpose-built for // this: only one waiter is woken per readiness event instead // of all of them, but — unlike EPOLLONESHOT — no explicit // rearm is needed and a different thread is free to pick up // the next event, so cross-connection parallelism is kept // without the herd. (Not recommended to combine with // EPOLLONESHOT, which is why this is the only flag added.) // Scoped to QUIC only — TCP/SSL listeners weren't part of what // this was measured against, so left as before. if (ss->_Type == sockettype::QUIC) { setevent.events |= EPOLLEXCLUSIVE; } setevent.data.fd = ss->fd(); if (epoll_ctl(_pollFD, EPOLL_CTL_ADD, ss->fd(), &setevent) < 0) { Loading