You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
rmw_wait issues one syscall per entity on every call, not per ready entity #51
rmw_wait makes three full passes over every entity in the wait set on every call, even when a single socket has data. For a node with a large wait set this dominates CPU.
The passes:
Drain every subscription, service and client socket (rmw_wait.cpp#L175-L199). Nearly all of these recvmsg calls return EAGAIN.
Re-arm every fd with epoll_ctl(EPOLL_CTL_ADD) (rmw_wait.cpp#L307-L349). Nearly all return EEXIST.
For a wait set holding 719 entities that is about 2150 syscalls per rmw_wait call.
Measured on an 8-core x86 machine, a node subscribed to roughly 700 topics sits at 66% CPU with 30% of a core in kernel time. At ~1us for a trivial syscall that is ~300k syscalls/s, so ~140 wait cycles/s, which matches the expected executor rate.
CPU tracks wait-set size closely:
entities in wait set
CPU
719
66%
654
61%
250
52%
15
23%
The user/system split is about 50/50, which is the shape of a syscall storm rather than real work.
Why this is avoidable
epoll_wait already fills ready_events with exactly the fds that have data. Both drain loops throw that away and poll everything instead.
Suggested fix
Post-epoll drain: iterate ready_events rather than the full entity list.
Pre-epoll drain: replace the full sweep with one epoll_wait(..., timeout=0) to find ready fds, then drain only those. One syscall instead of N.
Re-arm: track armed fds in UdsWaitSet and call epoll_ctl only when an entity joins or leaves.
The re-arm currently uses EPOLL_CTL_ADD returning EEXIST as a cheap way to cope with an fd number reused after its previous owner closed. A replacement has to keep that property, for example by storing the (fd, entity) pair and re-arming when it changes.
Together this removes roughly 1430 syscalls from the drains and the remaining ~719 from the re-arm.
Fix in flight: PR #57 (fix/wait-drain-only-ready-fds) converts every wait from O(wait-set entities) syscalls to O(ready fds) via an armed-fd cache, and also carries the zero-length-datagram consume fix. Sequencing: it conflicts with #60's rewrite of rmw_wait.cpp, so it gets reconciled on the integration branch and merged after #60.
Activity
benaliabderrahmane commentedon Aug 13, 2026
Fix in flight: PR #57 (
fix/wait-drain-only-ready-fds) converts every wait from O(wait-set entities) syscalls to O(ready fds) via an armed-fd cache, and also carries the zero-length-datagram consume fix. Sequencing: it conflicts with #60's rewrite ofrmw_wait.cpp, so it gets reconciled on the integration branch and merged after #60.