From 611217c96ec684799882cf330f40a0936131b6b5 Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Fri, 27 Jun 2014 21:58:35 +0100 Subject: [PATCH] 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). --- src/event.c | 150 +++++++++++++++++++++++++++++++++++++++++++--------- src/event.h | 3 ++ 2 files changed, 129 insertions(+), 24 deletions(-) diff --git a/src/event.c b/src/event.c index 27b884c4..0e4e2bdf 100644 --- a/src/event.c +++ b/src/event.c @@ -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); } diff --git a/src/event.h b/src/event.h index c6522c0e..bebb520d 100644 --- a/src/event.h +++ b/src/event.h @@ -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; -- 2.20.1