210 lines
6.2 KiB
C
210 lines
6.2 KiB
C
|
/*
|
||
|
* Copyright (c) 2009, Cybozu Labs, Inc.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright notice,
|
||
|
* this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||
|
* this list of conditions and the following disclaimer in the documentation
|
||
|
* and/or other materials provided with the distribution.
|
||
|
* * Neither the name of the <ORGANIZATION> nor the names of its contributors
|
||
|
* may be used to endorse or promote products derived from this software
|
||
|
* without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/event.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <unistd.h>
|
||
|
#include "picoev.h"
|
||
|
|
||
|
#define EV_QUEUE_SZ 128
|
||
|
|
||
|
#define BACKEND_BUILD(next_fd, events) \
|
||
|
((unsigned)((next_fd << 8) | (events & 0xff)))
|
||
|
#define BACKEND_GET_NEXT_FD(backend) ((int)(backend) >> 8)
|
||
|
#define BACKEND_GET_OLD_EVENTS(backend) ((int)(backend) & 0xff)
|
||
|
|
||
|
typedef struct picoev_loop_kqueue_st {
|
||
|
picoev_loop loop;
|
||
|
int kq;
|
||
|
int changed_fds; /* link list using picoev_fd::_backend, -1 if not changed */
|
||
|
struct kevent events[1024];
|
||
|
struct kevent changelist[256];
|
||
|
} picoev_loop_kqueue;
|
||
|
|
||
|
picoev_globals picoev;
|
||
|
|
||
|
static int apply_pending_changes(picoev_loop_kqueue* loop, int apply_all)
|
||
|
{
|
||
|
#define SET(op, events) \
|
||
|
EV_SET(loop->changelist + cl_off++, loop->changed_fds, \
|
||
|
(((events) & PICOEV_READ) != 0 ? EVFILT_READ : 0) \
|
||
|
| (((events) & PICOEV_WRITE) != 0 ? EVFILT_WRITE : 0), \
|
||
|
(op), 0, 0, NULL)
|
||
|
|
||
|
int cl_off = 0, nevents;
|
||
|
|
||
|
while (loop->changed_fds != -1) {
|
||
|
picoev_fd* changed = picoev.fds + loop->changed_fds;
|
||
|
int old_events = BACKEND_GET_OLD_EVENTS(changed->_backend);
|
||
|
if (changed->events != old_events) {
|
||
|
if (old_events != 0) {
|
||
|
SET(EV_DISABLE, old_events);
|
||
|
}
|
||
|
if (changed->events != 0) {
|
||
|
SET(EV_ADD | EV_ENABLE, changed->events);
|
||
|
}
|
||
|
if ((size_t)cl_off + 1
|
||
|
>= sizeof(loop->changelist) / sizeof(loop->changelist[0])) {
|
||
|
nevents = kevent(loop->kq, loop->changelist, cl_off, NULL, 0, NULL);
|
||
|
assert(nevents == 0);
|
||
|
cl_off = 0;
|
||
|
}
|
||
|
}
|
||
|
loop->changed_fds = BACKEND_GET_NEXT_FD(changed->_backend);
|
||
|
changed->_backend = -1;
|
||
|
}
|
||
|
|
||
|
if (apply_all && cl_off != 0) {
|
||
|
nevents = kevent(loop->kq, loop->changelist, cl_off, NULL, 0, NULL);
|
||
|
assert(nevents == 0);
|
||
|
cl_off = 0;
|
||
|
}
|
||
|
|
||
|
return cl_off;
|
||
|
|
||
|
#undef SET
|
||
|
}
|
||
|
|
||
|
picoev_loop* picoev_create_loop(int max_timeout)
|
||
|
{
|
||
|
picoev_loop_kqueue* loop;
|
||
|
|
||
|
/* init parent */
|
||
|
assert(PICOEV_IS_INITED);
|
||
|
if ((loop = (picoev_loop_kqueue*)malloc(sizeof(picoev_loop_kqueue)))
|
||
|
== NULL) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (picoev_init_loop_internal(&loop->loop, max_timeout) != 0) {
|
||
|
free(loop);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* init kqueue */
|
||
|
if ((loop->kq = kqueue()) == -1) {
|
||
|
picoev_deinit_loop_internal(&loop->loop);
|
||
|
free(loop);
|
||
|
return NULL;
|
||
|
}
|
||
|
loop->changed_fds = -1;
|
||
|
|
||
|
loop->loop.now = time(NULL);
|
||
|
return &loop->loop;
|
||
|
}
|
||
|
|
||
|
int picoev_destroy_loop(picoev_loop* _loop)
|
||
|
{
|
||
|
picoev_loop_kqueue* loop = (picoev_loop_kqueue*)_loop;
|
||
|
|
||
|
if (close(loop->kq) != 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
picoev_deinit_loop_internal(&loop->loop);
|
||
|
free(loop);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int picoev_update_events_internal(picoev_loop* _loop, int fd, int events)
|
||
|
{
|
||
|
picoev_loop_kqueue* loop = (picoev_loop_kqueue*)_loop;
|
||
|
picoev_fd* target = picoev.fds + fd;
|
||
|
|
||
|
assert(PICOEV_FD_BELONGS_TO_LOOP(&loop->loop, fd));
|
||
|
|
||
|
/* initialize if adding the fd */
|
||
|
if ((events & PICOEV_ADD) != 0) {
|
||
|
target->_backend = -1;
|
||
|
}
|
||
|
/* return if nothing to do */
|
||
|
if (events == PICOEV_DEL
|
||
|
? target->_backend == -1
|
||
|
: (events & PICOEV_READWRITE) == target->events) {
|
||
|
return 0;
|
||
|
}
|
||
|
/* add to changed list if not yet being done */
|
||
|
if (target->_backend == -1) {
|
||
|
target->_backend = BACKEND_BUILD(loop->changed_fds, target->events);
|
||
|
loop->changed_fds = fd;
|
||
|
}
|
||
|
/* update events */
|
||
|
target->events = events & PICOEV_READWRITE;
|
||
|
/* apply immediately if is a DELETE */
|
||
|
if ((events & PICOEV_DEL) != 0) {
|
||
|
apply_pending_changes(loop, 1);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int picoev_poll_once_internal(picoev_loop* _loop, int max_wait)
|
||
|
{
|
||
|
picoev_loop_kqueue* loop = (picoev_loop_kqueue*)_loop;
|
||
|
struct timespec ts;
|
||
|
int cl_off = 0, nevents, i;
|
||
|
|
||
|
/* apply pending changes, with last changes stored to loop->changelist */
|
||
|
cl_off = apply_pending_changes(loop, 0);
|
||
|
|
||
|
ts.tv_sec = max_wait;
|
||
|
ts.tv_nsec = 0;
|
||
|
nevents = kevent(loop->kq, loop->changelist, cl_off, loop->events,
|
||
|
sizeof(loop->events) / sizeof(loop->events[0]), &ts);
|
||
|
if (nevents == -1) {
|
||
|
/* the errors we can only rescue */
|
||
|
assert(errno == EACCES || errno == EFAULT || errno == EINTR);
|
||
|
return -1;
|
||
|
}
|
||
|
for (i = 0; i < nevents; ++i) {
|
||
|
struct kevent* event = loop->events + i;
|
||
|
picoev_fd* target = picoev.fds + event->ident;
|
||
|
assert((event->flags & EV_ERROR) == 0); /* changelist errors are fatal */
|
||
|
if (loop->loop.loop_id == target->loop_id
|
||
|
&& (event->filter & (EVFILT_READ | EVFILT_WRITE)) != 0) {
|
||
|
int revents;
|
||
|
switch (event->filter) {
|
||
|
case EVFILT_READ:
|
||
|
revents = PICOEV_READ;
|
||
|
break;
|
||
|
case EVFILT_WRITE:
|
||
|
revents = PICOEV_WRITE;
|
||
|
break;
|
||
|
default:
|
||
|
assert(0);
|
||
|
revents = 0; // suppress compiler warning
|
||
|
break;
|
||
|
}
|
||
|
(*target->callback)(&loop->loop, event->ident, revents, target->cb_arg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|