A new kernel polling interface
lwn.net
lwn.net
Everyone else (IOCP, kqueue, etc) solved this problem. And then, Linux created the famously broken epoll().
There is no reference to the context behind rejecting a kqueue-like API (opinion was that it looks a lot like ioctl - very true)
There is no rationale for why IOCP or kqueue is obviously a better design for Linux compared to epoll etc etc.
But just chalk it up to NIH, because that is the easiest and cheapest explanation for just about anything.
FWIW in the context of network APIs before Kqueue, there was the then-standard STREAMS, and the decisionmaking that led to Linux having epoll is the same that led to it avoiding the tragedy that was STREAMS. But if Linux had STREAMS today and not epoll or kqueue, the people who'd cry NIH and not-following-standards today would instead by crying about how much STREAMS sucks, and why Linux doesn't do its own thing
I can, I think, partially address this, though unfortunately in the negative.
My understanding of IOCP is that the model requires the following: if you want to, say, receive data on a socket, you supply the IOCP w/ a buffer/length. After some data has been received, the buffer and the amount received are returned to you.
The problem with this model is that the buffer is effectively locked up in kernel space until the I/O is complete. Compared to readiness-notifications (select() and derivatives, such as poll/epoll/kqueue), which can share the same receive buffer among all receiving sockets. (They might need to buffer things like partially received commands, but you can perhaps use a much smaller buffer there, and/or only allocate it in the event you require it.)
It has been a long time since I've done Windows programming, so please correct me if I've gotten the above wrong. But that's a fundamental difference and advantage that epoll/kqueue have over IOCP.
Now, I do think the IOCP model is very much conceptually simpler, and I would guess that it is easier to write correct IOCP code than epoll code, but at the expense of memory in some situations. But IOCP doesn't (I don't think) cover as many situations as epoll/kqueue.
The benefit of IOCP and Overlapped I/O on Windows isn't the design. The benefit is that it comes complete out of the box, whereas on Linux and *BSD you either need to roll your own or supplement inconsistent kernel interfaces that people tend to avoid. But both IOCP and Overlapped I/O are higher-level APIs than traditional Unix readiness notification. The problem on Windows is that there's nothing like epoll or kqueue, which is critically important when you're trying to write library code that works with different event models. (Even on Windows Overlapped I/O isn't always ideal, especially in libraries trying to avoid callbacks or support multi-threading strategies different than those dictated by Overlapped I/O). Windows does implement something equivalent to traditional Unix readiness notification internally--it's how IOCP and Overlapped I/O are implemented--but its unpublished and exceptionally opaque. See https://github.com/piscisaureus/wepoll
And then you have a language with GC and you want a async IO, and your runtime becomes so messy with IOCP support, so you just use poll on windows, as many languages do. kqueue is fine, but messy and incomprehensible, and I don't see how is it fundamentally better than epoll with ET.
Wasn't IOCP taken essentially intact from VAX/VMS? Like, from the 1970's?
I don't understand why someone wouldn't have been aware of it when creating epoll().
Meanwhile I agree, it's a lovely interface, and vastly less system call-heavy than epoll
What do you mean by this? If you mean that kqueue doesn't support adding or modifying events simultaneous with querying, then as the OP said it does.
> no kernel<->ringbuffer support which is the point of this article
That's an implementation detail. Indeed, a 2007 Linux kqueue patch used ring buffers. See https://lwn.net/Articles/233462/
> I'm not sure if it can be nested
Can you poll on kqueue descriptors recursively? (That is, install a notification event for kqueue descriptor C with kqueue B, which in turn is installed with kqueue A, such that readiness for kqueue C bubbles up to kqueue A) Yes, you can. Same thing with Solaris Port descriptors.
> cross section of features it supports differs fairly wildly from e.g. epoll.
kqueue is an API framework for exposing pollable notification events (i.e. event filters in kqueue parlance). If the semantics of an event type is different, just use different identifiers. Different BSDs support different identifiers (e.g. MacOS supports polling on a Mach Port with EVFILT_MACHPORT) or different flags (notes in kqueue parlance) to control semantics of pre-existing identifiers.
Expecting Linux to adopt kqueue is unreasonable, especially at this point when Linux has reproduced much (but hardly all) common kqueue event filters. What remains irksome, however, is how epoll+friends have ignored the design decisions and real-world experience of kqueue. Most annoying IMO is the behavior of epoll on fork. This despite the fact that kqueue was both ridiculously well documented and mature by the time epoll came out, not to mention the simple fact that the semantics are just atrocious for anyone familiar with Unix programming.[1]
[1] It seems obvious to me that this poor behavior of epoll was an implementation short-cut. Other poor behaviors were premature optimizations. All turned out, IMHO, to have been entirely unnecessary. Tragic considering how Linux once championed the idea that most of the time (if not all of the time) you can make the convenient or most sensible semantics at least as performant as designs that sacrifice ergonomic, composable semantics at the alter of optimization (and in particular optimizations with very specific, very niche use cases in mind). This notion is still preached in the Linux world but not practiced as much, a consequence of corporate influence. (You can see the same thing in FreeBSD, though to a lesser extent. FreeBSD is better at polishing their turds.)
OP's comment is n the context of an article describing extensions to AIO to support polling, one of the benefits of that mechanism is a single call can submit new IO operations simultaneous to registering readiness notifications. AFAIK neither epoll nor kqueue support that
Thanks for weighing in, plenty to digest here
Re: epoll-on-fork, do you mean the behaviour where it associates with the wrong kernel object? I knew about that one, although fork sounds curious, but I think you're probably just referring to the trouble fd leaks can cause due to the former issue
The way epoll() is written triggers so many syscalls that things like socket clients can get triggered for every single packet. Whereas kqueue coordinates with other socket options set on the descriptor, such as not returning until x bytes have been received and/or the server has closed the connection.
...not to mention how much more it's tied to across the IO subsystem.
SO_RCVLOWAT and SO_SNDLOWAT
Specify the minimum number of bytes in the buffer until the socket
layer will pass the data to the protocol (SO_SNDLOWAT) or the user
on receiving (SO_RCVLOWAT). These two values are initialized to 1.
On the transmit side: EPOLLET
Sets the Edge Triggered behavior for the associated file descriptor.
The default behavior for epoll is Level Triggered. See epoll(7) for
more detailed informa‐ tion about Edge and Level Triggered event
distribution architectures.Unless I'm missing something (and I could very well be missing something), I'm not seeing how that is any different than anything I tried...and I went through every socket option I could find, even if it seemed irrelevant.
Have you written code that actually performs this way? Or are you speculating? I really do wish I could do this with epoll, but I (and several others) just never were able to figure it out. And kqueue simply performed at least an order of magnitude better of any variation I could hack together with epoll.
However, because of what you have observed I wrote up a quick test, which confirms to my satisfaction that epoll_wait() respects SO_RCVLOWAT on TCP sockets: https://gist.github.com/keaston/d2473b8b996a34a5860b0744684f...
It seems likely that you must have been dealing with an old "enterprise" kernel in 2015 that hadn't had this fix backported to it?
My only guess is that I was botching something else when I was digging into this a few years ago.
I can't tell you how much I appreciate you putting that together. This seriously changes a lot of what I work on. Thank you.
The client code I wrote: https://gist.github.com/benwills/95ee844853d3b18588fb3df7d56...
The errors are just because you need to check for EAGAIN or EWOULDBLOCK when read() returns -1, and not bail out when that happens.
> Multiple notifications can be consumed without the need to enter the kernel at all, and polling for multiple file descriptors can be re-established with a single io_submit() call. The result, Hellwig said in the patch posting, is an up-to-10% improvement in the performance of the Seastar I/O framework. More recently, he noted that the improvement grows to 16% on kernels with page-table isolation turned on.
I’m not convinced. Can anybody explain what’s the set of desirable properties of polling interfaces, and why we need at least four different interfaces to implement all of them?
Bad property 1. Limited number of things you can block on. select(2) can only do FD_SETSIZE descriptors.
Bad property 2. Entire list of descriptors needs to be re-examined on every blocking call. select(2) and poll(2) have this problem. Entering the syscall, the kernel needs to copy the list of descriptors, and after returning from it, the application needs to scan the list to see what changed - these are both O(N) operations. (Aside: Windows's WaitForMultipleObjects suffers from the former problem but not the latter, but has bad property #1 since it's limited to 64 handles.) epoll and kqueue fix this by having the kernel maintain the list of descriptors across calls, notifying you of which ones change.
Bad property 3. Too many syscalls, which epoll suffers from and this latest one is trying to solve. kqueue(2) tries to mitigate this by allowing you to batch add/remove descriptors, rather than doing a separate syscall for each.
I'll give you a partial answer.
One desirable property is that each iteration of your event loop is not O(n) with the total number of descriptors being watched. select() and poll() are flawed for that reason—the entire list of file descriptors is passed in on each iteration and has to at least be compared to the previous iteration. No one writes things using these ancient interfaces anymore. (That's a bit unfortunate given that all the modern interfaces are single-platform, so everyone who cares about portability needs an abstraction layer, but it is what it is.) epoll() is better.
The kernel doesn't break old programs, so old interfaces stick around, basically no matter how bad they are. There are three interfaces now, so there have to be at least three. If there can be only three or if there have to be four comes down to if epoll is (or can become) good enough or not. If folks keep coming up with new requirements that can't be met with existing interfaces, there will just be more and more interfaces over time...
If those old ones don’t have any unique properties, couldn’t they be implemented on top of a single syscall, or is the syscall interface sacred on Linux, and calls cannot be retired?
https://lore.kernel.org/lkml/CA+55aFw9mxNPX6OtOp-aoUMdXSg=gB...
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...