12359735bf3cd084db9d5c19273febc3aa769554
[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;
41     time_t to;
42     struct muth *th;
43 };
44
45 static int qfd = -1, fdln = 0;
46 static int exitstatus;
47 static struct blocker **fdlist;
48
49 static int regfd(struct blocker *bl)
50 {
51     struct blocker *o;
52     int prev;
53     struct kevent evd;
54     
55     if(bl->fd >= fdln) {
56         if(fdlist) {
57             fdlist = srealloc(fdlist, sizeof(*fdlist) * (bl->fd + 1));
58             memset(fdlist + fdln, 0, sizeof(*fdlist) * (bl->fd + 1 - fdln));
59             fdln = bl->fd + 1;
60         } else {
61             fdlist = szmalloc(sizeof(*fdlist) * (fdln = (bl->fd + 1)));
62         }
63     }
64     for(prev = 0, o = fdlist[bl->fd]; o; o = o->n2)
65         prev |= o->ev;
66     if((bl->ev & EV_READ) && !(prev & EV_READ)) {
67         evd = (struct kevent) {
68             .flags = EV_ADD,
69             .ident = bl->fd,
70             .filter = EVFILT_READ,
71         };
72         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
73             /* XXX?! Whatever to do, really? */
74             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
75             return(-1);
76         }
77     }
78     if((bl->ev & EV_WRITE) && !(prev & EV_WRITE)) {
79         evd = (struct kevent) {
80             .flags = EV_ADD,
81             .ident = bl->fd,
82             .filter = EVFILT_WRITE,
83         };
84         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
85             /* XXX?! Whatever to do, really? */
86             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
87             return(-1);
88         }
89     }
90     bl->n2 = fdlist[bl->fd];
91     bl->p2 = NULL;
92     if(fdlist[bl->fd] != NULL)
93         fdlist[bl->fd]->p2 = bl;
94     fdlist[bl->fd] = bl;
95     bl->reg = 1;
96     return(0);
97 }
98
99 static void remfd(struct blocker *bl)
100 {
101     struct blocker *o;
102     struct kevent evd;
103     int left;
104     
105     if(!bl->reg)
106         return;
107     if(bl->n2)
108         bl->n2->p2 = bl->p2;
109     if(bl->p2)
110         bl->p2->n2 = bl->n2;
111     if(bl == fdlist[bl->fd])
112         fdlist[bl->fd] = bl->n2;
113     for(left = 0, o = fdlist[bl->fd]; o; o = o->n2)
114         left |= o->ev;
115     if((bl->ev & EV_READ) && !(left & EV_READ)) {
116         evd = (struct kevent) {
117             .flags = EV_DELETE,
118             .ident = bl->fd,
119             .filter = EVFILT_READ,
120         };
121         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
122             /* XXX?! Whatever to do, really? */
123             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
124         }
125     }
126     if((bl->ev & EV_WRITE) && !(left & EV_WRITE)) {
127         evd = (struct kevent) {
128             .flags = EV_DELETE,
129             .ident = bl->fd,
130             .filter = EVFILT_WRITE,
131         };
132         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
133             /* XXX?! Whatever to do, really? */
134             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
135         }
136     }
137     bl->reg = 0;
138 }
139
140 int block(int fd, int ev, time_t to)
141 {
142     struct blocker *bl;
143     int rv;
144     
145     omalloc(bl);
146     bl->fd = fd;
147     bl->ev = ev;
148     if(to > 0)
149         bl->to = time(NULL) + to;
150     bl->th = current;
151     if((qfd >= 0) && regfd(bl)) {
152         free(bl);
153         return(-1);
154     }
155     bl->n = blockers;
156     if(blockers)
157         blockers->p = bl;
158     blockers = bl;
159     rv = yield();
160     if(bl->n)
161         bl->n->p = bl->p;
162     if(bl->p)
163         bl->p->n = bl->n;
164     if(bl == blockers)
165         blockers = bl->n;
166     remfd(bl);
167     free(bl);
168     return(rv);
169 }
170
171 int ioloop(void)
172 {
173     struct blocker *bl, *nbl;
174     struct kevent evs[16];
175     int i, fd, nev, ev;
176     time_t now, timeout;
177     struct timespec *toval;
178     
179     exitstatus = 0;
180     qfd = kqueue();
181     fcntl(qfd, F_SETFD, FD_CLOEXEC);
182     for(bl = blockers; bl; bl = nbl) {
183         nbl = bl->n;
184         if(regfd(bl))
185             resume(bl->th, -1);
186     }
187     while(blockers != NULL) {
188         timeout = 0;
189         for(bl = blockers; bl; bl = bl->n) {
190             if((bl->to != 0) && ((timeout == 0) || (timeout > bl->to)))
191                 timeout = bl->to;
192         }
193         now = time(NULL);
194         if(timeout == 0)
195             toval  = NULL;
196         else if(timeout > now)
197             toval = &(struct timespec){.tv_sec = timeout - now};
198         else
199             toval = &(struct timespec){.tv_sec = 1};
200         if(exitstatus)
201             break;
202         nev = kevent(qfd, NULL, 0, evs, sizeof(evs) / sizeof(*evs), toval);
203         if(nev < 0) {
204             if(errno != EINTR) {
205                 flog(LOG_CRIT, "ioloop: kevent errored out: %s", strerror(errno));
206                 /* To avoid CPU hogging in case it's bad, which it
207                  * probably is. */
208                 sleep(1);
209             }
210             continue;
211         }
212         for(i = 0; i < nev; i++) {
213             fd = (int)evs[i].ident;
214             ev = (evs[i].filter == EVFILT_READ)?EV_READ:EV_WRITE;
215             for(bl = fdlist[fd]; bl; bl = nbl) {
216                 nbl = bl->n2;
217                 if(ev & bl->ev)
218                     resume(bl->th, ev);
219             }
220         }
221         now = time(NULL);
222         for(bl = blockers; bl; bl = nbl) {
223             nbl = bl->n;
224             if((bl->to != 0) && (bl->to <= now))
225                 resume(bl->th, 0);
226         }
227     }
228     for(bl = blockers; bl; bl = bl->n)
229         remfd(bl);
230     close(qfd);
231     qfd = -1;
232     return(exitstatus);
233 }
234
235 void exitioloop(int status)
236 {
237     exitstatus = status;
238 }