blob: c7759c0d6de08df49eb9594403797e56946c713f [file] [log] [blame]
/*
* File descriptors management functions.
*
* Copyright 2000-2006 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
/*
* FIXME:
* - we still use 'listeners' to check whether we want to stop or not.
* - the various pollers should be moved to other external files, possibly
* dynamic libs.
*/
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <common/compat.h>
#include <common/config.h>
#include <common/time.h>
#include <types/fd.h>
#include <types/global.h>
#include <proto/fd.h>
#include <proto/polling.h>
#include <proto/task.h>
struct fdtab *fdtab = NULL; /* array of all the file descriptors */
int maxfd; /* # of the highest fd + 1 */
int totalconn; /* total # of terminated sessions */
int actconn; /* # of active sessions */
fd_set *StaticReadEvent, *StaticWriteEvent;
int cfg_polling_mechanism = 0; /* POLL_USE_{SELECT|POLL|EPOLL} */
/******************************
* pollers
******************************/
#if !defined(CONFIG_HAP_INLINE_FD_SET)
/*
* Benchmarks performed on a Pentium-M notebook show that using functions
* instead of the usual macros improve the FD_* performance by about 80%,
* and that marking them regparm(2) adds another 20%.
*/
REGPRM2 void my_fd_set(const int fd, fd_set *ev)
{
FD_SET(fd, ev);
}
REGPRM2 void my_fd_clr(const int fd, fd_set *ev)
{
FD_CLR(fd, ev);
}
REGPRM2 int my_fd_isset(const int fd, const fd_set *ev)
{
return FD_ISSET(fd, ev);
}
#endif
/*
* FIXME: this is dirty, but at the moment, there's no other solution to remove
* the old FDs from outside the loop. Perhaps we should export a global 'poll'
* structure with pointers to functions such as init_fd() and close_fd(), plus
* a private structure with several pointers to places such as below.
*/
#if defined(ENABLE_EPOLL)
fd_set *PrevReadEvent = NULL, *PrevWriteEvent = NULL;
#if defined(USE_MY_EPOLL)
#include <errno.h>
#include <sys/syscall.h>
_syscall1 (int, epoll_create, int, size);
_syscall4 (int, epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event *, event);
_syscall4 (int, epoll_wait, int, epfd, struct epoll_event *, events, int, maxevents, int, timeout);
#endif
/*
* Main epoll() loop.
* does 3 actions :
* 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures
* 1 (POLL_LOOP_ACTION_RUN) : runs the loop
* 2 (POLL_LOOP_ACTION_CLEAN) : cleans up
*
* returns 0 if initialization failed, !0 otherwise.
*/
int epoll_loop(int action)
{
int next_time;
int status;
int fd;
int fds, count;
int pr, pw, sr, sw;
unsigned rn, ro, wn, wo; /* read new, read old, write new, write old */
struct epoll_event ev;
/* private data */
static struct epoll_event *epoll_events = NULL;
static int epoll_fd;
if (action == POLL_LOOP_ACTION_INIT) {
epoll_fd = epoll_create(global.maxsock + 1);
if (epoll_fd < 0)
return 0;
else {
epoll_events = (struct epoll_event*)
calloc(1, sizeof(struct epoll_event) * global.maxsock);
PrevReadEvent = (fd_set *)
calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
PrevWriteEvent = (fd_set *)
calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
}
return 1;
}
else if (action == POLL_LOOP_ACTION_CLEAN) {
if (PrevWriteEvent) free(PrevWriteEvent);
if (PrevReadEvent) free(PrevReadEvent);
if (epoll_events) free(epoll_events);
close(epoll_fd);
epoll_fd = 0;
return 1;
}
/* OK, it's POLL_LOOP_ACTION_RUN */
tv_now(&now);
while (1) {
next_time = process_runnable_tasks();
/* stop when there's no connection left and we don't allow them anymore */
if (!actconn && listeners == 0)
break;
for (fds = 0; (fds << INTBITS) < maxfd; fds++) {
rn = ((int*)StaticReadEvent)[fds]; ro = ((int*)PrevReadEvent)[fds];
wn = ((int*)StaticWriteEvent)[fds]; wo = ((int*)PrevWriteEvent)[fds];
if ((ro^rn) | (wo^wn)) {
for (count = 0, fd = fds << INTBITS; count < (1<<INTBITS) && fd < maxfd; count++, fd++) {
#define FDSETS_ARE_INT_ALIGNED
#ifdef FDSETS_ARE_INT_ALIGNED
#define WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
#ifdef WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
pr = (ro >> count) & 1;
pw = (wo >> count) & 1;
sr = (rn >> count) & 1;
sw = (wn >> count) & 1;
#else
pr = MY_FD_ISSET(fd&((1<<INTBITS)-1), (typeof(fd_set*))&ro);
pw = MY_FD_ISSET(fd&((1<<INTBITS)-1), (typeof(fd_set*))&wo);
sr = MY_FD_ISSET(fd&((1<<INTBITS)-1), (typeof(fd_set*))&rn);
sw = MY_FD_ISSET(fd&((1<<INTBITS)-1), (typeof(fd_set*))&wn);
#endif
#else
pr = MY_FD_ISSET(fd, PrevReadEvent);
pw = MY_FD_ISSET(fd, PrevWriteEvent);
sr = MY_FD_ISSET(fd, StaticReadEvent);
sw = MY_FD_ISSET(fd, StaticWriteEvent);
#endif
if (!((sr^pr) | (sw^pw)))
continue;
ev.events = (sr ? EPOLLIN : 0) | (sw ? EPOLLOUT : 0);
ev.data.fd = fd;
#ifdef EPOLL_CTL_MOD_WORKAROUND
/* I encountered a rarely reproducible problem with
* EPOLL_CTL_MOD where a modified FD (systematically
* the one in epoll_events[0], fd#7) would sometimes
* be set EPOLL_OUT while asked for a read ! This is
* with the 2.4 epoll patch. The workaround is to
* delete then recreate in case of modification.
* This is in 2.4 up to epoll-lt-0.21 but not in 2.6
* nor RHEL kernels.
*/
if ((pr | pw) && fdtab[fd].state != FD_STCLOSE)
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev);
if ((sr | sw))
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
#else
if ((pr | pw)) {
/* the file-descriptor already exists... */
if ((sr | sw)) {
/* ...and it will still exist */
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev) < 0) {
// perror("epoll_ctl(MOD)");
// exit(1);
}
} else {
/* ...and it will be removed */
if (fdtab[fd].state != FD_STCLOSE &&
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev) < 0) {
// perror("epoll_ctl(DEL)");
// exit(1);
}
}
} else {
/* the file-descriptor did not exist, let's add it */
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
// perror("epoll_ctl(ADD)");
// exit(1);
}
}
#endif // EPOLL_CTL_MOD_WORKAROUND
}
((int*)PrevReadEvent)[fds] = rn;
((int*)PrevWriteEvent)[fds] = wn;
}
}
/* now let's wait for events */
status = epoll_wait(epoll_fd, epoll_events, maxfd, next_time);
tv_now(&now);
for (count = 0; count < status; count++) {
fd = epoll_events[count].data.fd;
if (MY_FD_ISSET(fd, StaticReadEvent)) {
if (fdtab[fd].state == FD_STCLOSE)
continue;
if (epoll_events[count].events & ( EPOLLIN | EPOLLERR | EPOLLHUP ))
fdtab[fd].cb[DIR_RD].f(fd);
}
if (MY_FD_ISSET(fd, StaticWriteEvent)) {
if (fdtab[fd].state == FD_STCLOSE)
continue;
if (epoll_events[count].events & ( EPOLLOUT | EPOLLERR | EPOLLHUP ))
fdtab[fd].cb[DIR_WR].f(fd);
}
}
}
return 1;
}
#endif
#if defined(ENABLE_POLL)
/*
* Main poll() loop.
* does 3 actions :
* 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures
* 1 (POLL_LOOP_ACTION_RUN) : runs the loop
* 2 (POLL_LOOP_ACTION_CLEAN) : cleans up
*
* returns 0 if initialization failed, !0 otherwise.
*/
int poll_loop(int action)
{
int next_time;
int status;
int fd, nbfd;
int fds, count;
int sr, sw;
unsigned rn, wn; /* read new, write new */
/* private data */
static struct pollfd *poll_events = NULL;
if (action == POLL_LOOP_ACTION_INIT) {
poll_events = (struct pollfd*)
calloc(1, sizeof(struct pollfd) * global.maxsock);
return 1;
}
else if (action == POLL_LOOP_ACTION_CLEAN) {
if (poll_events)
free(poll_events);
return 1;
}
/* OK, it's POLL_LOOP_ACTION_RUN */
tv_now(&now);
while (1) {
next_time = process_runnable_tasks();
/* stop when there's no connection left and we don't allow them anymore */
if (!actconn && listeners == 0)
break;
nbfd = 0;
for (fds = 0; (fds << INTBITS) < maxfd; fds++) {
rn = ((int*)StaticReadEvent)[fds];
wn = ((int*)StaticWriteEvent)[fds];
if ((rn|wn)) {
for (count = 0, fd = fds << INTBITS; count < (1<<INTBITS) && fd < maxfd; count++, fd++) {
#define FDSETS_ARE_INT_ALIGNED
#ifdef FDSETS_ARE_INT_ALIGNED
#define WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
#ifdef WE_REALLY_NOW_THAT_FDSETS_ARE_INTS
sr = (rn >> count) & 1;
sw = (wn >> count) & 1;
#else
sr = MY_FD_ISSET(fd&((1<<INTBITS)-1), (typeof(fd_set*))&rn);
sw = MY_FD_ISSET(fd&((1<<INTBITS)-1), (typeof(fd_set*))&wn);
#endif
#else
sr = MY_FD_ISSET(fd, StaticReadEvent);
sw = MY_FD_ISSET(fd, StaticWriteEvent);
#endif
if ((sr|sw)) {
poll_events[nbfd].fd = fd;
poll_events[nbfd].events = (sr ? POLLIN : 0) | (sw ? POLLOUT : 0);
nbfd++;
}
}
}
}
/* now let's wait for events */
status = poll(poll_events, nbfd, next_time);
tv_now(&now);
for (count = 0; status > 0 && count < nbfd; count++) {
fd = poll_events[count].fd;
if (!(poll_events[count].revents & ( POLLOUT | POLLIN | POLLERR | POLLHUP )))
continue;
/* ok, we found one active fd */
status--;
if (MY_FD_ISSET(fd, StaticReadEvent)) {
if (fdtab[fd].state == FD_STCLOSE)
continue;
if (poll_events[count].revents & ( POLLIN | POLLERR | POLLHUP ))
fdtab[fd].cb[DIR_RD].f(fd);
}
if (MY_FD_ISSET(fd, StaticWriteEvent)) {
if (fdtab[fd].state == FD_STCLOSE)
continue;
if (poll_events[count].revents & ( POLLOUT | POLLERR | POLLHUP ))
fdtab[fd].cb[DIR_WR].f(fd);
}
}
}
return 1;
}
#endif
/*
* Main select() loop.
* does 3 actions :
* 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures
* 1 (POLL_LOOP_ACTION_RUN) : runs the loop
* 2 (POLL_LOOP_ACTION_CLEAN) : cleans up
*
* returns 0 if initialization failed, !0 otherwise.
*/
int select_loop(int action)
{
int next_time;
int status;
int fd,i;
struct timeval delta;
int readnotnull, writenotnull;
static fd_set *ReadEvent = NULL, *WriteEvent = NULL;
if (action == POLL_LOOP_ACTION_INIT) {
ReadEvent = (fd_set *)
calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
WriteEvent = (fd_set *)
calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE);
return 1;
}
else if (action == POLL_LOOP_ACTION_CLEAN) {
if (WriteEvent) free(WriteEvent);
if (ReadEvent) free(ReadEvent);
return 1;
}
/* OK, it's POLL_LOOP_ACTION_RUN */
tv_now(&now);
while (1) {
next_time = process_runnable_tasks();
/* stop when there's no connection left and we don't allow them anymore */
if (!actconn && listeners == 0)
break;
if (next_time > 0) { /* FIXME */
/* Convert to timeval */
/* to avoid eventual select loops due to timer precision */
next_time += SCHEDULER_RESOLUTION;
delta.tv_sec = next_time / 1000;
delta.tv_usec = (next_time % 1000) * 1000;
}
else if (next_time == 0) { /* allow select to return immediately when needed */
delta.tv_sec = delta.tv_usec = 0;
}
/* let's restore fdset state */
readnotnull = 0; writenotnull = 0;
for (i = 0; i < (maxfd + FD_SETSIZE - 1)/(8*sizeof(int)); i++) {
readnotnull |= (*(((int*)ReadEvent)+i) = *(((int*)StaticReadEvent)+i)) != 0;
writenotnull |= (*(((int*)WriteEvent)+i) = *(((int*)StaticWriteEvent)+i)) != 0;
}
// /* just a verification code, needs to be removed for performance */
// for (i=0; i<maxfd; i++) {
// if (MY_FD_ISSET(i, ReadEvent) != MY_FD_ISSET(i, StaticReadEvent))
// abort();
// if (MY_FD_ISSET(i, WriteEvent) != MY_FD_ISSET(i, StaticWriteEvent))
// abort();
//
// }
status = select(maxfd,
readnotnull ? ReadEvent : NULL,
writenotnull ? WriteEvent : NULL,
NULL,
(next_time >= 0) ? &delta : NULL);
/* this is an experiment on the separation of the select work */
// status = (readnotnull ? select(maxfd, ReadEvent, NULL, NULL, (next_time >= 0) ? &delta : NULL) : 0);
// status |= (writenotnull ? select(maxfd, NULL, WriteEvent, NULL, (next_time >= 0) ? &delta : NULL) : 0);
tv_now(&now);
if (status > 0) { /* must proceed with events */
int fds;
char count;
for (fds = 0; (fds << INTBITS) < maxfd; fds++)
if ((((int *)(ReadEvent))[fds] | ((int *)(WriteEvent))[fds]) != 0)
for (count = 1<<INTBITS, fd = fds << INTBITS; count && fd < maxfd; count--, fd++) {
/* if we specify read first, the accepts and zero reads will be
* seen first. Moreover, system buffers will be flushed faster.
*/
if (MY_FD_ISSET(fd, ReadEvent)) {
if (fdtab[fd].state == FD_STCLOSE)
continue;
fdtab[fd].cb[DIR_RD].f(fd);
}
if (MY_FD_ISSET(fd, WriteEvent)) {
if (fdtab[fd].state == FD_STCLOSE)
continue;
fdtab[fd].cb[DIR_WR].f(fd);
}
}
}
else {
// fprintf(stderr,"select returned %d, maxfd=%d\n", status, maxfd);
}
}
return 1;
}
/*********************
* generic functions
*********************/
/* Deletes an FD from the fdsets, and recomputes the maxfd limit.
* The file descriptor is also closed.
*/
void fd_delete(int fd)
{
MY_FD_CLR(fd, StaticReadEvent);
MY_FD_CLR(fd, StaticWriteEvent);
#if defined(ENABLE_EPOLL)
if (PrevReadEvent) {
MY_FD_CLR(fd, PrevReadEvent);
MY_FD_CLR(fd, PrevWriteEvent);
}
#endif
close(fd);
fdtab[fd].state = FD_STCLOSE;
while ((maxfd-1 >= 0) && (fdtab[maxfd-1].state == FD_STCLOSE))
maxfd--;
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/