When Mac OS X first launched they did so without an existing poll function. They later added poll() in Mac OS X 10.3, but we quickly discovered that it was broken (it returned a non-zero value when asked to wait for nothing) so in the curl project we added a check in configure for that and subsequently avoided using poll() in all OS X versions to and including Mac OS 10.8 (Darwin 12). The code would instead switch to the alternative solution based on select() for these platforms.
With the release of Mac OS X 10.9 “Mavericks” in October 2013, Apple had fixed their poll() implementation and we’ve built libcurl to use it since with no issues at all. The configure script picks the correct underlying function to use.
Enter macOS 10.12 (yeah, its not called OS X anymore) “Sierra”, released in September 2016. Quickly we discovered that poll() once against did not act like it should and we are back to disabling the use of it in preference to the backup solution using select().
The new error looks similar to the old problem: when there’s nothing to wait for and we ask poll() to wait N milliseconds, the 10.12 version of poll() returns immediately without waiting. Causing busy-loops. The problem has been reported to Apple and its Radar number is 28372390. (There has been no news from them on how they plan to act on this.)
poll() is defined by POSIX and The Single Unix Specification it specifically says:
If none of the defined events have occurred on any selected file descriptor, poll() waits at least timeout milliseconds for an event to occur on any of the selected file descriptors.
We pushed a configure check for this in curl, to be part of the upcoming 7.51.0 release. I’ll also show you a small snippet you can use stand-alone below.
Apple is hardly alone in the broken-poll department. Remember how Windows’ WSApoll is broken?
Here’s a little code snippet that can detect the 10.12 breakage:
#include <poll.h>
#include <stdio.h>
#include <sys/time.h>
int main(void)
{
struct timeval before, after;
int rc;
size_t us;
gettimeofday(&before, NULL);
rc = poll(NULL, 0, 500);
gettimeofday(&after, NULL);
us = (after.tv_sec - before.tv_sec) * 1000000 +
(after.tv_usec - before.tv_usec);
if(us < 400000) {
puts("poll() is broken");
return 1;
}
else {
puts("poll() works");
}
return 0;
}
Follow-up, January 2017
This poll bug has been confirmed fixed in the macOS 10.12.2 update (released on December 13, 2016), but I’ve found no official mention or statement about this fact.
This same thing used to be (maybe still is) broken for `kqueue(2)`, too. `select(2)` is unimpaired, but this has been an issue for nearly 10yrs now and I believe is deliberate on the desktop in order to make the XServe the preferred platform for servers (vs Mac Minis).
The XServe was discontinued in January 2011, so I don’t think this is a deliberate play for XServe market share.
The XServe was discontinued in 2004, so this seems implausible.
Poll, select and kqueue are used in client applications too.
Good catch. What are the implications of this for current applications using curl when run on Sierra? Should we expect broken communication?
Per M: this problem was discovered when a user used the curl option for limiting curl’s transfer speed, which then can end up doing a poll() without a socket but a timeout. In that case, curl ends up busy-looping like crazy instead of just gently sleeping as it should. It still worked, just wasted lots of CPU and possibly caused other apps to run slower on the machine in the mean time.
So expect other apps too to occasionally suffer from problems like this until the apps have had time to respond like curl does, or if Apple decides to ship an update that fixes poll().
Ok, thanks Daniel.
This might not be directly related but when I updated to sierra and include stdio.h I get a “Can not build module ‘Darwin'”. This is very strange to me. If I start a new project and include stdio.h it is no problem. Any ideas?
So I solved the issue but the solution is very very unsatesfying.
I first opend a new project and imported the stdio.h and got no error so I thought it was strange. I then just went to project preferences and deleted the header search paths and added EXACTLY the same paths and then it worked.
Ideas?
Add to this that poll on macos never has worked on character devices. While this is as documented in the manpage, it’s still pretty annoying.
Thanks for the test code. Have used it together with the select-based wrapper in LIRC (http://sf.net/p/lirc)
Alec: my pleasure, glad to be of service! We’re probably going with disabling poll() completely on all mac OS versions in libcurl to allow users to run the same built code on multiple versions with less risk.