a8216885f8721678b7d22e434131f1b9c893fe76
[ashd.git] / lib / mtio-kqueue.c
1 /*
2     ashd - A Sane HTTP Daemon
3     Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
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 3 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
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <time.h>
22 #include <fcntl.h>
23 #include <string.h>
24 #include <sys/event.h>
25 #include <errno.h>
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30 #include <log.h>
31 #include <utils.h>
32 #include <mt.h>
33 #include <mtio.h>
34
35 static struct blocker *blockers;
36
37 struct blocker {
38     struct blocker *n, *p, *n2, *p2;
39     int fd, reg;
40     int ev, rev, id;
41     int thpos;
42     time_t to;
43     struct muth *th;
44 };
45
46 static int qfd = -1, fdln = 0;
47 static int exitstatus;
48 static struct blocker **fdlist;
49 static typedbuf(struct blocker *) timeheap;
50
51 static int regfd(struct blocker *bl)
52 {
53     struct blocker *o;
54     int prev;
55     struct kevent evd;
56     
57     if(bl->fd < 0)
58         return(0);
59     if(bl->fd >= fdln) {
60         if(fdlist) {
61             fdlist = srealloc(fdlist, sizeof(*fdlist) * (bl->fd + 1));
62             memset(fdlist + fdln, 0, sizeof(*fdlist) * (bl->fd + 1 - fdln));
63             fdln = bl->fd + 1;
64         } else {
65             fdlist = szmalloc(sizeof(*fdlist) * (fdln = (bl->fd + 1)));
66         }
67     }
68     for(prev = 0, o = fdlist[bl->fd]; o; o = o->n2)
69         prev |= o->ev;
70     if((bl->ev & EV_READ) && !(prev & EV_READ)) {
71         evd = (struct kevent) {
72             .flags = EV_ADD,
73             .ident = bl->fd,
74             .filter = EVFILT_READ,
75         };
76         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
77             /* XXX?! Whatever to do, really? */
78             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
79             return(-1);
80         }
81     }
82     if((bl->ev & EV_WRITE) && !(prev & EV_WRITE)) {
83         evd = (struct kevent) {
84             .flags = EV_ADD,
85             .ident = bl->fd,
86             .filter = EVFILT_WRITE,
87         };
88         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
89             /* XXX?! Whatever to do, really? */
90             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
91             return(-1);
92         }
93     }
94     bl->n2 = fdlist[bl->fd];
95     bl->p2 = NULL;
96     if(fdlist[bl->fd] != NULL)
97         fdlist[bl->fd]->p2 = bl;
98     fdlist[bl->fd] = bl;
99     bl->reg = 1;
100     return(0);
101 }
102
103 static void remfd(struct blocker *bl)
104 {
105     struct blocker *o;
106     struct kevent evd;
107     int left;
108     
109     if(!bl->reg)
110         return;
111     if(bl->n2)
112         bl->n2->p2 = bl->p2;
113     if(bl->p2)
114         bl->p2->n2 = bl->n2;
115     if(bl == fdlist[bl->fd])
116         fdlist[bl->fd] = bl->n2;
117     for(left = 0, o = fdlist[bl->fd]; o; o = o->n2)
118         left |= o->ev;
119     if((bl->ev & EV_READ) && !(left & EV_READ)) {
120         evd = (struct kevent) {
121             .flags = EV_DELETE,
122             .ident = bl->fd,
123             .filter = EVFILT_READ,
124         };
125         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
126             /* XXX?! Whatever to do, really? */
127             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
128         }
129     }
130     if((bl->ev & EV_WRITE) && !(left & EV_WRITE)) {
131         evd = (struct kevent) {
132             .flags = EV_DELETE,
133             .ident = bl->fd,
134             .filter = EVFILT_WRITE,
135         };
136         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
137             /* XXX?! Whatever to do, really? */
138             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
139         }
140     }
141     bl->reg = 0;
142 }
143
144 static void thraise(struct blocker *bl, int n)
145 {
146     int p;
147     
148     while(n > 0) {
149         p = (n - 1) >> 1;
150         if(timeheap.b[p]->to <= bl->to)
151             break;
152         timeheap.b[n] = timeheap.b[p];
153         timeheap.b[n]->thpos = n;
154         n = p;
155     }
156     timeheap.b[n] = bl;
157     bl->thpos = n;
158 }
159
160 static void thlower(struct blocker *bl, int n)
161 {
162     int c;
163     
164     while(1) {
165         c = (n << 1) + 1;
166         if(c >= timeheap.d)
167             break;
168         if((c + 1 < timeheap.d) && (timeheap.b[c + 1]->to < timeheap.b[c]->to))
169             c = c + 1;
170         if(timeheap.b[c]->to > bl->to)
171             break;
172         timeheap.b[n] = timeheap.b[c];
173         timeheap.b[n]->thpos = n;
174         n = c;
175     }
176     timeheap.b[n] = bl;
177     bl->thpos = n;
178 }
179
180 static void addtimeout(struct blocker *bl, time_t to)
181 {
182     sizebuf(timeheap, ++timeheap.d);
183     thraise(bl, timeheap.d - 1);
184 }
185
186 static void deltimeout(struct blocker *bl)
187 {
188     int n;
189     
190     if(bl->thpos == timeheap.d - 1) {
191         timeheap.d--;
192         return;
193     }
194     n = bl->thpos;
195     bl = timeheap.b[--timeheap.d];
196     if((n > 0) && (timeheap.b[(n - 1) >> 1]->to > bl->to))
197         thraise(bl, n);
198     else
199         thlower(bl, n);
200 }
201
202 static int addblock(struct blocker *bl)
203 {
204     if((qfd >= 0) && regfd(bl))
205         return(-1);
206     bl->n = blockers;
207     if(blockers)
208         blockers->p = bl;
209     blockers = bl;
210     if(bl->to > 0)
211         addtimeout(bl, bl->to);
212     return(0);
213 }
214
215 static void remblock(struct blocker *bl)
216 {
217     if(bl->to > 0)
218         deltimeout(bl);
219     if(bl->n)
220         bl->n->p = bl->p;
221     if(bl->p)
222         bl->p->n = bl->n;
223     if(bl == blockers)
224         blockers = bl->n;
225     remfd(bl);
226 }
227
228 struct selected mblock(time_t to, int n, struct selected *spec)
229 {
230     int i, id;
231     struct blocker bls[n];
232     
233     to = (to > 0)?(time(NULL) + to):0;
234     for(i = 0; i < n; i++) {
235         bls[i] = (struct blocker) {
236             .fd = spec[i].fd,
237             .ev = spec[i].ev,
238             .id = i,
239             .to = to,
240             .th = current,
241         };
242         if(addblock(&bls[i])) {
243             for(i--; i >= 0; i--)
244                 remblock(&bls[i]);
245             return((struct selected){.fd = -1, .ev = -1});
246         }
247     }
248     id = yield();
249     for(i = 0; i < n; i++)
250         remblock(&bls[i]);
251     if(id < 0)
252         return((struct selected){.fd = -1, .ev = -1});
253     return((struct selected){.fd = bls[id].fd, .ev = bls[id].rev});
254 }
255
256 int block(int fd, int ev, time_t to)
257 {
258     struct blocker bl;
259     int rv;
260     
261     bl = (struct blocker) {
262         .fd = fd,
263         .ev = ev,
264         .id = -1,
265         .to = (to > 0)?(time(NULL) + to):0,
266         .th = current,
267     };
268     addblock(&bl);
269     rv = yield();
270     remblock(&bl);
271     return(rv);
272 }
273
274 int ioloop(void)
275 {
276     struct blocker *bl, *nbl;
277     struct kevent evs[16];
278     int i, fd, nev, ev;
279     time_t now;
280     struct timespec *toval;
281     
282     exitstatus = 0;
283     qfd = kqueue();
284     fcntl(qfd, F_SETFD, FD_CLOEXEC);
285     for(bl = blockers; bl; bl = nbl) {
286         nbl = bl->n;
287         if(regfd(bl))
288             resume(bl->th, -1);
289     }
290     while(blockers != NULL) {
291         now = time(NULL);
292         toval = &(struct timespec){};
293         if(timeheap.d == 0)
294             toval  = NULL;
295         else if(timeheap.b[0]->to > now)
296             *toval = (struct timespec){.tv_sec = timeheap.b[0]->to - now};
297         else
298             *toval = (struct timespec){.tv_sec = 1};
299         if(exitstatus)
300             break;
301         nev = kevent(qfd, NULL, 0, evs, sizeof(evs) / sizeof(*evs), toval);
302         if(nev < 0) {
303             if(errno != EINTR) {
304                 flog(LOG_CRIT, "ioloop: kevent errored out: %s", strerror(errno));
305                 /* To avoid CPU hogging in case it's bad, which it
306                  * probably is. */
307                 sleep(1);
308             }
309             continue;
310         }
311         for(i = 0; i < nev; i++) {
312             fd = (int)evs[i].ident;
313             ev = (evs[i].filter == EVFILT_READ)?EV_READ:EV_WRITE;
314             for(bl = fdlist[fd]; bl; bl = nbl) {
315                 nbl = bl->n2;
316                 if(ev & bl->ev) {
317                     if(bl->id < 0) {
318                         resume(bl->th, ev);
319                     } else {
320                         bl->rev = ev;
321                         resume(bl->th, bl->id);
322                     }
323                 }
324             }
325         }
326         now = time(NULL);
327         while((timeheap.d > 0) && ((bl = timeheap.b[0])->to <= now)) {
328             if(bl->id < 0) {
329                 resume(bl->th, 0);
330             } else {
331                 bl->rev = 0;
332                 resume(bl->th, bl->id);
333             }
334         }
335     }
336     for(bl = blockers; bl; bl = bl->n)
337         remfd(bl);
338     close(qfd);
339     qfd = -1;
340     return(exitstatus);
341 }
342
343 void exitioloop(int status)
344 {
345     exitstatus = status;
346 }