commit f436aa54ddf3e9824f825a23aeb42cbc147d7fcc from: Omar Polo date: Sat Feb 17 23:08:23 2024 UTC import ev.[ch] from syncparty and amused; locally tweaked to support an arbitrary number of timers. commit - 57ba4f1bd9f8bbbff1e3c9fd7a0c145e39c42152 commit + f436aa54ddf3e9824f825a23aeb42cbc147d7fcc blob - /dev/null blob + 8ead398358280dd3bbcbccb9d677bf8fdad17a96 (mode 644) --- /dev/null +++ ev.c @@ -0,0 +1,415 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * 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 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 "compat.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ev.h" + +struct evcb { + void (*cb)(int, int, void *); + void *udata; +}; + +struct evtimer { + unsigned int id; + struct timeval tv; + struct evcb cb; +}; + +struct evbase { + size_t len; + + struct pollfd *pfds; + size_t pfdlen; + + struct evcb *cbs; + size_t cblen; + + int sigpipe[2]; + struct evcb sigcb; + + unsigned int tid; + struct evtimer *timers; + size_t ntimers; + size_t timerscap; +}; + +static struct evbase *base; +static int ev_stop; + +static int +ev_resize(size_t len) +{ + void *t; + size_t i; + + t = recallocarray(base->pfds, base->pfdlen, len, sizeof(*base->pfds)); + if (t == NULL) + return -1; + base->pfds = t; + base->pfdlen = len; + + for (i = base->len; i < len; ++i) + base->pfds[i].fd = -1; + + t = recallocarray(base->cbs, base->cblen, len, sizeof(*base->cbs)); + if (t == NULL) + return -1; + base->cbs = t; + base->cblen = len; + + base->len = len; + return 0; +} + +int +ev_init(void) +{ + if (base != NULL) { + errno = EINVAL; + return -1; + } + + if ((base = calloc(1, sizeof(*base))) == NULL) + return -1; + + base->sigpipe[0] = -1; + base->sigpipe[1] = -1; + + if (ev_resize(16) == -1) { + free(base->pfds); + free(base->cbs); + free(base); + base = NULL; + return -1; + } + + return 0; +} + +int +ev_add(int fd, int ev, void (*cb)(int, int, void *), void *udata) +{ + if (fd < 0 || (size_t)fd >= base->len) { + if (ev_resize(fd + 1) == -1) + return -1; + } + + base->pfds[fd].fd = fd; + base->pfds[fd].events = ev; + + base->cbs[fd].cb = cb; + base->cbs[fd].udata = udata; + + return 0; +} + +static void +ev_sigcatch(int signo) +{ + unsigned char s; + int err; + + err = errno; + + /* + * We should be able to write up to PIPE_BUF bytes without + * blocking. + */ + s = signo; + (void) write(base->sigpipe[1], &s, sizeof(s)); + + errno = err; +} + +static void +ev_sigdispatch(int fd, int ev, void *data) +{ + unsigned char signo; + + if (read(fd, &signo, sizeof(signo)) != sizeof(signo)) + return; + + base->sigcb.cb(signo, 0, base->sigcb.udata); +} + +int +ev_signal(int sig, void (*cb)(int, int, void *), void *udata) +{ + int flags; + + if (base->sigpipe[0] == -1) { + /* pipe2(2) is not available everywhere... sigh */ + if (pipe(base->sigpipe) == -1) + return -1; + + if ((flags = fcntl(base->sigpipe[1], F_GETFL)) == -1 || + fcntl(base->sigpipe[1], F_SETFL, flags | O_NONBLOCK) == -1) + return -1; + + if (ev_add(base->sigpipe[0], POLLIN, ev_sigdispatch, NULL) + == -1) + return -1; + } + + base->sigcb.cb = cb; + base->sigcb.udata = udata; + + signal(sig, ev_sigcatch); + return 0; +} + +static inline void +bubbleup(size_t i) +{ + struct evtimer tmp; + size_t p; + + for (;;) { + if (i == 0) + return; + + p = (i - 1) / 2; + if (timercmp(&base->timers[p].tv, &base->timers[i].tv, <)) + return; + + /* swap */ + memcpy(&tmp, &base->timers[p], sizeof(tmp)); + memcpy(&base->timers[p], &base->timers[i], sizeof(tmp)); + memcpy(&base->timers[i], &tmp, sizeof(tmp)); + i = p; + } +} + +unsigned int +ev_timer(const struct timeval *tv, void (*cb)(int, int, void*), void *udata) +{ + struct evtimer *evt; + void *t; + size_t newcap; + unsigned int nextid; + + if (tv == NULL) { + errno = EINVAL; + return 0; + } + + if (base->ntimers == base->timerscap) { + newcap = base->timerscap + 8; + t = recallocarray(base->timers, base->timerscap, newcap, + sizeof(*base->timers)); + if (t == NULL) + return 0; + base->timers = t; + base->timerscap = newcap; + } + + if ((nextid = ++base->tid) == 0) + nextid = ++base->tid; + + evt = &base->timers[base->ntimers]; + evt->id = nextid; + memcpy(&evt->tv, tv, sizeof(*tv)); + evt->cb.cb = cb; + evt->cb.udata = udata; + + bubbleup(base->ntimers); + base->ntimers++; + + return (nextid); +} + +int +ev_timer_pending(unsigned int id) +{ + size_t i; + + for (i = 0; i < base->ntimers; ++i) { + if (base->timers[i].id == id) + return (1); + } + + return (0); +} + +static void +bubbledown(size_t i) +{ + struct timeval tmp; + size_t l, r, s; + + for (;;) { + l = 2 * i + 1; + r = 2 * i + 2; + + /* base case: there are no children */ + if (l > base->ntimers) + return; + + /* find the smaller child */ + s = r; + if (r > base->ntimers || + timercmp(&base->timers[l].tv, &base->timers[r].tv, <)) + s = l; + + /* other base case: it's at the right place */ + if (timercmp(&base->timers[i].tv, &base->timers[s].tv, <)) + return; + + /* swap */ + memcpy(&tmp, &base->timers[s], sizeof(tmp)); + memcpy(&base->timers[s], &base->timers[i], sizeof(tmp)); + memcpy(&base->timers[i], &tmp, sizeof(tmp)); + + i = s; + } +} + +static inline void +cancel_timer(size_t i) +{ + /* special case: it's the last one */ + if (i == base->ntimers - 1) { + base->ntimers--; + memset(&base->timers[base->ntimers], 0, sizeof(*base->timers)); + return; + } + + memcpy(&base->timers[i], &base->timers[base->ntimers - 1], + sizeof(*base->timers)); + base->ntimers--; + + bubbledown(i); +} + +int +ev_timer_cancel(unsigned int id) +{ + size_t i; + + for (i = 0; i < base->ntimers; ++i) { + if (base->timers[i].id == id) + break; + } + + if (i == base->ntimers) + return -1; + + cancel_timer(i); + return (0); +} + +int +ev_del(int fd) +{ + if (fd < 0 || (size_t)fd >= base->len) { + errno = ERANGE; + return -1; + } + + base->pfds[fd].fd = -1; + base->pfds[fd].events = 0; + + base->cbs[fd].cb = NULL; + base->cbs[fd].udata = NULL; + + return 0; +} + +int +ev_loop(void) +{ + struct timespec elapsed, beg, end, min, *wait; + struct timeval tv, sub; + int n; + size_t i; + + while (!ev_stop) { + wait = NULL; + if (base->ntimers) { + TIMEVAL_TO_TIMESPEC(&base->timers[0].tv, &min); + wait = &min; + } + + clock_gettime(CLOCK_MONOTONIC, &beg); + if ((n = ppoll(base->pfds, base->len, wait, NULL)) == -1) { + if (errno != EINTR) + return -1; + } + + if (n == 0) + memcpy(&elapsed, &min, sizeof(min)); + else { + clock_gettime(CLOCK_MONOTONIC, &end); + timespecsub(&end, &beg, &elapsed); + } + + TIMESPEC_TO_TIMEVAL(&tv, &elapsed); + + for (i = 0; i < base->ntimers && !ev_stop; /* nop */) { + timersub(&base->timers[i].tv, &tv, &sub); + if (sub.tv_sec <= 0) { + base->timers[i].cb.cb(-1, POLLHUP, + base->timers[i].cb.udata); + cancel_timer(i); + continue; + } + + memcpy(&base->timers[i].tv, &sub, sizeof(sub)); + i++; + } + + for (i = 0; i < base->len && n > 0 && !ev_stop; ++i) { + if (base->pfds[i].fd == -1) + continue; + if (base->pfds[i].revents & (POLLIN|POLLOUT|POLLHUP)) { + n--; + base->cbs[i].cb(base->pfds[i].fd, + base->pfds[i].revents, + base->cbs[i].udata); + } + } + } + + return 0; +} + +void +ev_break(void) +{ + ev_stop = 1; +} blob - /dev/null blob + 9697eeb7c227c45cc70b4228a7d91eecc015aa91 (mode 644) --- /dev/null +++ ev.h @@ -0,0 +1,37 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * 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 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. + */ + +struct timeval; + +int ev_init(void); +int ev_add(int, int, void(*)(int, int, void *), void *); +int ev_signal(int, void(*)(int, int, void * ), void *); +unsigned int ev_timer(const struct timeval *, void(*)(int, int, void *), + void *); +int ev_timer_pending(unsigned int); +int ev_timer_cancel(unsigned int); +int ev_del(int); +int ev_loop(void); +void ev_break(void);