Basic tech stuff

Programming and Linux administration

Archive for January, 2012

Unix: select vs poll

Posted by Daniel Brahneborg on 2012 January 23

Asch, det här får bli på svenska för ovanlighetens skull.

Så länge jag kan minnas så har jag använt funktionen select() för att vänta på data på en socket. Koden för att använda den ser ut ungefär så här:

fd_set fdset;
struct timeval tv;

FD_ZERO(&fdset);
FD_SET(fd, &fdset);

timeout.tv_sec = 60;
timeout.tv_usec = 0;

switch(select(fd + 1, &fdset, NULL, NULL, &timeout)) {
case -1: ;/* something went wrong, check errno */
case 1: ; /* read data */
default: ; /* timeout */
}

Hos en kund fick vi plötsligt bara timeouter. Trots ett fint select-anrop med rätt parametrar, så sa den aldrig till när det fanns data att läsa, eller när det var ok att skicka tillbaka ett svar.

Problemet visade sig vara den där fd_set-saken. I man-sidan till select() står nämligen följande:

The behavior of these macros is undefined if the fd argument is less than 0 or greater
than or equal to FD_SETSIZE, …

Så vad är värdet på FD_SETSIZE? Jo, både i Linux och Solaris är det 1024. I de logfiler vi undersökte så var fd mellan 1025 och 1028, eftersom programmet hade ganska många aktiva uppkopplingar igång. Hoppsan.

Alltså var select() oanvändbar för vår del, så vi fick byta till funktionen poll(), som funkar lite annorlunda. Istället för att ta en bitmap där man har markerat vilka filer man vill vänta på, så skickar man in en array med filnummer. Filnumret blir irrelevant och kan vara hur stort som helst, och det tar inte längre tid oavsett hur stort det än är. Stackars select() måste ju gå igenom hela bitmappen för att hitta vilka bittar man har satt, vilket tar lite tid.

Det här är för övrigt precis det byte av perspektiv för att få bättre Ordo-värde som jag skrev om i höstens nanowrimo, för er typ 2-3 personer som har läst den. 🙂

Koden blir lite annorlunda, även om principen är densamma.

struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN;
rc = poll(&pfd, 1, 60 * 1000);
if ((rc < 0) || (pfd.revents & (POLLERR | POLLNVAL | POLLHUP))) { } /* fail */
else if (pfd.revents & POLLIN) { } /* read data */
else { } /* timeout */

Refaktorerat till en “finns det data att läsa på socket ‘fd’ inom x sekunder?”-funktion blev den nya koden riktigt smidig. Förut fanns en handfull olika anrop till select() som alla gjorde lite, lite olika när det gällde felhanteringen. Att flytta in det hela i en separat funktion gör att det är lätt att justera parametrarna till poll() om det skulle behövas, att det är lätt att logga vartenda anrop, och att samma typ av fel alltid rapporteras uppåt på exakt samma sätt.

Advertisements

Posted in programming | Leave a Comment »