Use native Windows events for the event loop.
[tinc] / src / event.c
1 /*
2     event.c -- I/O, timeout and signal event handling
3     Copyright (C) 2012-2013 Guus Sliepen <guus@tinc-vpn.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "system.h"
21
22 #include "dropin.h"
23 #include "event.h"
24 #include "net.h"
25 #include "utils.h"
26 #include "xalloc.h"
27
28 struct timeval now;
29
30 #ifndef HAVE_MINGW
31 static fd_set readfds;
32 static fd_set writefds;
33 #else
34 static const long READ_EVENTS = FD_READ | FD_ACCEPT | FD_CLOSE;
35 static const long WRITE_EVENTS = FD_WRITE | FD_CONNECT;
36 static DWORD event_count = 0;
37 #endif
38 static volatile bool running;
39
40 static int io_compare(const io_t *a, const io_t *b) {
41 #ifndef HAVE_MINGW
42         return a->fd - b->fd;
43 #else
44         return a->event - b->event;
45 #endif
46 }
47
48 static int timeout_compare(const timeout_t *a, const timeout_t *b) {
49         struct timeval diff;
50         timersub(&a->tv, &b->tv, &diff);
51         if(diff.tv_sec < 0)
52                 return -1;
53         if(diff.tv_sec > 0)
54                 return 1;
55         if(diff.tv_usec < 0)
56                 return -1;
57         if(diff.tv_usec > 0)
58                 return 1;
59         if(a < b)
60                 return -1;
61         if(a > b)
62                 return 1;
63         return 0;
64 }
65
66 static splay_tree_t io_tree = {.compare = (splay_compare_t)io_compare};
67 static splay_tree_t timeout_tree = {.compare = (splay_compare_t)timeout_compare};
68
69 void io_add(io_t *io, io_cb_t cb, void *data, int fd, int flags) {
70         if(io->cb)
71                 return;
72
73         io->fd = fd;
74 #ifdef HAVE_MINGW
75         io->event = WSACreateEvent();
76         if (io->event == WSA_INVALID_EVENT)
77                 abort();
78         event_count++;
79 #endif
80         io->cb = cb;
81         io->data = data;
82         io->node.data = io;
83
84         io_set(io, flags);
85
86         if(!splay_insert_node(&io_tree, &io->node))
87                 abort();
88 }
89
90 void io_set(io_t *io, int flags) {
91         if (flags == io->flags)
92                 return;
93         io->flags = flags;
94
95 #ifndef HAVE_MINGW
96         if(flags & IO_READ)
97                 FD_SET(io->fd, &readfds);
98         else
99                 FD_CLR(io->fd, &readfds);
100
101         if(flags & IO_WRITE)
102                 FD_SET(io->fd, &writefds);
103         else
104                 FD_CLR(io->fd, &writefds);
105 #else
106         long events = 0;
107         if (flags & IO_WRITE)
108                 events |= WRITE_EVENTS;
109         if (flags & IO_READ)
110                 events |= READ_EVENTS;
111         if (WSAEventSelect(io->fd, io->event, events) != 0)
112                 abort();
113 #endif
114 }
115
116 void io_del(io_t *io) {
117         if(!io->cb)
118                 return;
119
120         io_set(io, 0);
121 #ifdef HAVE_MINGW
122         if (WSACloseEvent(io->event) == FALSE)
123                 abort();
124         event_count--;
125 #endif
126
127         splay_unlink_node(&io_tree, &io->node);
128         io->cb = NULL;
129 }
130
131 void timeout_add(timeout_t *timeout, timeout_cb_t cb, void *data, struct timeval *tv) {
132         timeout->cb = cb;
133         timeout->data = data;
134         timeout->node.data = timeout;
135
136         timeout_set(timeout, tv);
137 }
138
139 void timeout_set(timeout_t *timeout, struct timeval *tv) {
140         if(timerisset(&timeout->tv))
141                 splay_unlink_node(&timeout_tree, &timeout->node);
142
143         if(!now.tv_sec)
144                 gettimeofday(&now, NULL);
145
146         timeradd(&now, tv, &timeout->tv);
147
148         if(!splay_insert_node(&timeout_tree, &timeout->node))
149                 abort();
150 }
151
152 void timeout_del(timeout_t *timeout) {
153         if(!timeout->cb)
154                 return;
155
156         splay_unlink_node(&timeout_tree, &timeout->node);
157         timeout->cb = 0;
158         timeout->tv = (struct timeval){0, 0};
159 }
160
161 #ifndef HAVE_MINGW
162 static int signal_compare(const signal_t *a, const signal_t *b) {
163         return a->signum - b->signum;
164 }
165
166 static io_t signalio;
167 static int pipefd[2] = {-1, -1};
168 static splay_tree_t signal_tree = {.compare = (splay_compare_t)signal_compare};
169
170 static void signal_handler(int signum) {
171         unsigned char num = signum;
172         write(pipefd[1], &num, 1);
173 }
174
175 static void signalio_handler(void *data, int flags) {
176         unsigned char signum;
177         if(read(pipefd[0], &signum, 1) != 1)
178                 return;
179
180         signal_t *sig = splay_search(&signal_tree, &((signal_t){.signum = signum}));
181         if(sig)
182                 sig->cb(sig->data);
183 }
184
185 static void pipe_init(void) {
186         if(!pipe(pipefd))
187                 io_add(&signalio, signalio_handler, NULL, pipefd[0], IO_READ);
188 }
189
190 void signal_add(signal_t *sig, signal_cb_t cb, void *data, int signum) {
191         if(sig->cb)
192                 return;
193
194         sig->cb = cb;
195         sig->data = data;
196         sig->signum = signum;
197         sig->node.data = sig;
198
199         if(pipefd[0] == -1)
200                 pipe_init();
201
202         signal(sig->signum, signal_handler);
203
204         if(!splay_insert_node(&signal_tree, &sig->node))
205                 abort();
206 }
207
208 void signal_del(signal_t *sig) {
209         if(!sig->cb)
210                 return;
211
212         signal(sig->signum, SIG_DFL);
213
214         splay_unlink_node(&signal_tree, &sig->node);
215         sig->cb = NULL;
216 }
217 #endif
218
219 static struct timeval * get_time_remaining(struct timeval *diff) {
220         gettimeofday(&now, NULL);
221         struct timeval *tv = NULL;
222
223         while(timeout_tree.head) {
224                 timeout_t *timeout = timeout_tree.head->data;
225                 timersub(&timeout->tv, &now, diff);
226
227                 if(diff->tv_sec < 0) {
228                         timeout->cb(timeout->data);
229                         if(timercmp(&timeout->tv, &now, <))
230                                 timeout_del(timeout);
231                 } else {
232                         tv = diff;
233                         break;
234                 }
235         }
236
237         return tv;
238 }
239
240 bool event_loop(void) {
241         running = true;
242
243 #ifndef HAVE_MINGW
244         fd_set readable;
245         fd_set writable;
246
247         while(running) {
248                 memcpy(&readable, &readfds, sizeof readable);
249                 memcpy(&writable, &writefds, sizeof writable);
250                 struct timeval diff;
251                 struct timeval *tv = get_time_remaining(&diff);
252
253                 int fds = 0;
254
255                 if(io_tree.tail) {
256                         io_t *last = io_tree.tail->data;
257                         fds = last->fd + 1;
258                 }
259
260                 int n = select(fds, &readable, &writable, NULL, tv);
261
262                 if(n < 0) {
263                         if(sockwouldblock(sockerrno))
264                                 continue;
265                         else
266                                 return false;
267                 }
268
269                 if(!n)
270                         continue;
271
272                 for splay_each(io_t, io, &io_tree) {
273                         if(FD_ISSET(io->fd, &writable))
274                                 io->cb(io->data, IO_WRITE);
275                         else if(FD_ISSET(io->fd, &readable))
276                                 io->cb(io->data, IO_READ);
277                 }
278         }
279 #else
280         while (running) {
281                 struct timeval diff;
282                 struct timeval *tv = get_time_remaining(&diff);
283                 DWORD timeout_ms = tv ? (tv->tv_sec * 1000 + tv->tv_usec / 1000 + 1) : WSA_INFINITE;
284
285                 if (!event_count) {
286                         LeaveCriticalSection(&mutex);
287                         Sleep(timeout_ms);
288                         EnterCriticalSection(&mutex);
289                         continue;
290                 }
291
292                 /*
293                    For some reason, Microsoft decided to make the FD_WRITE event edge-triggered instead of level-triggered,
294                    which is the opposite of what select() does. In practice, that means that if a FD_WRITE event triggers,
295                    it will never trigger again until a send() returns EWOULDBLOCK. Since the semantics of this event loop
296                    is that write events are level-triggered (i.e. they continue firing until the socket is full), we need
297                    to emulate these semantics by making sure we fire each IO_WRITE that is still writeable.
298
299                    Note that technically FD_CLOSE has the same problem, but it's okay because user code does not rely on
300                    this event being fired again if ignored.
301                 */
302                 io_t* writeable_io = NULL;
303                 for splay_each(io_t, io, &io_tree)
304                         if (io->flags & IO_WRITE && send(io->fd, NULL, 0, 0) == 0) {
305                                 writeable_io = io;
306                                 break;
307                         }
308                 if (writeable_io) {
309                         writeable_io->cb(writeable_io->data, IO_WRITE);
310                         continue;
311                 }
312
313                 WSAEVENT* events = xmalloc(event_count * sizeof(*events));
314                 DWORD event_index = 0;
315                 for splay_each(io_t, io, &io_tree) {
316                         events[event_index] = io->event;
317                         event_index++;
318                 }
319
320                 LeaveCriticalSection(&mutex);
321                 DWORD result = WSAWaitForMultipleEvents(event_count, events, FALSE, timeout_ms, FALSE);
322                 EnterCriticalSection(&mutex);
323
324                 WSAEVENT event;
325                 if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + event_count)
326                         event = events[result - WSA_WAIT_EVENT_0];
327                 free(events);
328                 if (result == WSA_WAIT_TIMEOUT)
329                         continue;
330                 if (result < WSA_WAIT_EVENT_0 || result >= WSA_WAIT_EVENT_0 + event_count)
331                         return false;
332
333                 io_t *io = splay_search(&io_tree, &((io_t){.event = event}));
334                 if (!io)
335                         abort();
336
337                 WSANETWORKEVENTS network_events;
338                 if (WSAEnumNetworkEvents(io->fd, io->event, &network_events) != 0)
339                         return false;
340                 if (network_events.lNetworkEvents & WRITE_EVENTS)
341                         io->cb(io->data, IO_WRITE);
342                 if (network_events.lNetworkEvents & READ_EVENTS)
343                         io->cb(io->data, IO_READ);
344         }
345 #endif
346
347         return true;
348 }
349
350 void event_flush_output(void) {
351         for splay_each(io_t, io, &io_tree)
352                 if(io->flags & IO_WRITE)
353                         io->cb(io->data, IO_WRITE);
354 }
355
356 void event_exit(void) {
357         running = false;
358 }