Emulated Netlink sockets cause infinite busy loops under event-loop#112
Emulated Netlink sockets cause infinite busy loops under event-loop#112doanbaotrung wants to merge 1 commit into
Conversation
|
Fix #105 |
There was a problem hiding this comment.
Netlink busy-loop fix review. The core approach is sound: keeping the pipe write end open and signaling readiness only when a response is buffered fixes the spurious peer-closed POLLIN that spun avahi-daemon. A few correctness and robustness points below.
Append Close #105 at the end of git commit messages.
| @@ -605,6 +631,9 @@ int64_t netlink_sendmsg(int guest_fd, guest_t *g, uint64_t msg_gva, int flags) | |||
| } | |||
|
|
|||
| int ret = nl_process_request(ns, req, rlen); | |||
There was a problem hiding this comment.
netlink_signal_readable() writes a token on every successful send. Since each request overwrites ns->buf (buf_pos reset to 0), repeated sends without an intervening recv keep pushing bytes into the pipe until it fills and the writes silently drop on EAGAIN. Harmless today, but it makes the pipe level drift from ns->buf state and can mask a real missed-wakeup later. Signal only on the empty-to-nonempty transition: capture bool was_empty = ns->buf_pos >= ns->buf_len before nl_process_request() and write only when it goes non-empty. Same at line 667 (netlink_send).
| if (pipe(pipefd) < 0) | ||
| return -LINUX_EMFILE; | ||
|
|
||
| if (fd_set_nonblock(pipefd[0]) < 0 || fd_set_nonblock(pipefd[1]) < 0) { |
There was a problem hiding this comment.
fd_set_nonblock() failure returns -LINUX_EMFILE, which is misleading -- this is an fcntl failure, not fd exhaustion. Preserve the real errno (return -linux_errno() from the failing call) after closing both pipe fds.
bcf3579 to
d5fcee1
Compare
|
Address netlink review feedback on busy-loop fix
|
| while (ns->buf_pos >= ns->buf_len) { | ||
| bool nonblock = (flags & LINUX_MSG_DONTWAIT) || | ||
| (fd_table[guest_fd].linux_flags & LINUX_O_NONBLOCK); |
c6fedec to
6f282ae
Compare
jserv
left a comment
There was a problem hiding this comment.
Be aware of preferable code style.
| if (was_empty && ns->buf_pos < ns->buf_len) { | ||
| netlink_signal_readable(ns); | ||
| } |
There was a problem hiding this comment.
Use brackets only when necessary.
| if (was_empty && ns->buf_pos < ns->buf_len) { | ||
| netlink_signal_readable(ns); | ||
| } |
There was a problem hiding this comment.
Use brackets only when necessary.
| if (errno == EINTR) { | ||
| return -LINUX_EINTR; | ||
| } |
There was a problem hiding this comment.
Use brackets only when necessary.
| if (ns->buf_pos >= ns->buf_len) { | ||
| netlink_clear_readable(ns); | ||
| } |
There was a problem hiding this comment.
Use brackets only when necessary.
| if (errno == EINTR) { | ||
| return -LINUX_EINTR; | ||
| } |
There was a problem hiding this comment.
Use brackets only when necessary.
| if (ns->buf_pos >= ns->buf_len) { | ||
| netlink_clear_readable(ns); | ||
| } |
There was a problem hiding this comment.
Use brackets only when necessary.
When guest applications (such as avahi-daemon) run event loops that wait for netlink messages using ppoll() or select(), the emulated descriptor immediately returns POLLIN because the write end is closed. The application's subsequent recvmsg or read call returns 0 bytes. Because no sender credentials can be resolved from a 0-byte read, the application ignores the read and immediately queries ppoll() again. This causes the guest application to spin in an infinite busy-loop consuming 100% CPU. To address this, implement a non-blocking self-pipe signaling mechanism inside src/syscall/netlink.c: 1. Store and use pipe_rd in netlink_state_t directly to avoid lockless global state reads in netlink_clear_readable. 2. Honor MSG_DONTWAIT and O_NONBLOCK flags and implement proper blocking semantics by polling the pipe outside nl_lock. 3. Signal readability only on empty-to-nonempty transition to prevent pipe buffer drift. 4. Handle zero-length reads/recvs before empty-buffer check. Close sysprog21#105
6f282ae to
035e92a
Compare
|
Noted. I'll aware of using brackets only when necessary. |
When guest applications (such as avahi-daemon) run event loops that
wait for netlink messages using ppoll() or select(), the emulated
descriptor immediately returns POLLIN because the write end is
closed. The application's subsequent recvmsg or read call returns 0
bytes.
Because no sender credentials can be resolved from a 0-byte read,
the application ignores the read and immediately queries ppoll()
again. This causes the guest application to spin in an infinite
busy-loop consuming 100% CPU.
To address this, implement a non-blocking self-pipe signaling
mechanism inside src/syscall/netlink.c:
lockless global state reads in netlink_clear_readable.
blocking semantics by polling the pipe outside nl_lock.
prevent pipe buffer drift.
Close #105
Summary by cubic
Fixes infinite busy loops in emulated Netlink sockets by signaling readiness only when data exists and enforcing correct blocking. Stops 100% CPU spin in ppoll/select loops (e.g., avahi-daemon) and honors MSG_DONTWAIT/O_NONBLOCK.
nl_lock; pass flags fromsys_recvfromtonetlink_recv.Written for commit 035e92a. Summary will update on new commits.