Files
libuev/src/uev.c
T
Joachim Nilsson e35c065d77 uev.c: Stop I/O watcher immediately on EPOLLERR or EPOLLHUP
No need to check fd with fcntl().  On EPOLLERR the descriptor has an
error condition that must bed ACK'ed by the callback.  On EPOLLHUP
we got a hang-up on the remote end, usually a stream or pipe, either
way the callback must ACK this too, regardless.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2017-11-14 12:56:39 +01:00

394 lines
8.0 KiB
C

/* libuEv - Micro event loop library
*
* Copyright (c) 2012 Flemming Madsen <flemming!madsen()madsensoft!dk>
* Copyright (c) 2013-2017 Joachim Nilsson <troglobit()gmail!com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <errno.h>
#include <fcntl.h> /* O_CLOEXEC */
#include <string.h> /* memset() */
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/select.h> /* for select() workaround */
#include <sys/signalfd.h> /* struct signalfd_siginfo */
#include <unistd.h> /* close(), read() */
#include "uev.h"
#define UNUSED(arg) arg __attribute__ ((unused))
static int _init(uev_ctx_t *ctx, int close_old)
{
int fd;
fd = epoll_create1(EPOLL_CLOEXEC);
if (fd < 0)
return -1;
if (close_old)
close(ctx->fd);
ctx->fd = fd;
return 0;
}
/* Used by file i/o workaround when epoll => EPERM */
static int has_data(int fd)
{
struct timeval timeout = { 0, 0 };
fd_set fds;
int n = 0;
FD_ZERO(&fds);
FD_SET(fd, &fds);
if (select(1, &fds, NULL, NULL, &timeout) > 0)
return ioctl(0, FIONREAD, &n) == 0 && n > 0;
return 0;
}
/* Private to libuEv, do not use directly! */
int _uev_watcher_init(uev_ctx_t *ctx, uev_t *w, uev_type_t type, uev_cb_t *cb, void *arg, int fd, int events)
{
if (!ctx || !w) {
errno = EINVAL;
return -1;
}
w->ctx = ctx;
w->type = type;
w->active = 0;
w->fd = fd;
w->cb = cb;
w->arg = arg;
w->events = events;
return 0;
}
/* Private to libuEv, do not use directly! */
int _uev_watcher_start(uev_t *w)
{
struct epoll_event ev;
if (!w || w->fd < 0) {
errno = EINVAL;
return -1;
}
if (_uev_watcher_active(w))
return 0;
ev.events = w->events | EPOLLRDHUP;
ev.data.ptr = w;
if (epoll_ctl(w->ctx->fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) {
if (errno != EPERM)
return -1;
/* Handle special case: `application < file.txt` */
if (w->type != UEV_IO_TYPE || w->events != UEV_READ)
return -1;
/* Only allow this special handling for stdin */
if (w->fd != STDIN_FILENO)
return -1;
w->ctx->workaround = 1;
w->active = -1;
} else {
w->active = 1;
}
/* Add to internal list for bookkeeping */
LIST_INSERT_HEAD(&w->ctx->watchers, w, link);
return 0;
}
/* Private to libuEv, do not use directly! */
int _uev_watcher_stop(uev_t *w)
{
if (!w) {
errno = EINVAL;
return -1;
}
if (!_uev_watcher_active(w))
return 0;
w->active = 0;
/* Remove from internal list */
LIST_REMOVE(w, link);
/* Remove from kernel */
if (epoll_ctl(w->ctx->fd, EPOLL_CTL_DEL, w->fd, NULL) < 0)
return -1;
return 0;
}
/* Private to libuEv, do not use directly! */
int _uev_watcher_active(uev_t *w)
{
if (!w)
return 0;
return w->active > 0;
}
/* Private to libuEv, do not use directly! */
int _uev_watcher_rearm(uev_t *w)
{
struct epoll_event ev;
if (!w || w->fd < 0) {
errno = EINVAL;
return -1;
}
ev.events = w->events | EPOLLRDHUP;
ev.data.ptr = w;
if (epoll_ctl(w->ctx->fd, EPOLL_CTL_MOD, w->fd, &ev) < 0)
return -1;
return 0;
}
/**
* Create an event loop context
* @param ctx Pointer to an uev_ctx_t context to be initialized
*
* @return POSIX OK(0) on success, or non-zero on error.
*/
int uev_init(uev_ctx_t *ctx)
{
if (!ctx) {
errno = EINVAL;
return -1;
}
memset(ctx, 0, sizeof(*ctx));
LIST_INIT(&ctx->watchers);
return _init(ctx, 0);
}
/**
* Terminate the event loop
* @param ctx A valid libuEv context
*
* @return POSIX OK(0) or non-zero with @param errno set on error.
*/
int uev_exit(uev_ctx_t *ctx)
{
if (!ctx) {
errno = EINVAL;
return -1;
}
while (!LIST_EMPTY(&ctx->watchers)) {
uev_t *w = LIST_FIRST(&ctx->watchers);
/* Remove from internal list */
LIST_REMOVE(w, link);
if (!_uev_watcher_active(w))
continue;
switch (w->type) {
case UEV_CRON_TYPE:
case UEV_TIMER_TYPE:
uev_timer_stop(w);
break;
case UEV_SIGNAL_TYPE:
uev_signal_stop(w);
break;
case UEV_IO_TYPE:
uev_io_stop(w);
break;
}
}
ctx->running = 0;
close(ctx->fd);
ctx->fd = -1;
return 0;
}
/**
* Start the event loop
* @param ctx A valid libuEv context
* @param flags A mask of %UEV_ONCE and %UEV_NONBLOCK, or zero
*
* With @flags set to %UEV_ONCE the event loop returns after the first
* event has been served, useful for instance to set a timeout on a file
* descriptor. If @flags also has the %UEV_NONBLOCK flag set the event
* loop will return immediately if no event is pending, useful when run
* inside another event loop.
*
* @return POSIX OK(0) upon successful termination of the event loop, or
* non-zero on error.
*/
int uev_run(uev_ctx_t *ctx, int flags)
{
int timeout = -1;
uev_t *w;
if (!ctx || ctx->fd < 0) {
errno = EINVAL;
return -1;
}
if (flags & UEV_NONBLOCK)
timeout = 0;
/* Start the event loop */
ctx->running = 1;
/* Start all dormant timers */
LIST_FOREACH(w, &ctx->watchers, link) {
if (UEV_CRON_TYPE == w->type)
uev_cron_set(w, w->u.c.when, w->u.c.interval);
if (UEV_TIMER_TYPE == w->type)
uev_timer_set(w, w->u.t.timeout, w->u.t.period);
}
while (ctx->running && !LIST_EMPTY(&ctx->watchers)) {
int i, nfds, rerun = 0;
struct epoll_event ee[UEV_MAX_EVENTS];
/* Handle special case: `application < file.txt` */
if (ctx->workaround) {
LIST_FOREACH(w, &ctx->watchers, link) {
if (w->active != -1 || !w->cb)
continue;
if (!has_data(w->fd)) {
w->active = 0;
LIST_REMOVE(w, link);
}
rerun++;
w->cb(w, w->arg, UEV_READ);
}
}
if (rerun)
continue;
ctx->workaround = 0;
while ((nfds = epoll_wait(ctx->fd, ee, UEV_MAX_EVENTS, timeout)) < 0) {
if (!ctx->running)
break;
if (EINTR == errno)
continue; /* Signalled, try again */
/* Unrecoverable error, cleanup and exit with error. */
uev_exit(ctx);
return -2;
}
for (i = 0; ctx->running && i < nfds; i++) {
uint64_t exp;
uint32_t events;
struct signalfd_siginfo fdsi;
ssize_t sz = sizeof(fdsi);
w = (uev_t *)ee[i].data.ptr;
events = ee[i].events;
switch (w->type) {
case UEV_IO_TYPE:
if (events & (EPOLLHUP | EPOLLERR))
uev_io_stop(w);
break;
case UEV_SIGNAL_TYPE:
if (read(w->fd, &fdsi, sz) != sz) {
if (uev_signal_start(w)) {
uev_signal_stop(w);
events = UEV_ERROR;
}
}
break;
case UEV_TIMER_TYPE:
if (read(w->fd, &exp, sizeof(exp)) != sizeof(exp)) {
uev_timer_stop(w);
events = UEV_ERROR;
}
if (!w->u.t.period)
w->u.t.timeout = 0;
break;
case UEV_CRON_TYPE:
if (read(w->fd, &exp, sizeof(exp)) != sizeof(exp)) {
events = UEV_HUP;
if (errno != ECANCELED) {
uev_cron_stop(w);
events = UEV_ERROR;
}
}
if (!w->u.c.interval)
w->u.c.when = 0;
else
w->u.c.when += w->u.c.interval;
break;
}
if (w->cb)
w->cb(w, w->arg, events & UEV_EVENT_MASK);
if (UEV_CRON_TYPE == w->type) {
if (!w->u.c.when)
uev_timer_stop(w);
}
if (UEV_TIMER_TYPE == w->type) {
if (!w->u.t.timeout)
uev_timer_stop(w);
}
}
if (flags & UEV_ONCE)
break;
}
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/