Correctly handle a writable->unwritable transition on a socket in kevent.
[srvx.git] / src / ioset-kevent.c
1 /* ioset kqueue()/kevent() backend for srvx
2  * Copyright 2008 srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 #include "ioset-impl.h"
22 #include "common.h"
23 #include "log.h"
24
25 #ifdef HAVE_SYS_EVENT_H
26 # include <sys/event.h>
27 #endif
28
29 #define MAX_EVENTS 16
30
31 extern int clock_skew;
32 static int kq_fd;
33
34 static int
35 ioset_kevent_init(void)
36 {
37     kq_fd = kqueue();
38     return kq_fd >= 0;
39 }
40
41 static void
42 ioset_kevent_add(struct io_fd *fd)
43 {
44     struct kevent changes[2];
45     int nchanges = 0;
46     int res;
47
48     EV_SET(&changes[nchanges++], fd->fd, EVFILT_READ, EV_ADD, 0, 0, fd);
49     EV_SET(&changes[nchanges++], fd->fd, EVFILT_WRITE, fd_wants_writes(fd) ? EV_ADD : EV_DELETE, 0, 0, fd);
50     res = kevent(kq_fd, changes, nchanges, NULL, 0, NULL);
51     if (res < 0) {
52         log_module(MAIN_LOG, LOG_ERROR, "kevent() add failed: %s", strerror(errno));
53     }
54 }
55
56 static void
57 ioset_kevent_remove(struct io_fd *fd, int closed)
58 {
59     if (!closed) {
60         struct kevent changes[2];
61         int nchanges = 0;
62         int res;
63
64         EV_SET(&changes[nchanges++], fd->fd, EVFILT_READ, EV_DELETE, 0, 0, fd);
65         EV_SET(&changes[nchanges++], fd->fd, EVFILT_WRITE, EV_DELETE, 0, 0, fd);
66         res = kevent(kq_fd, changes, nchanges, NULL, 0, NULL);
67         if (res < 0) {
68             log_module(MAIN_LOG, LOG_ERROR, "kevent() remove failed: %s", strerror(errno));
69         }
70     }
71 }
72
73 static void
74 ioset_kevent_update(struct io_fd *fd)
75 {
76     ioset_kevent_add(fd);
77 }
78
79 static void
80 ioset_kevent_cleanup(void)
81 {
82     close(kq_fd);
83 }
84
85 static int
86 ioset_kevent_loop(struct timeval *timeout)
87 {
88     struct kevent events[MAX_EVENTS];
89     struct timespec ts;
90     struct timespec *pts;
91     int is_write;
92     int is_read;
93     int res;
94     int ii;
95
96     /* Try to get events from the kernel. */
97     if (timeout) {
98         ts.tv_sec = timeout->tv_sec;
99         ts.tv_nsec = timeout->tv_usec * 1000;
100         pts = &ts;
101     } else {
102         pts = NULL;
103     }
104     res = kevent(kq_fd, NULL, 0, events, MAX_EVENTS, pts);
105     if (res < 0) {
106         log_module(MAIN_LOG, LOG_ERROR, "kevent() poll failed: %s", strerror(errno));
107         return 1;
108     }
109
110     /* Process the events we got. */
111     for (ii = 0; ii < res; ++ii) {
112         is_write = events[ii].filter == EVFILT_WRITE;
113         is_read = events[ii].filter == EVFILT_READ;
114         ioset_events(events[ii].udata, is_read, is_write);
115     }
116
117     return 0;
118 }
119
120 struct io_engine io_engine_kevent = {
121     .name = "kevent",
122     .init = ioset_kevent_init,
123     .add = ioset_kevent_add,
124     .remove = ioset_kevent_remove,
125     .update = ioset_kevent_update,
126     .loop = ioset_kevent_loop,
127     .cleanup = ioset_kevent_cleanup,
128 };