Async IO on Linux: select, poll, and epoll
jvns.ca
jvns.ca
So, struct fd_set is a fixed size structure, and the fd, passed to the macros (or kernel) has a max fd value that it can support. So its quite possible your application has a fd > FD_SETSIZE even if you don't think your using a lot of fds (libraries and such can drive up the value of fd quite easily).
The result is random buffer smashing, both by userspace via the FD_XXX macros as well as the kernel, which doesn't have any knowledge beyond the nfds (which is the largest fd, not the number of fd's) its given. That is why you use poll()..
I can't tell you how many times I've had to fix that bug in programs that appear to work fine for $SHORTTIME, but fail when running for $LONGTIME.
> “level-triggered” vs “edge-triggered” ... I’d never heard this terminology before (I think it comes from electrical engineering maybe?).
This is IMO easiest to understand when using a logic analyzer or an o-scope. Often you want to see what condition the system was in right when something interesting happens and you can either detect a pulse (like from a momentary button, e.g. elevator call button) or a level change (like from a pole/knife switch like a room's light switch). Also, if you've programmed interrupts before you'll need to understand this distinction.
Yes, me neither, but my guess is comes from the level or edge of a square wave, respectively.
The description of edge vs level triggering is short and IMO not the easiest to understand.
The highlight of differences between the states that poll vs select can return doesn't actually explain the extra states that poll gives you.
The memory considerations of select vs poll vs epoll are only slightly touched upon.
If you want to understand the how, you'll probably have to read the book she mentions (I haven't read it), or other articles around the web (what I did read). IMO the rule of thumb is that if you are writing a non-performance critical piece of software, just use select. It is widely available and easy to understand. If you want performance, you will want to use epoll and kqueue with a fallback to poll. Understanding kqueue and epoll will give you a better idea of why these are different than poll/select.
The last time I touched this level of code, I did it in Python and I wrote a generic listener set of classes that wrap all four of these calls to make it work as performantly as possible on as many systems as possible. I suggest this as an exercise to budding networking programmers.
Besides the article is going down to the level of reading kernel source. Most people don't do that because they want to modify it, by because they want to understand it. Just saying "use Node" is not the answer to those types of questions.
If I had to guess, I'd say it makes separate A and AAAA queries in parallel
write(3, "\3048\1\0\0\1\0\0\0\0\0\0\7example\3com\0\0\34\0\1", 29
write(4, ";\251\1\0\0\1\0\0\0\0\0\0\7example\3com\0\0\1\0\1", 29
We can make these a little more legible like this: $ printf "%b" '\3048\1\0\0\1\0\0\0\0\0\0\7example\3com\0\0\34\0\1' | xxd
00000000: c438 0100 0001 0000 0000 0000 0765 7861 .8...........exa
00000010: 6d70 6c65 0363 6f6d 0000 1c00 01 mple.com.....
$ printf "%b" ';\251\1\0\0\1\0\0\0\0\0\0\7example\3com\0\0\1\0\1' | xxd -a
00000000: 3ba9 0100 0001 0000 0000 0000 0765 7861 ;............exa
00000010: 6d70 6c65 0363 6f6d 0000 0100 01 mple.com.....
RFC1035 and later explain how to decode. We only care about decoding the "question section" which follows the format of QNAME/QTYPE/QCLASS.The QNAME is sequence of labels ("example", "com", "") preceded by their length. So we see "example" (preceded by its length of 7), "com" (preceded by its length of 3, and the null label (empty string with a length of 0) representing the root.
Each QNAME is then followed by the QTYPE (two bytes) and QCLASS (two bytes). The first QTYPE is 0x1c, or 28 decimal, which is indeed a AAAA query. The second QTYPE is 1, which is an A query.
And yet kqueue and IO completion ports do support regular disk files [1]. It's true that epoll's model doesn't make sense in the context of disk files, but doesn't mean that you can't do asynchronous notification on disk files, it means that its model isn't general enough.
http://people.eecs.berkeley.edu/~sangjin/2012/12/21/epoll-vs...
I would definitely recommend this to anyone working on Linux systems. Even if you're using a higher-level language, I really think you get extra superpowers if you can go down the stack a few levels, and it is great for that.
The author is one of the most thorough I've known. He gives you every caveat and detail. I don't think there is anything he pretends to know, but every statement feels verified.
This would be a bit like signal driven I/O. I'm thinking nobody uses that because it's single threaded and antiquated.
My feeling is that nobody uses signal driven I/O because signal handling is error prone and complicated. In fact, a very common pattern is to only set a flag in the signal handler, which is checked at defined points in the main program... not completely dissimilar from doing non-blocking calls at defined points in the main program.
There's also kqueue/kevent, which came before epoll and which is arguably superior.
I like this style of writing, as well: "I'm learning this new thing, come with me and I'll explain it as I figure it out."