Use native Windows events for the event loop.
authorEtienne Dechamps <etienne@edechamps.fr>
Fri, 27 Jun 2014 20:58:35 +0000 (21:58 +0100)
committerEtienne Dechamps <etienne@edechamps.fr>
Sat, 28 Jun 2014 17:45:13 +0000 (18:45 +0100)
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
src/event.h

index 27b884c..0e4e2bd 100644 (file)
 #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);
 }
 
index c6522c0..bebb520 100644 (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;