Use poll() instead of select() for the server main loop.
Fixed races with SIGCHLD handling and ptrace.
Minor fixes to timeout handling.
diff --git a/server/select.c b/server/select.c
index 540a09b..a1286cf 100644
--- a/server/select.c
+++ b/server/select.c
@@ -10,6 +10,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/poll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
@@ -17,6 +18,13 @@
#include "object.h"
#include "thread.h"
+
+struct poll_user
+{
+ void (*func)(int event, void *private); /* callback function */
+ void *private; /* callback private data */
+};
+
struct timeout_user
{
struct timeout_user *next; /* next in sorted timeout list */
@@ -26,62 +34,92 @@
void *private; /* callback private data */
};
-static struct select_user *users[FD_SETSIZE]; /* users array */
-static fd_set read_set, write_set, except_set; /* current select sets */
-static int nb_users; /* current number of users */
-static int max_fd; /* max fd in use */
+static struct poll_user *poll_users; /* users array */
+static struct pollfd *pollfd; /* poll fd array */
+static int nb_users; /* count of array entries actually in use */
+static int active_users; /* current number of active users */
+static int allocated_users; /* count of allocated entries in the array */
+static struct poll_user *freelist; /* list of free entries in the array */
+
static struct timeout_user *timeout_head; /* sorted timeouts list head */
static struct timeout_user *timeout_tail; /* sorted timeouts list tail */
-/* register a user */
-void register_select_user( struct select_user *user )
+/* add a user and return an opaque handle to it, or -1 on failure */
+int add_select_user( int fd, void (*func)(int, void *), void *private )
{
- assert( !users[user->fd] );
-
- users[user->fd] = user;
- if (user->fd > max_fd) max_fd = user->fd;
- nb_users++;
+ int ret;
+ if (freelist)
+ {
+ ret = freelist - poll_users;
+ freelist = poll_users[ret].private;
+ assert( !poll_users[ret].func );
+ }
+ else
+ {
+ if (nb_users == allocated_users)
+ {
+ struct poll_user *newusers;
+ struct pollfd *newpoll;
+ int new_count = allocated_users ? (allocated_users + allocated_users / 2) : 16;
+ if (!(newusers = realloc( poll_users, new_count * sizeof(*poll_users) ))) return -1;
+ if (!(newpoll = realloc( pollfd, new_count * sizeof(*pollfd) )))
+ {
+ free( newusers );
+ return -1;
+ }
+ poll_users = newusers;
+ pollfd = newpoll;
+ allocated_users = new_count;
+ }
+ ret = nb_users++;
+ }
+ pollfd[ret].fd = fd;
+ pollfd[ret].events = 0;
+ pollfd[ret].revents = 0;
+ poll_users[ret].func = func;
+ poll_users[ret].private = private;
+ active_users++;
+ return ret;
}
-/* remove a user */
-void unregister_select_user( struct select_user *user )
+/* remove a user and close its fd */
+void remove_select_user( int user )
{
- assert( users[user->fd] == user );
+ if (user == -1) return; /* avoids checking in all callers */
+ assert( poll_users[user].func );
+ close( pollfd[user].fd );
+ pollfd[user].fd = -1;
+ pollfd[user].events = 0;
+ pollfd[user].revents = 0;
+ poll_users[user].func = NULL;
+ poll_users[user].private = freelist;
+ freelist = &poll_users[user];
+ active_users--;
+}
- FD_CLR( user->fd, &read_set );
- FD_CLR( user->fd, &write_set );
- FD_CLR( user->fd, &except_set );
- users[user->fd] = NULL;
- if (max_fd == user->fd) while (max_fd && !users[max_fd]) max_fd--;
- nb_users--;
+/* change the fd of a select user (the old fd is closed) */
+void change_select_fd( int user, int fd )
+{
+ assert( poll_users[user].func );
+ close( pollfd[user].fd );
+ pollfd[user].fd = fd;
}
/* set the events that select waits for on this fd */
-void set_select_events( struct select_user *user, int events )
+void set_select_events( int user, int events )
{
- assert( users[user->fd] == user );
- if (events & READ_EVENT) FD_SET( user->fd, &read_set );
- else FD_CLR( user->fd, &read_set );
- if (events & WRITE_EVENT) FD_SET( user->fd, &write_set );
- else FD_CLR( user->fd, &write_set );
- if (events & EXCEPT_EVENT) FD_SET( user->fd, &except_set );
- else FD_CLR( user->fd, &except_set );
+ assert( poll_users[user].func );
+ pollfd[user].events = events;
}
/* check if events are pending */
-int check_select_events( struct select_user *user, int events )
+int check_select_events( int fd, int events )
{
- fd_set read_fds, write_fds, except_fds;
- struct timeval tv = { 0, 0 };
-
- FD_ZERO( &read_fds );
- FD_ZERO( &write_fds );
- FD_ZERO( &except_fds );
- if (events & READ_EVENT) FD_SET( user->fd, &read_fds );
- if (events & WRITE_EVENT) FD_SET( user->fd, &write_fds );
- if (events & EXCEPT_EVENT) FD_SET( user->fd, &except_fds );
- return select( user->fd + 1, &read_fds, &write_fds, &except_fds, &tv ) > 0;
+ struct pollfd pfd;
+ pfd.fd = fd;
+ pfd.events = events;
+ return poll( &pfd, 1, 0 ) > 0;
}
/* add a timeout user */
@@ -98,11 +136,7 @@
/* Now insert it in the linked list */
for (pos = timeout_head; pos; pos = pos->next)
- {
- if (pos->when.tv_sec > user->when.tv_sec) break;
- if ((pos->when.tv_sec == user->when.tv_sec) &&
- (pos->when.tv_usec > user->when.tv_usec)) break;
- }
+ if (!time_before( &pos->when, when )) break;
if (pos) /* insert it before 'pos' */
{
@@ -132,123 +166,103 @@
free( user );
}
-/* make an absolute timeout value from a relative timeout in milliseconds */
-void make_timeout( struct timeval *when, int timeout )
+/* add a timeout in milliseconds to an absolute time */
+void add_timeout( struct timeval *when, int timeout )
{
- gettimeofday( when, 0 );
- if (!timeout) return;
- if ((when->tv_usec += (timeout % 1000) * 1000) >= 1000000)
+ if (timeout)
{
- when->tv_usec -= 1000000;
- when->tv_sec++;
+ long sec = timeout / 1000;
+ if ((when->tv_usec += (timeout - 1000*sec) * 1000) >= 1000000)
+ {
+ when->tv_usec -= 1000000;
+ when->tv_sec++;
+ }
+ when->tv_sec += sec;
}
- when->tv_sec += timeout / 1000;
}
-/* handle an expired timeout */
-static void handle_timeout( struct timeout_user *user )
+/* handle the next expired timeout */
+static void handle_timeout(void)
{
+ struct timeout_user *user = timeout_head;
+ timeout_head = user->next;
if (user->next) user->next->prev = user->prev;
else timeout_tail = user->prev;
- if (user->prev) user->prev->next = user->next;
- else timeout_head = user->next;
user->callback( user->private );
free( user );
}
-#ifdef DEBUG_OBJECTS
-static int do_dump_objects;
-
/* SIGHUP handler */
-static void sighup()
+static void sighup_handler()
{
- do_dump_objects = 1;
-}
+#ifdef DEBUG_OBJECTS
+ dump_objects();
#endif
-
-/* dummy SIGCHLD handler */
-static void sigchld()
-{
}
/* server main loop */
void select_loop(void)
{
- int i, ret;
+ int ret;
+ sigset_t sigset;
+ struct sigaction action;
setsid();
signal( SIGPIPE, SIG_IGN );
- signal( SIGCHLD, sigchld );
-#ifdef DEBUG_OBJECTS
- signal( SIGHUP, sighup );
-#endif
- while (nb_users)
+ /* block the signals we use */
+ sigemptyset( &sigset );
+ sigaddset( &sigset, SIGCHLD );
+ sigaddset( &sigset, SIGHUP );
+ sigprocmask( SIG_BLOCK, &sigset, NULL );
+
+ /* set the handlers */
+ action.sa_mask = sigset;
+ action.sa_flags = 0;
+ action.sa_handler = sigchld_handler;
+ sigaction( SIGCHLD, &action, NULL );
+ action.sa_handler = sighup_handler;
+ sigaction( SIGHUP, &action, NULL );
+
+ while (active_users)
{
- fd_set read = read_set, write = write_set, except = except_set;
+ long diff = -1;
if (timeout_head)
{
- struct timeval tv, now;
+ struct timeval now;
gettimeofday( &now, NULL );
- if ((timeout_head->when.tv_sec < now.tv_sec) ||
- ((timeout_head->when.tv_sec == now.tv_sec) &&
- (timeout_head->when.tv_usec < now.tv_usec)))
+ while (timeout_head)
{
- handle_timeout( timeout_head );
- continue;
+ if (!time_before( &now, &timeout_head->when )) handle_timeout();
+ else
+ {
+ diff = (timeout_head->when.tv_sec - now.tv_sec) * 1000
+ + (timeout_head->when.tv_usec - now.tv_usec) / 1000;
+ break;
+ }
}
- tv.tv_sec = timeout_head->when.tv_sec - now.tv_sec;
- if ((tv.tv_usec = timeout_head->when.tv_usec - now.tv_usec) < 0)
- {
- tv.tv_usec += 1000000;
- tv.tv_sec--;
- }
-#if 0
- printf( "select: " );
- for (i = 0; i <= max_fd; i++) printf( "%c", FD_ISSET( i, &read_set ) ? 'r' :
- (FD_ISSET( i, &write_set ) ? 'w' : '-') );
- printf( " timeout %d.%06d\n", tv.tv_sec, tv.tv_usec );
-#endif
- ret = select( max_fd + 1, &read, &write, &except, &tv );
}
- else /* no timeout */
+
+ sigprocmask( SIG_UNBLOCK, &sigset, NULL );
+
+ /* Note: we assume that the signal handlers do not manipulate the pollfd array
+ * or the timeout list, otherwise there is a race here.
+ */
+ ret = poll( pollfd, nb_users, diff );
+
+ sigprocmask( SIG_BLOCK, &sigset, NULL );
+
+ if (ret > 0)
{
-#if 0
- printf( "select: " );
- for (i = 0; i <= max_fd; i++) printf( "%c", FD_ISSET( i, &read_set ) ? 'r' :
- (FD_ISSET( i, &write_set ) ? 'w' : '-') );
- printf( " no timeout\n" );
-#endif
- ret = select( max_fd + 1, &read, &write, &except, NULL );
- }
-
- if (!ret) continue;
- if (ret == -1) {
- if (errno == EINTR)
+ int i;
+ for (i = 0; i < nb_users; i++)
{
-#ifdef DEBUG_OBJECTS
- if (do_dump_objects) dump_objects();
- do_dump_objects = 0;
-#endif
- wait4_thread( NULL, 0 );
- continue;
+ if (pollfd[i].revents)
+ {
+ poll_users[i].func( pollfd[i].revents, poll_users[i].private );
+ if (!--ret) break;
+ }
}
- perror("select");
- continue;
- }
-
- for (i = 0; i <= max_fd; i++)
- {
- int event = 0;
- if (FD_ISSET( i, &except )) event |= EXCEPT_EVENT;
- if (FD_ISSET( i, &write )) event |= WRITE_EVENT;
- if (FD_ISSET( i, &read )) event |= READ_EVENT;
-
- /* Note: users[i] might be NULL here, because an event routine
- called in an earlier pass of this loop might have removed
- the current user ... */
- if (event && users[i])
- users[i]->func( event, users[i]->private );
}
}
}