diff options
Diffstat (limited to 'src/lib/ioloop-kqueue.c')
-rw-r--r-- | src/lib/ioloop-kqueue.c | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/src/lib/ioloop-kqueue.c b/src/lib/ioloop-kqueue.c new file mode 100644 index 0000000..061b1b1 --- /dev/null +++ b/src/lib/ioloop-kqueue.c @@ -0,0 +1,175 @@ +/* + * BSD kqueue() based ioloop handler. + * + * Copyright (c) 2005 Vaclav Haisman <v.haisman@sh.cvut.cz> + */ + +#include "lib.h" + +#ifdef IOLOOP_KQUEUE + +#include "array.h" +#include "sleep.h" +#include "ioloop-private.h" + +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/event.h> +#include <sys/time.h> + +/* kevent.udata's type just has to be different in NetBSD than in + FreeBSD and OpenBSD.. */ +#ifdef __NetBSD__ +# define MY_EV_SET(a, b, c, d, e, f, g) \ + EV_SET(a, b, c, d, e, f, (intptr_t)g) +#else +# define MY_EV_SET(a, b, c, d, e, f, g) \ + EV_SET(a, b, c, d, e, f, g) +#endif + +struct ioloop_handler_context { + int kq; + + unsigned int deleted_count; + ARRAY(struct kevent) events; +}; + +void io_loop_handler_init(struct ioloop *ioloop, unsigned int initial_fd_count) +{ + struct ioloop_handler_context *ctx; + + ioloop->handler_context = ctx = i_new(struct ioloop_handler_context, 1); + ctx->kq = kqueue(); + if (ctx->kq < 0) + i_fatal("kqueue() in io_loop_handler_init() failed: %m"); + fd_close_on_exec(ctx->kq, TRUE); + + i_array_init(&ctx->events, initial_fd_count); +} + +void io_loop_handler_deinit(struct ioloop *ioloop) +{ + if (close(ioloop->handler_context->kq) < 0) + i_error("close(kqueue) in io_loop_handler_deinit() failed: %m"); + array_free(&ioloop->handler_context->events); + i_free(ioloop->handler_context); +} + +void io_loop_handle_add(struct io_file *io) +{ + struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; + struct kevent ev; + + if ((io->io.condition & (IO_READ | IO_ERROR)) != 0) { + MY_EV_SET(&ev, io->fd, EVFILT_READ, EV_ADD, 0, 0, io); + if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) + i_panic("kevent(EV_ADD, READ, %d) failed: %m", io->fd); + } + if ((io->io.condition & IO_WRITE) != 0) { + MY_EV_SET(&ev, io->fd, EVFILT_WRITE, EV_ADD, 0, 0, io); + if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) + i_panic("kevent(EV_ADD, WRITE, %d) failed: %m", io->fd); + } + + /* allow kevent() to return the maximum number of events + by keeping space allocated for each handle */ + if (ctx->deleted_count > 0) + ctx->deleted_count--; + else + array_append_zero(&ctx->events); +} + +void io_loop_handle_remove(struct io_file *io, bool closed) +{ + struct ioloop_handler_context *ctx = io->io.ioloop->handler_context; + struct kevent ev; + + i_assert(io->io.condition != 0); + if ((io->io.condition & (IO_READ | IO_ERROR)) != 0 && !closed) { + MY_EV_SET(&ev, io->fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) + i_error("kevent(EV_DELETE, %d) failed: %m", io->fd); + } + if ((io->io.condition & IO_WRITE) != 0 && !closed) { + MY_EV_SET(&ev, io->fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + if (kevent(ctx->kq, &ev, 1, NULL, 0, NULL) < 0) + i_error("kevent(EV_DELETE, %d) failed: %m", io->fd); + } + io->io.condition = 0; + + /* since we're not freeing memory in any case, just increase + deleted counter so next handle_add() can just decrease it + instead of appending to the events array */ + ctx->deleted_count++; + + i_assert(io->refcount > 0); + if (--io->refcount == 0) + i_free(io); +} + +void io_loop_handler_run_internal(struct ioloop *ioloop) +{ + struct ioloop_handler_context *ctx = ioloop->handler_context; + struct kevent *events; + const struct kevent *event; + struct timeval tv; + struct timespec ts; + struct io_file *io; + unsigned int events_count; + int ret, i, msecs; + + /* get the time left for next timeout task */ + msecs = io_loop_run_get_wait_time(ioloop, &tv); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + + /* wait for events */ + events = array_get_modifiable(&ctx->events, &events_count); + + if (events_count > 0) { + ret = kevent (ctx->kq, NULL, 0, events, events_count, &ts); + if (ret < 0 && errno != EINTR) { + i_panic("kevent(events=%u, ts=%ld.%u) failed: %m", + events_count, (long)ts.tv_sec, + (unsigned int)ts.tv_nsec); + } + } else { + i_assert(msecs >= 0); + i_sleep_intr_msecs(msecs); + ret = 0; + } + + /* reference all IOs */ + for (i = 0; i < ret; i++) { + io = (void *)events[i].udata; + i_assert(io->refcount > 0); + io->refcount++; + } + + /* execute timeout handlers */ + io_loop_handle_timeouts(ioloop); + + if (!ioloop->running) + return; + + for (i = 0; i < ret; i++) { + /* io_loop_handle_add() may cause events array reallocation, + so we have use array_idx() */ + event = array_idx(&ctx->events, i); + io = (void *)event->udata; + + /* callback is NULL if io_remove() was already called */ + if (io->io.callback != NULL) { + io_loop_call_io(&io->io); + if (!ioloop->running) + break; + } + + i_assert(io->refcount > 0); + if (--io->refcount == 0) + i_free(io); + } +} + +#endif |