Re: Kqueues and fork

From: Gleb Popov <arrowd_at_freebsd.org>
Date: Fri, 29 Aug 2025 10:57:52 UTC
On Wed, Aug 20, 2025 at 2:12 PM Konstantin Belousov <kostikbel@gmail.com> wrote:
>
> Is this what the app writers want?

I was one of those who asked Konstantin about this feature.

My context is the dbus-daemon program, which is essentially an RPC
server working over unix sockets.
It has an abstraction called "pollable set" that is implemented via
epoll(7) on Linux and poll(2) as a fallback.
I developed the kqueue(2) based implementation which was
straightforward, but then I bumped into
the mentioned difference on fork.

The daemon does basically this:

parent: kqueuex(0x1)                 = 3 (0x3)
parent: fork()
parent: bind(4,{ AF_UNIX "/var/run/dbus/system_bus_socket" },33)
parent: listen(4,128)
parent: kevent(3, { 4,EVFILT_READ}, ... )

child: connect(...) = 5
child: kevent(3,{ 5,EVFILT_READ}, ... )

This obviously doesn't work for kqueue without KQUEUE_CPONFORK, but
works for Linux, because epoll fds are
inherited by children by default. At the same time dbus-daemon doesn't
really need any intricate events sharing.

My theory is that the code is written this way simply because the
software is primarily developed on Linux and
dbus-daemon authors didn't notice that they are leaking the epoll fd
into the child. I imagine that there might be
other software in the wild that accidentally shares the epoll fd.

With Konstantin's patch (and after some bugfixing on my side of
things) the kqueue-based implementation worked
flawlessly. All the extensive dbus tests pass with no overhead. The
proposed KQUEUE_CPONFORK flag
would also be really useful for the devel/libepoll-shim library, which
tries its best to emulate epoll(7) via kqueue(2),
but can't do anything about consumers that expect the fd to live after
calling fork()