Behavioral difference between Linux and FreeBSD poll()

From: Gleb Popov <arrowd_at_freebsd.org>
Date: Wed, 04 Jun 2025 08:04:49 UTC
Hey hackers.

I was debugging two seemingly unrelated issues regarding busy-looping
in D-Bus and Qt applications and bumped into a common root problem -
our poll() implementation doesn't return POLLERR when callers expect
that. The standard is indeed blurry on this topic [1] and does not
specify when exactly POLLERR should be delivered.

Here is a program that demonstrates the difference: https://arrowd.name/poll.c
On Linux I only see

28 (POLLOUT | POLLERR | POLLHUP)
28 (POLLOUT | POLLERR | POLLHUP)
...

and on FreeBSD I get

20 (POLLOUT|POLLWRNORM|POLLHUP)
16 (POLLHUP)
16 (POLLHUP)
...

Now here are places in the code expecting POLLERR:
- Qt [2] + [3], [4]
- D-Bus [5]

On FreeBSD these places result in busy-loop, because poll() returns
immediately, but the caller code does not handle the revents = POLLHUP
case properly and gets back to calling poll() again.

My question is what would be a correct way to fix these issues. Should
I patch poll() consumers to better handle POLLHUP or should we rather
fix the kernel side to return POLLERR?

Grepping through the src reveals that we have places, where calling
code requests POLLERR in .events, which makes me think that it is the
kernel that should be fixed. But I'm not a real kernel hacker.

P.S. Interestingly, the standard says that POLLOUT and POLLHUP are
mutually exclusive, but both Linux and FreeBSD send them together.

[1] https://pubs.opengroup.org/onlinepubs/009696799/functions/poll.html
[2] https://github.com/qt/qtbase/blob/4db3961ee140867e14f8e1d20173e85060bc6c50/src/corelib/kernel/qeventdispatcher_glib.cpp#L60
[3] https://github.com/qt/qtbase/blob/4db3961ee140867e14f8e1d20173e85060bc6c50/src/corelib/kernel/qeventdispatcher_glib.cpp#L437
[4] https://github.com/qt/qtbase/blob/4db3961ee140867e14f8e1d20173e85060bc6c50/src/network/socket/qnativesocketengine_unix.cpp#L1383
[5] https://gitlab.freedesktop.org/dbus/dbus/-/blob/6bba6c58c5635bc123cb565ee1aac0f12cd980d3/dbus/dbus-transport-socket.c?page=2#L1207-1212