There also is the thing that, sometimes, different types of event are identical types, lower down in the stack. A simple example are keyboard and mouse events generated from USB devices.
Why would the low-level USB stack have to do a switch on device type to figure out what subsystem to post an event to?
Think about async programming you have a single `await` keyword that can await lots of different things.
One way to think about this is whether all these non-file fds are useful for a variety of generic operations, or whether they're only used for epoll and one-off system calls or ioctls specific to that type of fd. If it's the latter, it seems hard to believe that there actually is some kind of composability advantage.
So, what can you do with them?
1. You can use these fds with poll()/select(), not just epoll. That's not a big deal, since nobody really should be using either of them. But it does also mean that you should ideally be able to use them with any future event notification syscalls as well. And the invention of new mechanisms is not hypothetical, since Linux added io_uring. I'd be curious to know how well io_uring supports arbitrary fd types, and whether they got that support "for free" or if io_uring needs to be extended for every new fd type.
2. You can typically read() structured data from those fds describing the specific event. With kqueue all that data would need to be passed through struct kevent, which needs to be fixed size and generic enough to be shared between all the different event types.
3. You can pass individual fds between processes, either via UDS or fork(). I expect you would not be able to do that for individual kqueue filters.
4. You can close() the fd to release the underlying kernel resource. This doesn't seem interesting, just listing it for completeness.
So there's probably enough smoke there that one could at least argue the case. It's too bad the author didn't.
What's wrong with poll(), at least for smaller values of nfds? And what should one use instead when writing POSIX-compatible portable code, where epoll() and kqueue() don't exist?
The thread is an interesting read as it sounds like the naive approach has negative performance implications, knowing openbsd I suspect the main motivation was to reduce maintenance burden, that is, make it easier to reason about the internals of event driven mechanisms by only having one such system to worry about.
It's absolutely true that within the bounds of what the OS supports, both interfaces are complete. But that's sort of a specious point. One requires a giant tree of extra types be amended every time something changes, while the other exploits a nearly-five-decade old abstraction that already encapsulates the idea of "something on which you might want to wait".
That's pretty much the definition of technical debt though. "This interface works fine for you if you take steps to handle it specially according to its requirements". It makes kqueue into a point of friction for everything in the system wanting to provide a file descriptor-like API.
Yes, but those slashes are showing the lie in the statement. Letting files be polled/selected isn't "free", but it's standard. The poll() method has been in struct file_operations for literally decades[1]. Adding "epoll support" requires no meaningful changes to the API, for any device that ever supported select().
That kind of evolutionary flexibility (the opposite of "technical debt") is generally regarded as good design. And it's something that epoll had designed in and something that queue lacks, having decided to go its own way. And it's not unreasonable to call that out, IMHO.
[1] It's present in commit 1da177e4c3f4 ("Linux-2.6.12-rc2"), which is the very first git commit. I know people maintain archives of older trees, but I'm too lazy to dig. Suffice it to say that epoll relies on an interface that is likely older than many of the driver developers using it.
I don't really understand what argument you're making. Is io_uring also a bad design because it requires new file_operations?
Which, again, is a statement that gets to the root of the idea of "technical debt". You can excuse almost anything like that. It still doesn't make it better than a design that works by default. I remain shocked that this seems to be controversial.
FWIW: io_uring has been very loudly criticized for being hard to implement, maintain and use, via some of this same logic, yes. This isn't a senseless platform flame. Linux does bad stuff too. There are good designs and bad designs everywhere, and io_uring is probably not one (though to be fair it does have some extremely attractive performance characteristics, so I guess one might be tempted to forgive a few warts in the interface layers).
> I guess one might be tempted to forgive a few warts in the interface layers
... well, yeah, that's exactly my sentiment about kqueue here. What you're talking about is basically a small wart that no one's bothered to address because it's inconsequential.
The kernel doesn't magically know whether your device file has data available to read, your device file has to define what that means. That's all I'm referring to. Hooking that up to kqueue usually involves writing 5-10 lines of code.