Kqueue vs epoll

Not sure if this is better off in Development category, but for now decided to play it safe. What do you think about this argument?
https://ariadne.space/2021/06/06/actually-bsd-kqueue-is-a-mountain-of-technical-debt/

Lack of complete similar system in Haiku was also mentioned by @kallisti5 with regard to Mio port - https://github.com/tokio-rs/mio/issues/1472

Posting it here because devs seem to still have WIP task to improve similar mechanism in Haiku
https://dev.haiku-os.org/ticket/16846

i have built a lot of stuff on top of epoll… but I guess the better discussion would be about io_uring (and eBPF).

1 Like

The way it reads to me, it’s someone who’s looking for a story to use the term “technical debt” in. Not that having read it I’m any the wiser about what it means.

The gist of as best as I can make out, is that it’s better represent everything - signals for example - as file descriptors, so everything works with epoll(). So epoll() is great because it forces the kernel developer to make a file descriptor like device for signals, while kqueue is full of “technical debt” because it just handles signals all on its own.

I have not used kqueue (not doing a lot of *BSD programming here) but I have used epoll a lot lately, and also done some multi-threaded programming in Haiku where I think we have a bit different challenges.

Here are some things I don’t like about epoll.

In my application I use several sockets, but I also use threads. I wanted to do something like “wait until either a socket is readable, or a mutex is unlocked by another thread”. With epoll, this is not possible: mutexes are not file descriptors. So, I had to replace some mutex in my application with eventfd instead. It is not as nice an API to use. Fortunately I could put a C++ wrapper above it.

In Haiku context, a typical problem that would be great to have a solution for is "wait until my thread receives a BMessage, or until a socket becomes readable. To solve this, we have added the wait_for_objects function to our kernel. As you can see, it takes a kqueue-like approach as to what it can wait on. It allows to wait on file descriptors, semaphores, ports (the low level thing on which BMessage is built), and threads. It would make no sense for semaphore or threads to be exposed as file descriptors, I think.

(note: the wait_for_objects API has other limitations similar to the differnce between poll and epoll, but that I think is outside the scope of this discussion).

If you look at epoll vs kqueue in isolation, it may seem that epoll is better. If you look at them both in a context where you need to support both in your code, you are limited to the common denominator of both, which means your code will probably already be architectured to use file descriptors for everything. But if you are designing your application from scratch and planning to use only one of them, something like wait_for_objects enable you to use semaphores and ports normally in all other places in your app, and wait on them at the same time as you wait on files.

To put it in another way, it means the kernel provides an high level API for many things directly (acquiring and releasing a semaphore, for example). Whereas if you go with epoll, you have to use the “everything is a file descriptor” approach pretty much everywhere.

This is actually quite visible already: in Linux, if you want to be notified about changes to a file, you use inotify (a file descriptor). In Haiku, you use the node monitor (it delivers the data using a port/BMessage).

6 Likes

I have an idea to use port messages for all locking events like acquire semaphore, wait for file read/write, opening socket etc. It allows to use existing Application Kit event loop and have no restart overhead like wait_for_objects.

1 Like