Use native Windows events for the event loop.

This commit changes the event loop to use WSAEventSelect() and
WSAWaitForMultipleEvents() on Windows. This paves the way for making the
event loop more flexible on Windows by introducing the required
infrastructure to make the event loop wait on any Win32 event.

This commit only affects the internal implementation of the event
module. Externally visible behavior remains strictly unchanged (for
now).
This commit is contained in:
Etienne Dechamps 2014-06-27 21:58:35 +01:00
parent 86a99c6b99
commit 611217c96e
2 changed files with 129 additions and 24 deletions

View file

@ -23,15 +23,26 @@
#include "event.h"
#include "net.h"
#include "utils.h"
#include "xalloc.h"
struct timeval now;
#ifndef HAVE_MINGW
static fd_set readfds;
static fd_set writefds;
#else
static const long READ_EVENTS = FD_READ | FD_ACCEPT | FD_CLOSE;
static const long WRITE_EVENTS = FD_WRITE | FD_CONNECT;
static DWORD event_count = 0;
#endif
static volatile bool running;
static int io_compare(const io_t *a, const io_t *b) {
#ifndef HAVE_MINGW
return a->fd - b->fd;
#else
return a->event - b->event;
#endif
}
static int timeout_compare(const timeout_t *a, const timeout_t *b) {
@ -60,6 +71,12 @@ void io_add(io_t *io, io_cb_t cb, void *data, int fd, int flags) {
return;
io->fd = fd;
#ifdef HAVE_MINGW
io->event = WSACreateEvent();
if (io->event == WSA_INVALID_EVENT)
abort();
event_count++;
#endif
io->cb = cb;
io->data = data;
io->node.data = io;
@ -71,8 +88,11 @@ void io_add(io_t *io, io_cb_t cb, void *data, int fd, int flags) {
}
void io_set(io_t *io, int flags) {
if (flags == io->flags)
return;
io->flags = flags;
#ifndef HAVE_MINGW
if(flags & IO_READ)
FD_SET(io->fd, &readfds);
else
@ -82,6 +102,15 @@ void io_set(io_t *io, int flags) {
FD_SET(io->fd, &writefds);
else
FD_CLR(io->fd, &writefds);
#else
long events = 0;
if (flags & IO_WRITE)
events |= WRITE_EVENTS;
if (flags & IO_READ)
events |= READ_EVENTS;
if (WSAEventSelect(io->fd, io->event, events) != 0)
abort();
#endif
}
void io_del(io_t *io) {
@ -89,6 +118,11 @@ void io_del(io_t *io) {
return;
io_set(io, 0);
#ifdef HAVE_MINGW
if (WSACloseEvent(io->event) == FALSE)
abort();
event_count--;
#endif
splay_unlink_node(&io_tree, &io->node);
io->cb = NULL;
@ -182,32 +216,39 @@ void signal_del(signal_t *sig) {
}
#endif
static struct timeval * get_time_remaining(struct timeval *diff) {
gettimeofday(&now, NULL);
struct timeval *tv = NULL;
while(timeout_tree.head) {
timeout_t *timeout = timeout_tree.head->data;
timersub(&timeout->tv, &now, diff);
if(diff->tv_sec < 0) {
timeout->cb(timeout->data);
if(timercmp(&timeout->tv, &now, <))
timeout_del(timeout);
} else {
tv = diff;
break;
}
}
return tv;
}
bool event_loop(void) {
running = true;
#ifndef HAVE_MINGW
fd_set readable;
fd_set writable;
while(running) {
gettimeofday(&now, NULL);
struct timeval diff, *tv = NULL;
while(timeout_tree.head) {
timeout_t *timeout = timeout_tree.head->data;
timersub(&timeout->tv, &now, &diff);
if(diff.tv_sec < 0) {
timeout->cb(timeout->data);
if(timercmp(&timeout->tv, &now, <))
timeout_del(timeout);
} else {
tv = &diff;
break;
}
}
memcpy(&readable, &readfds, sizeof readable);
memcpy(&writable, &writefds, sizeof writable);
struct timeval diff;
struct timeval *tv = get_time_remaining(&diff);
int fds = 0;
@ -216,13 +257,7 @@ bool event_loop(void) {
fds = last->fd + 1;
}
#ifdef HAVE_MINGW
LeaveCriticalSection(&mutex);
#endif
int n = select(fds, &readable, &writable, NULL, tv);
#ifdef HAVE_MINGW
EnterCriticalSection(&mutex);
#endif
if(n < 0) {
if(sockwouldblock(sockerrno))
@ -241,13 +276,80 @@ bool event_loop(void) {
io->cb(io->data, IO_READ);
}
}
#else
while (running) {
struct timeval diff;
struct timeval *tv = get_time_remaining(&diff);
DWORD timeout_ms = tv ? (tv->tv_sec * 1000 + tv->tv_usec / 1000 + 1) : WSA_INFINITE;
if (!event_count) {
LeaveCriticalSection(&mutex);
Sleep(timeout_ms);
EnterCriticalSection(&mutex);
continue;
}
/*
For some reason, Microsoft decided to make the FD_WRITE event edge-triggered instead of level-triggered,
which is the opposite of what select() does. In practice, that means that if a FD_WRITE event triggers,
it will never trigger again until a send() returns EWOULDBLOCK. Since the semantics of this event loop
is that write events are level-triggered (i.e. they continue firing until the socket is full), we need
to emulate these semantics by making sure we fire each IO_WRITE that is still writeable.
Note that technically FD_CLOSE has the same problem, but it's okay because user code does not rely on
this event being fired again if ignored.
*/
io_t* writeable_io = NULL;
for splay_each(io_t, io, &io_tree)
if (io->flags & IO_WRITE && send(io->fd, NULL, 0, 0) == 0) {
writeable_io = io;
break;
}
if (writeable_io) {
writeable_io->cb(writeable_io->data, IO_WRITE);
continue;
}
WSAEVENT* events = xmalloc(event_count * sizeof(*events));
DWORD event_index = 0;
for splay_each(io_t, io, &io_tree) {
events[event_index] = io->event;
event_index++;
}
LeaveCriticalSection(&mutex);
DWORD result = WSAWaitForMultipleEvents(event_count, events, FALSE, timeout_ms, FALSE);
EnterCriticalSection(&mutex);
WSAEVENT event;
if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + event_count)
event = events[result - WSA_WAIT_EVENT_0];
free(events);
if (result == WSA_WAIT_TIMEOUT)
continue;
if (result < WSA_WAIT_EVENT_0 || result >= WSA_WAIT_EVENT_0 + event_count)
return false;
io_t *io = splay_search(&io_tree, &((io_t){.event = event}));
if (!io)
abort();
WSANETWORKEVENTS network_events;
if (WSAEnumNetworkEvents(io->fd, io->event, &network_events) != 0)
return false;
if (network_events.lNetworkEvents & WRITE_EVENTS)
io->cb(io->data, IO_WRITE);
if (network_events.lNetworkEvents & READ_EVENTS)
io->cb(io->data, IO_READ);
}
#endif
return true;
}
void event_flush_output(void) {
for splay_each(io_t, io, &io_tree)
if(FD_ISSET(io->fd, &writefds))
if(io->flags & IO_WRITE)
io->cb(io->data, IO_WRITE);
}

View file

@ -32,6 +32,9 @@ typedef void (*signal_cb_t)(void *data);
typedef struct io_t {
int fd;
int flags;
#ifdef HAVE_MINGW
WSAEVENT event;
#endif
io_cb_t cb;
void *data;
splay_node_t node;