/* * Event Loop * * Copyright (c) 2011-2013 David Herrmann * Copyright (c) 2011 University of Tuebingen * * 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. */ /** * SECTION:eloop * @short_description: Event loop * @include: eloop.h * * The event loop allows to register event sources and poll them for events. * When an event occurs, the user-supplied callback is called. * * The event-loop allows the callbacks to modify _any_ data they want. They can * remove themselves or other sources from the event loop even in a callback. * This, however, means that recursive dispatch calls are not supported to * increase performance and avoid internal dispatch-stacks. * * Sources can be one of: * - File descriptors: An fd that is watched for readable/writeable events * - Timers: An event that occurs after a relative timeout * - Counters: An event that occurs when the counter is non-zero * - Signals: An event that occurs when a signal is caught * - Idle: An event that occurs when nothing else is done * - Eloop: An event loop itself can be a source of another event loop * * A source can be registered for a single event-loop only! You cannot add it * to multiple event loops simultaneously. Also all provided sources are based * on the file-descriptor source so it is guaranteed that you can get a * file-descriptor for every source-type. This is not exported via the public * API, but you can get the epoll-fd which is basically a selectable FD summary * of all event sources. * * For instance, if you're developing a library, you can use the eloop library * internally and you will have a full event-loop implementation inside of a * library without any side-effects. You simply export the epoll-fd of the * eloop-object via your public API and the outside users think you only use a * single file-descriptor. They include this FD in their own application event * loop which will then dispatch the messages to your library. Internally, you * simply forward this dispatching to ev_eloop_dispatch() which then calls all * your internal callbacks. * That is, you have an event loop inside your library without requiring the * outside-user to use the same event loop. You also have no global state or * thread-bound event-loops like the Qt/Gtk event loops. So you have full * access to the whole event loop without any side-effects. * * * The whole eloop library does not use any global data. Therefore, it is fully * re-entrant and no synchronization needed. However, a single object is not * thread-safe. This means, if you access a single eloop object or registered * sources on this eloop object in two different threads, you need to * synchronize them. Furthermore, all callbacks are called from the thread that * calls ev_eloop_dispatch() or ev_eloop_run(). * This guarantees that you have full control over the eloop but that you also * have to implement additional functionality like thread-affinity yourself * (obviously, only if you need it). * * The philosophy behind this library is that a proper application needs only a * single thread that uses an event loop. Multiple threads should be used to do * calculations, but not to avoid learning how to do non-blocking I/O! * Therefore, only the application threads needs an event-loop, all other * threads only perform calculation and return the data to the main thread. * However, the library does not enforce this design-choice. On the contrary, * it supports all other types of application-designs, too. But as it is * optimized for performance, other application-designs may need to add further * functionality (like thread-affinity) by themselves as it would slow down the * event loop if it was natively implemented. * * * To get started simply create an eloop object with ev_eloop_new(). All * functions return 0 on success and a negative error code like -EFAULT on * failure. -EINVAL is returned if invalid parameters were passed. * Every object can be ref-counted. *_ref() increases the reference-count and * *_unref() decreases it. *_unref() also destroys the object if the ref-count * drops to 0. * To create new objects you call *_new(). It stores a pointer to the new * object in the location you passed as parameter. Nearly all structues are * opaque, that is, you cannot access member fields directly. This guarantees * ABI stability. * * You can create sources with ev_fd_new(), ev_timer_new(), ... and you can add * them to you eloop with ev_eloop_add_fd() or ev_eloop_add_timer(), ... * After they are added you can call ev_eloop_run() to run this eloop for the * given time. If you pass -1 as timeout, it runs until some callback calls * ev_eloop_exit() on this eloop. * You can perform _any_ operations on an eloop object inside of callbacks. You * can add new sources, remove sources, destroy sources, modify sources. You * also do all this on the currently active source. * * All objects are enabled by default. You can disable them with *_disable() * and re-enable them with *_enable(). Only when enabled, they are added to the * dispatcher and callbacks are called. * * Two sources are different for performance reasons: * Idle sources: Idle sources can be registered with * ev_eloop_register_idle_cb() and unregistered with * ev_eloop_unregister_idle_cb(). They internally share a single * file-descriptor to make them faster so you cannot get the same access as * to other event sources (you cannot enable/disable them or similar). * Idle sources are called every-time ev_eloop_dispatch() is called. That is, * as long as an idle-source is registered, the event-loop will not go to * sleep! * * Signal sources: Talking about the API they are very similar to * idle-sources. They same restrictions apply, however, their type is very * different. A signal-callback is called when the specified signal is * received. They are not called in signal-context! But rather called in the * same context as every other source. They are implemented with * linux-signalfd. * You can register multiple callbacks for the same signal and all callbacks * will be called (compared to plain signalfd where only one fd gets the * signal). This is done internally by sharing the signalfd. * However, there is one restriction: You cannot share a signalfd between * multiple eloop-instances. That is, if you register a callback for the same * signal on two different eloop-instances (which are connected themselves), * then only one eloop-instance will fire the signal source. This is a * restriction of signalfd that cannot be overcome. However, it is very * uncommon to register multiple callbacks for a signal so this shouldn't * affect common application use-cases. * Also note that if you register a callback for SIGCHLD then the eloop- * object will automatically reap all pending zombies _after_ your callback * has been called. So if you need to check for them, then check for all of * them in the callback. After you return, they will be gone. * When adding a signal handler the signal is automatically added to the * currently blocked signals. It is not removed when dropping the * signal-source, though. * * Eloop uses several system calls which may fail. All errors (including memory * allocation errors via -ENOMEM) are forwarded to the caller, however, it is * often preferable to have a more detailed logging message. Therefore, eloop * takes a loggin-function as argument for each object. Pass NULL if you are * not interested in logging. This will disable logging entirely. * Otherwise, pass in a callback from your application. This callback will be * called when a message is to be logged. The function may be called under any * circumstances (out-of-memory, etc...) and should always behave well. * Nothing is ever logged except through this callback. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "eloop.h" #include "shl_dlist.h" #include "shl_hook.h" #include "shl_llog.h" #include "shl_misc.h" #define LLOG_SUBSYSTEM "eloop" /** * ev_eloop: * @ref: refcnt of this object * @llog: llog log function * @llog_data: llog log function user-data * @efd: The epoll file descriptor. * @fd: Event source around \efd so you can nest event loops * @cnt: Counter source used for idle events * @sig_list: Shared signal sources * @idlers: List of idle sources * @cur_fds: Current dispatch array of fds * @cur_fds_cnt: current length of \cur_fds * @cur_fds_size: absolute size of \cur_fds * @exit: true if we should exit the main loop * * An event loop is an object where you can register event sources. If you then * sleep on the event loop, you will be woken up if a single event source is * firing up. An event loop itself is an event source so you can nest them. */ struct ev_eloop { unsigned long ref; llog_submit_t llog; void *llog_data; int efd; struct ev_fd *fd; int idle_fd; struct shl_dlist sig_list; struct shl_hook *chlds; struct shl_hook *idlers; struct shl_hook *pres; struct shl_hook *posts; bool dispatching; struct epoll_event *cur_fds; size_t cur_fds_cnt; size_t cur_fds_size; bool exit; }; /** * ev_fd: * @ref: refcnt for object * @llog: llog log function * @llog_data: llog log function user-data * @fd: the actual file descriptor * @mask: the event mask for this fd (EV_READABLE, EV_WRITEABLE, ...) * @cb: the user callback * @data: the user data * @enabled: true if the object is currently enabled * @loop: NULL or pointer to eloop if bound * * File descriptors are the most basic event source. Internally, they are used * to implement all other kinds of event sources. */ struct ev_fd { unsigned long ref; llog_submit_t llog; void *llog_data; int fd; int mask; ev_fd_cb cb; void *data; bool enabled; struct ev_eloop *loop; }; /** * ev_timer: * @ref: refcnt of this object * @llog: llog log function * @llog_data: llog log function user-data * @cb: user callback * @data: user data * @fd: the timerfd file descriptor * @efd: fd-source for @fd * * Based on timerfd this allows firing events based on relative timeouts. */ struct ev_timer { unsigned long ref; llog_submit_t llog; void *llog_data; ev_timer_cb cb; void *data; int fd; struct ev_fd *efd; }; /** * ev_counter: * @ref: refcnt of counter object * @llog: llog log function * @llog_data: llog log function user-data * @cb: user callback * @data: user data * @fd: eventfd file descriptor * @efd: fd-source for @fd * * Counter sources fire if they are non-zero. They are based on the eventfd * syscall in linux. */ struct ev_counter { unsigned long ref; llog_submit_t llog; void *llog_data; ev_counter_cb cb; void *data; int fd; struct ev_fd *efd; }; /** * ev_signal_shared: * @list: list integration into ev_eloop object * @fd: the signalfd file descriptor for this signal * @signum: the actual signal number * @hook: list of registered user callbacks for this signal * * A shared signal allows multiple listeners for the same signal. All listeners * are called if the signal is caught. */ struct ev_signal_shared { struct shl_dlist list; struct ev_fd *fd; int signum; struct shl_hook *hook; }; /* * Shared signals * signalfd allows us to conveniently listen for incoming signals. However, if * multiple signalfds are registered for the same signal, then only one of them * will get signaled. To avoid this restriction, we provide shared signals. * That means, the user can register for a signal and if no other user is * registered for this signal, yet, we create a new shared signal. Otherwise, * we add the user to the existing shared signals. * If the signal is caught, we simply call all users that are registered for * this signal. * To avoid side-effects, we automatically block all signals for the current * thread when a signalfd is created. We never unblock the signal. However, * most modern linux user-space programs avoid signal handlers, anyway, so you * can use signalfd only. */ static void sig_child(struct ev_eloop *loop, struct signalfd_siginfo *info, void *data) { pid_t pid; int status; struct ev_child_data d; while (1) { pid = waitpid(-1, &status, WNOHANG); if (pid == -1) { if (errno != ECHILD) llog_warn(loop, "cannot wait on child: %m"); break; } else if (pid == 0) { break; } else if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) llog_debug(loop, "child %d exited with status %d", pid, WEXITSTATUS(status)); else llog_debug(loop, "child %d exited successfully", pid); } else if (WIFSIGNALED(status)) { llog_debug(loop, "child %d exited by signal %d", pid, WTERMSIG(status)); } d.pid = pid; d.status = status; shl_hook_call(loop->chlds, loop, &d); } } static void shared_signal_cb(struct ev_fd *fd, int mask, void *data) { struct ev_signal_shared *sig = data; struct signalfd_siginfo info; int len; if (mask & EV_READABLE) { len = read(fd->fd, &info, sizeof(info)); if (len != sizeof(info)) llog_warn(fd, "cannot read signalfd (%d): %m", errno); else shl_hook_call(sig->hook, sig->fd->loop, &info); } else if (mask & (EV_HUP | EV_ERR)) { llog_warn(fd, "HUP/ERR on signal source"); } } /** * signal_new: * @out: Shared signal storage where the new object is stored * @loop: The event loop where this shared signal is registered * @signum: Signal number that this shared signal is for * * This creates a new shared signal and links it into the list of shared * signals in @loop. It automatically adds @signum to the signal mask of the * current thread so the signal is blocked. * * Returns: 0 on success, otherwise negative error code */ static int signal_new(struct ev_signal_shared **out, struct ev_eloop *loop, int signum) { sigset_t mask; int ret, fd; struct ev_signal_shared *sig; if (signum < 0) return llog_EINVAL(loop); sig = malloc(sizeof(*sig)); if (!sig) return llog_ENOMEM(loop); memset(sig, 0, sizeof(*sig)); sig->signum = signum; ret = shl_hook_new(&sig->hook); if (ret) goto err_free; sigemptyset(&mask); sigaddset(&mask, signum); fd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); if (fd < 0) { ret = -errno; llog_error(loop, "cannot created signalfd"); goto err_hook; } ret = ev_eloop_new_fd(loop, &sig->fd, fd, EV_READABLE, shared_signal_cb, sig); if (ret) goto err_sig; pthread_sigmask(SIG_BLOCK, &mask, NULL); shl_dlist_link(&loop->sig_list, &sig->list); *out = sig; return 0; err_sig: close(fd); err_hook: shl_hook_free(sig->hook); err_free: free(sig); return ret; } /** * signal_free: * @sig: The shared signal to be freed * * This unlinks the given shared signal from the event-loop where it was * registered and destroys it. This does _not_ unblock the signal number that it * was associated to. If you want this, you need to do this manually with * pthread_sigmask(). */ static void signal_free(struct ev_signal_shared *sig) { int fd; if (!sig) return; shl_dlist_unlink(&sig->list); fd = sig->fd->fd; ev_eloop_rm_fd(sig->fd); close(fd); shl_hook_free(sig->hook); free(sig); /* * We do not unblock the signal here as there may be other subsystems * which blocked this signal so we do not want to interfere. If you * need a clean sigmask then do it yourself. */ } /* * Eloop mainloop * The main eloop object is responsible for correctly dispatching all events. * You can register fd, idle or signal sources with it. All other kinds of * sources are based on these. In fact, event idle and signal sources are based * on fd sources. * As special feature, you can retrieve an fd of an eloop object, too, and pass * it to your own event loop. If this fd is readable, then call * ev_eloop_dispatch() to make this loop dispatch all pending events. * * There is one restriction when nesting eloops, though. You cannot share * signals across eloop boundaries. That is, if you have registered for shared * signals in two eloops for the _same_ signal, then only one eloop will * receive the signal (and this is pretty random). * However, such a setup is most often broken in design and hence should never * occur. Even shared signals are quite rare. * Anyway, you must take this into account when nesting eloops. * * For the curious reader: We implement idle sources with counter sources. That * is, whenever there is an idle source we increase the counter source. Hence, * the next dispatch call will call the counter source and this will call all * registered idle source. If the idle sources do not unregister them, then we * directly increase the counter again and the next dispatch round will call * all idle sources again. This, however, has the side-effect that idle sources * are _not_ called before other fd events but are rather mixed in between. */ static void eloop_event(struct ev_fd *fd, int mask, void *data) { struct ev_eloop *eloop = data; if (mask & EV_READABLE) ev_eloop_dispatch(eloop, 0); if (mask & (EV_HUP | EV_ERR)) llog_warn(eloop, "HUP/ERR on eloop source"); } static int write_eventfd(llog_submit_t llog, void *llog_data, int fd, uint64_t val) { int ret; if (!val) return llog_dEINVAL(llog, llog_data); if (val == 0xffffffffffffffffULL) { llog_dwarning(llog, llog_data, "increasing counter with invalid value %" PRIu64, val); return -EINVAL;; } ret = write(fd, &val, sizeof(val)); if (ret < 0) { if (errno == EAGAIN) llog_dwarning(llog, llog_data, "eventfd overflow while writing %" PRIu64, val); else llog_dwarning(llog, llog_data, "eventfd write error (%d): %m", errno); return -EFAULT; } else if (ret != sizeof(val)) { llog_dwarning(llog, llog_data, "wrote %d bytes instead of 8 to eventdfd", ret); return -EFAULT; } return 0; } static void eloop_idle_event(struct ev_eloop *loop, unsigned int mask) { int ret; uint64_t val; if (mask & (EV_HUP | EV_ERR)) { llog_warning(loop, "HUP/ERR on eventfd"); goto err_out; } if (!(mask & EV_READABLE)) return; ret = read(loop->idle_fd, &val, sizeof(val)); if (ret < 0) { if (errno != EAGAIN) { llog_warning(loop, "reading eventfd failed (%d): %m", errno); goto err_out; } } else if (ret == 0) { llog_warning(loop, "EOF on eventfd"); goto err_out; } else if (ret != sizeof(val)) { llog_warning(loop, "read %d bytes instead of 8 on eventfd", ret); goto err_out; } else if (val > 0) { shl_hook_call(loop->idlers, loop, NULL); if (shl_hook_num(loop->idlers) > 0) write_eventfd(loop->llog, loop->llog_data, loop->idle_fd, 1); } return; err_out: ret = epoll_ctl(loop->efd, EPOLL_CTL_DEL, loop->idle_fd, NULL); if (ret) llog_warning(loop, "cannot remove fd %d from epollset (%d): %m", loop->idle_fd, errno); } /** * ev_eloop_new: * @out: Storage for the result * @log: logging function or NULL * @log_data: logging function user-data * * This creates a new event-loop with ref-count 1. The new event loop is stored * in @out and has no registered events. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_eloop_new(struct ev_eloop **out, ev_log_t log, void *log_data) { struct ev_eloop *loop; int ret; struct epoll_event ep; if (!out) return llog_dEINVAL(log, log_data); loop = malloc(sizeof(*loop)); if (!loop) return llog_dENOMEM(log, log_data); memset(loop, 0, sizeof(*loop)); loop->ref = 1; loop->llog = log; loop->llog_data = log_data; shl_dlist_init(&loop->sig_list); loop->cur_fds_size = 32; loop->cur_fds = malloc(sizeof(struct epoll_event) * loop->cur_fds_size); if (!loop->cur_fds) { ret = llog_ENOMEM(loop); goto err_free; } ret = shl_hook_new(&loop->chlds); if (ret) goto err_fds; ret = shl_hook_new(&loop->idlers); if (ret) goto err_childs; ret = shl_hook_new(&loop->pres); if (ret) goto err_idlers; ret = shl_hook_new(&loop->posts); if (ret) goto err_pres; loop->efd = epoll_create1(EPOLL_CLOEXEC); if (loop->efd < 0) { ret = -errno; llog_error(loop, "cannot create epoll-fd"); goto err_posts; } ret = ev_fd_new(&loop->fd, loop->efd, EV_READABLE, eloop_event, loop, loop->llog, loop->llog_data); if (ret) goto err_close; loop->idle_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (loop->idle_fd < 0) { llog_error(loop, "cannot create eventfd (%d): %m", errno); ret = -EFAULT; goto err_fd; } memset(&ep, 0, sizeof(ep)); ep.events |= EPOLLIN; ep.data.ptr = loop; ret = epoll_ctl(loop->efd, EPOLL_CTL_ADD, loop->idle_fd, &ep); if (ret) { llog_warning(loop, "cannot add fd %d to epoll set (%d): %m", loop->idle_fd, errno); ret = -EFAULT; goto err_idle_fd; } llog_debug(loop, "new eloop object %p", loop); *out = loop; return 0; err_idle_fd: close(loop->idle_fd); err_fd: ev_fd_unref(loop->fd); err_close: close(loop->efd); err_posts: shl_hook_free(loop->posts); err_pres: shl_hook_free(loop->pres); err_idlers: shl_hook_free(loop->idlers); err_childs: shl_hook_free(loop->chlds); err_fds: free(loop->cur_fds); err_free: free(loop); return ret; } /** * ev_eloop_ref: * @loop: Event loop to be modified or NULL * * This increases the ref-count of @loop by 1. */ SHL_EXPORT void ev_eloop_ref(struct ev_eloop *loop) { if (!loop) return; ++loop->ref; } /** * ev_eloop_unref: * @loop: Event loop to be modified or NULL * * This decreases the ref-count of @loop by 1. If it drops to zero, the event * loop is destroyed. Note that every registered event source takes a ref-count * of the event loop so this ref-count will never drop to zero while there is an * registered event source. */ SHL_EXPORT void ev_eloop_unref(struct ev_eloop *loop) { struct ev_signal_shared *sig; int ret; if (!loop) return; if (!loop->ref) return llog_vEINVAL(loop); if (--loop->ref) return; llog_debug(loop, "free eloop object %p", loop); if (shl_hook_num(loop->chlds)) ev_eloop_unregister_signal_cb(loop, SIGCHLD, sig_child, loop); while (loop->sig_list.next != &loop->sig_list) { sig = shl_dlist_entry(loop->sig_list.next, struct ev_signal_shared, list); signal_free(sig); } ret = epoll_ctl(loop->efd, EPOLL_CTL_DEL, loop->idle_fd, NULL); if (ret) llog_warning(loop, "cannot remove fd %d from epollset (%d): %m", loop->idle_fd, errno); close(loop->idle_fd); ev_fd_unref(loop->fd); close(loop->efd); shl_hook_free(loop->posts); shl_hook_free(loop->pres); shl_hook_free(loop->idlers); shl_hook_free(loop->chlds); free(loop->cur_fds); free(loop); } /** * ev_eloop_flush_fd: * @loop: The event loop where @fd is registered * @fd: The fd to be flushed * * If @loop is currently dispatching events, this will remove all pending events * of @fd from the current event-list. */ SHL_EXPORT void ev_eloop_flush_fd(struct ev_eloop *loop, struct ev_fd *fd) { int i; if (!loop) return; if (!fd) return llog_vEINVAL(loop); if (loop->dispatching) { for (i = 0; i < loop->cur_fds_cnt; ++i) { if (loop->cur_fds[i].data.ptr == fd) loop->cur_fds[i].data.ptr = NULL; } } } static unsigned int convert_mask(uint32_t mask) { unsigned int res = 0; if (mask & EPOLLIN) res |= EV_READABLE; if (mask & EPOLLOUT) res |= EV_WRITEABLE; if (mask & EPOLLERR) res |= EV_ERR; if (mask & EPOLLHUP) res |= EV_HUP; return res; } /** * ev_eloop_dispatch: * @loop: Event loop to be dispatched * @timeout: Timeout in milliseconds * * This listens on @loop for incoming events and handles all events that * occurred. This waits at most @timeout milliseconds until returning. If * @timeout is -1, this waits until the first event arrives. If @timeout is 0, * then this returns directly if no event is currently pending. * * This performs only a single dispatch round. That is, if all sources where * checked for events and there are no more pending events, this will return. If * it handled events and the timeout has not elapsed, this will still return. * * If ev_eloop_exit() was called on @loop, then this will return immediately. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_eloop_dispatch(struct ev_eloop *loop, int timeout) { struct epoll_event *ep; struct ev_fd *fd; int i, count, mask, ret; if (!loop) return -EINVAL; if (loop->exit) return llog_EINVAL(loop); if (loop->dispatching) { llog_warn(loop, "recursive dispatching not allowed"); return -EOPNOTSUPP; } loop->dispatching = true; shl_hook_call(loop->pres, loop, NULL); count = epoll_wait(loop->efd, loop->cur_fds, loop->cur_fds_size, timeout); if (count < 0) { if (errno == EINTR) { ret = 0; goto out_dispatch; } else { llog_warn(loop, "epoll_wait dispatching failed: %m"); ret = -errno; goto out_dispatch; } } else if (count > loop->cur_fds_size) { count = loop->cur_fds_size; } ep = loop->cur_fds; loop->cur_fds_cnt = count; for (i = 0; i < count; ++i) { if (ep[i].data.ptr == loop) { mask = convert_mask(ep[i].events); eloop_idle_event(loop, mask); } else { fd = ep[i].data.ptr; if (!fd || !fd->cb || !fd->enabled) continue; mask = convert_mask(ep[i].events); fd->cb(fd, mask, fd->data); } } if (count == loop->cur_fds_size) { ep = realloc(loop->cur_fds, sizeof(struct epoll_event) * loop->cur_fds_size * 2); if (!ep) { llog_warning(loop, "cannot reallocate dispatch cache to size %zu", loop->cur_fds_size * 2); } else { loop->cur_fds = ep; loop->cur_fds_size *= 2; } } ret = 0; out_dispatch: shl_hook_call(loop->posts, loop, NULL); loop->dispatching = false; return ret; } /** * ev_eloop_run: * @loop: The event loop to be run * @timeout: Timeout for this operation * * This is similar to ev_eloop_dispatch() but runs _exactly_ for @timeout * milliseconds. It calls ev_eloop_dispatch() as often as it can until the * timeout has elapsed. If @timeout is -1 this will run until you call * ev_eloop_exit(). If @timeout is 0 this is equal to calling * ev_eloop_dispatch() with a timeout of 0. * * Calling ev_eloop_exit() will always interrupt this function and make it * return. * * Returns: 0 on success, otherwise a negative error code */ SHL_EXPORT int ev_eloop_run(struct ev_eloop *loop, int timeout) { int ret; struct timeval tv, start; int64_t off, msec; if (!loop) return -EINVAL; loop->exit = false; llog_debug(loop, "run for %d msecs", timeout); gettimeofday(&start, NULL); while (!loop->exit) { ret = ev_eloop_dispatch(loop, timeout); if (ret) return ret; if (!timeout) { break; } else if (timeout > 0) { gettimeofday(&tv, NULL); off = tv.tv_sec - start.tv_sec; msec = (int64_t)tv.tv_usec - (int64_t)start.tv_usec; if (msec < 0) { off -= 1; msec = 1000000 + msec; } off *= 1000; off += msec / 1000; if (off >= timeout) break; } } return 0; } /** * ev_eloop_exit: * @loop: Event loop that should exit * * This makes a call to ev_eloop_run() stop. */ SHL_EXPORT void ev_eloop_exit(struct ev_eloop *loop) { if (!loop) return; llog_debug(loop, "exiting %p", loop); loop->exit = true; if (loop->fd->loop) ev_eloop_exit(loop->fd->loop); } /** * ev_eloop_get_fd: * @loop: Event loop * * Returns a single file descriptor for the whole event-loop. If that FD is * readable, then one of the event-sources is active and you should call * ev_eloop_dispatch(loop, 0); to dispatch these events. * If the fd is not readable, then ev_eloop_dispatch() would sleep as there are * no active events. * * Returns: A file descriptor for the event loop or negative error code */ SHL_EXPORT int ev_eloop_get_fd(struct ev_eloop *loop) { if (!loop) return -EINVAL; return loop->efd; } /** * ev_eloop_new_eloop: * @loop: The parent event-loop where the new event loop is registered * @out: Storage for new event loop * * This creates a new event loop and directly registers it as event source on * the parent event loop \loop. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_eloop_new_eloop(struct ev_eloop *loop, struct ev_eloop **out) { struct ev_eloop *el; int ret; if (!loop) return -EINVAL; if (!out) return llog_EINVAL(loop); ret = ev_eloop_new(&el, loop->llog, loop->llog_data); if (ret) return ret; ret = ev_eloop_add_eloop(loop, el); if (ret) { ev_eloop_unref(el); return ret; } ev_eloop_unref(el); *out = el; return 0; } /** * ev_eloop_add_eloop: * @loop: Parent event loop * @add: The event loop that is registered as event source on @loop * * This registers the existing event loop @add as event source on the parent * event loop @loop. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_eloop_add_eloop(struct ev_eloop *loop, struct ev_eloop *add) { int ret; if (!loop) return -EINVAL; if (!add) return llog_EINVAL(loop); if (add->fd->loop) return -EALREADY; /* This adds the epoll-fd into the parent epoll-set. This works * perfectly well with registered FDs, timers, etc. However, we use * shared signals in this event-loop so if the parent and child have * overlapping shared-signals, then the signal will be randomly * delivered to either the parent-hook or child-hook but never both. * TODO: * We may fix this by linking the childs-sig_list into the parent's * siglist but we didn't need this, yet, so ignore it here. */ ret = ev_eloop_add_fd(loop, add->fd); if (ret) return ret; ev_eloop_ref(add); return 0; } /** * ev_eloop_rm_eloop: * @rm: Event loop to be unregistered from its parent * * This unregisters the event loop @rm as event source from its parent. If this * event loop was not registered on any other event loop, then this call does * nothing. */ SHL_EXPORT void ev_eloop_rm_eloop(struct ev_eloop *rm) { if (!rm || !rm->fd->loop) return; ev_eloop_rm_fd(rm->fd); ev_eloop_unref(rm); } /* * FD sources * This allows adding file descriptors to an eloop. A file descriptor is the * most basic kind of source and used for all other source types. * By default a source is always enabled but you can easily disable the source * by calling ev_fd_disable(). This will have the effect, that the source is * still registered with the eloop but will not wake up the thread or get * called until you enable it again. */ /** * ev_fd_new: * @out: Storage for result * @rfd: The actual file descriptor * @mask: Bitmask of %EV_READABLE and %EV_WRITEABLE flags * @cb: User callback * @data: User data * @log: llog function or NULL * @log_data: logging function user-data * * This creates a new file descriptor source that is watched for the events set * in @mask. @rfd is the system filedescriptor. The resulting object is stored * in @out. @cb and @data are the user callback and the user-supplied data that * is passed to the callback on events. * The FD is automatically watched for EV_HUP and EV_ERR events, too. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_fd_new(struct ev_fd **out, int rfd, int mask, ev_fd_cb cb, void *data, ev_log_t log, void *log_data) { struct ev_fd *fd; if (!out || rfd < 0) return llog_dEINVAL(log, log_data); fd = malloc(sizeof(*fd)); if (!fd) return llog_dEINVAL(log, log_data); memset(fd, 0, sizeof(*fd)); fd->ref = 1; fd->llog = log; fd->llog_data = log_data; fd->fd = rfd; fd->mask = mask; fd->cb = cb; fd->data = data; fd->enabled = true; *out = fd; return 0; } /** * ev_fd_ref: * @fd: FD object * * Increases the ref-count of @fd by 1. */ SHL_EXPORT void ev_fd_ref(struct ev_fd *fd) { if (!fd) return; if (!fd->ref) return llog_vEINVAL(fd); ++fd->ref; } /** * ev_fd_unref: * @fd: FD object * * Decreases the ref-count of @fd by 1. Destroys the object if the ref-count * drops to zero. */ SHL_EXPORT void ev_fd_unref(struct ev_fd *fd) { if (!fd) return; if (!fd->ref) return llog_vEINVAL(fd); if (--fd->ref) return; free(fd); } static int fd_epoll_add(struct ev_fd *fd) { struct epoll_event ep; int ret; if (!fd->loop) return 0; memset(&ep, 0, sizeof(ep)); if (fd->mask & EV_READABLE) ep.events |= EPOLLIN; if (fd->mask & EV_WRITEABLE) ep.events |= EPOLLOUT; if (fd->mask & EV_ET) ep.events |= EPOLLET; ep.data.ptr = fd; ret = epoll_ctl(fd->loop->efd, EPOLL_CTL_ADD, fd->fd, &ep); if (ret) { llog_warning(fd, "cannot add fd %d to epoll set (%d): %m", fd->fd, errno); return -EFAULT; } return 0; } static void fd_epoll_remove(struct ev_fd *fd) { int ret; if (!fd->loop) return; ret = epoll_ctl(fd->loop->efd, EPOLL_CTL_DEL, fd->fd, NULL); if (ret && errno != EBADF) llog_warning(fd, "cannot remove fd %d from epoll set (%d): %m", fd->fd, errno); } static int fd_epoll_update(struct ev_fd *fd) { struct epoll_event ep; int ret; if (!fd->loop) return 0; memset(&ep, 0, sizeof(ep)); if (fd->mask & EV_READABLE) ep.events |= EPOLLIN; if (fd->mask & EV_WRITEABLE) ep.events |= EPOLLOUT; if (fd->mask & EV_ET) ep.events |= EPOLLET; ep.data.ptr = fd; ret = epoll_ctl(fd->loop->efd, EPOLL_CTL_MOD, fd->fd, &ep); if (ret) { llog_warning(fd, "cannot update epoll fd %d (%d): %m", fd->fd, errno); return -EFAULT; } return 0; } /** * ev_fd_enable: * @fd: FD object * * This enables @fd. By default every fd object is enabled. If you disabled it * you can re-enable it with this call. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_fd_enable(struct ev_fd *fd) { int ret; if (!fd) return -EINVAL; if (fd->enabled) return 0; ret = fd_epoll_add(fd); if (ret) return ret; fd->enabled = true; return 0; } /** * ev_fd_disable: * @fd: FD object * * Disables @fd. That means, no more events are handled for @fd until you * re-enable it with ev_fd_enable(). */ SHL_EXPORT void ev_fd_disable(struct ev_fd *fd) { if (!fd || !fd->enabled) return; fd->enabled = false; fd_epoll_remove(fd); } /** * ev_fd_is_enabled: * @fd: FD object * * Returns whether the fd object is enabled or disabled. * * Returns: true if @fd is enabled, otherwise false. */ SHL_EXPORT bool ev_fd_is_enabled(struct ev_fd *fd) { return fd && fd->enabled; } /** * ev_fd_is_bound: * @fd: FD object * * Returns true if the fd object is bound to an event loop. * * Returns: true if @fd is bound, otherwise false */ SHL_EXPORT bool ev_fd_is_bound(struct ev_fd *fd) { return fd && fd->loop; } /** * ev_fd_set_cb_data: * @fd: FD object * @cb: New user callback * @data: New user data * * This changes the user callback and user data that were set in ev_fd_new(). * Both can be set to NULL. If @cb is NULL, then the callback will not be called * anymore. */ SHL_EXPORT void ev_fd_set_cb_data(struct ev_fd *fd, ev_fd_cb cb, void *data) { if (!fd) return; fd->cb = cb; fd->data = data; } /** * ev_fd_update: * @fd: FD object * @mask: Bitmask of %EV_READABLE and %EV_WRITEABLE * * This resets the event mask of @fd to @mask. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_fd_update(struct ev_fd *fd, int mask) { int ret; int omask; if (!fd) return -EINVAL; if (fd->mask == mask && !(mask & EV_ET)) return 0; omask = fd->mask; fd->mask = mask; if (!fd->enabled) return 0; ret = fd_epoll_update(fd); if (ret) { fd->mask = omask; return ret; } return 0; } /** * ev_eloop_new_fd: * @loop: Event loop * @out: Storage for result * @rfd: File descriptor * @mask: Bitmask of %EV_READABLE and %EV_WRITEABLE * @cb: User callback * @data: User data * * This creates a new fd object like ev_fd_new() and directly registers it in * the event loop @loop. See ev_fd_new() and ev_eloop_add_fd() for more * information. * The ref-count of @out is 1 so you must call ev_eloop_rm_fd() to destroy the * fd. You must not call ev_fd_unref() unless you called ev_fd_ref() before. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_eloop_new_fd(struct ev_eloop *loop, struct ev_fd **out, int rfd, int mask, ev_fd_cb cb, void *data) { struct ev_fd *fd; int ret; if (!loop) return -EINVAL; if (!out || rfd < 0) return llog_EINVAL(loop); ret = ev_fd_new(&fd, rfd, mask, cb, data, loop->llog, loop->llog_data); if (ret) return ret; ret = ev_eloop_add_fd(loop, fd); if (ret) { ev_fd_unref(fd); return ret; } ev_fd_unref(fd); *out = fd; return 0; } /** * ev_eloop_add_fd: * @loop: Event loop * @fd: FD Object * * Registers @fd in the event loop @loop. This increases the ref-count of both * @loop and @fd. From now on the user callback of @fd may get called during * dispatching. * * Returns: 0 on success, otherwise negative error code */ SHL_EXPORT int ev_eloop_add_fd(struct ev_eloop *loop, struct ev_fd *fd) { int ret; if (!loop) return -EINVAL; if (!fd || fd->loop) return llog_EINVAL(loop); fd->loop = loop; if (fd->enabled) { ret = fd_epoll_add(fd); if (ret) { fd->loop = NULL; return ret; } } ev_fd_ref(fd); ev_eloop_ref(loop); return 0; } /** * ev_eloop_rm_fd: * @fd: FD object * * Removes the fd object @fd from its event loop. If you did not call * ev_eloop_add_fd() before, this will do nothing. * This decreases the refcount of @fd and the event loop by 1. * It is safe to call this in any callback. This makes sure that the current * dispatcher will not get confused or read invalid memory. */ SHL_EXPORT void ev_eloop_rm_fd(struct ev_fd *fd) { struct ev_eloop *loop; size_t i; if (!fd || !fd->loop) return; loop = fd->loop; if (fd->enabled) fd_epoll_remove(fd); /* * If we are currently dispatching events, we need to remove ourself * from the temporary event list. */ if (loop->dispatching) { for (i = 0; i < loop->cur_fds_cnt; ++i) { if (fd == loop->cur_fds[i].data.ptr) loop->cur_fds[i].data.ptr = NULL; } } fd->loop = NULL; ev_fd_unref(fd); ev_eloop_unref(loop); } /* * Timer sources * Timer sources allow delaying a specific event by an relative timeout. The * timeout can be set to trigger after a specific time. Optionally, you can * also make the timeout trigger every next time the timeout elapses so you * basically get a pulse that reliably calls the callback. * The callback gets as parameter the number of timeouts that elapsed since it * was last called (in case the application couldn't call the callback fast * enough). The timeout can be specified with nano-seconds precision. However, * real precision depends on the operating-system and hardware. */ static int timer_drain(struct ev_timer *timer, uint64_t *out) { int len; uint64_t expirations; if (out) *out = 0; len = read(timer->fd, &expirations, sizeof(expirations)); if (len < 0) { if (errno == EAGAIN) { return 0; } else { llog_warning(timer, "cannot read timerfd (%d): %m", errno); return errno; } } else if (len == 0) { llog_warning(timer, "EOF on timer source"); return -EFAULT; } else if (len != sizeof(expirations)) { llog_warn(timer, "invalid size %d read on timerfd", len); return -EFAULT; } else { if (out) *out = expirations; return 0; } } static void timer_cb(struct ev_fd *fd, int mask, void *data) { struct ev_timer *timer = data; uint64_t expirations; int ret; if (mask & (EV_HUP | EV_ERR)) { llog_warn(fd, "HUP/ERR on timer source"); goto err_cb; } if (mask & EV_READABLE) { ret = timer_drain(timer, &expirations); if (ret) goto err_cb; if (expirations > 0) { if (timer->cb) timer->cb(timer, expirations, timer->data); } } return; err_cb: ev_timer_disable(timer); if (timer->cb) timer->cb(timer, 0, timer->data); } static const struct itimerspec ev_timer_zero; /** * ev_timer_new: * @out: Timer pointer where to store the new timer * @spec: Timespan * @cb: callback to use for this event-source * @data: user-specified data * @log: logging function or NULL * @log_data: logging function user-data * * This creates a new timer-source. See "man timerfd_create" for information on * the @spec argument. The timer is always relative and uses the * monotonic-kernel clock. * * Returns: 0 on success, negative error on failure */ SHL_EXPORT int ev_timer_new(struct ev_timer **out, const struct itimerspec *spec, ev_timer_cb cb, void *data, ev_log_t log, void *log_data) { struct ev_timer *timer; int ret; if (!out) return llog_dEINVAL(log, log_data); if (!spec) spec = &ev_timer_zero; timer = malloc(sizeof(*timer)); if (!timer) return llog_dENOMEM(log, log_data); memset(timer, 0, sizeof(*timer)); timer->ref = 1; timer->llog = log; timer->llog_data = log_data; timer->cb = cb; timer->data = data; timer->fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (timer->fd < 0) { llog_error(timer, "cannot create timerfd (%d): %m", errno); ret = -EFAULT; goto err_free; } ret = timerfd_settime(timer->fd, 0, spec, NULL); if (ret) { llog_warn(timer, "cannot set timerfd (%d): %m", errno); ret = -EFAULT; goto err_close; } ret = ev_fd_new(&timer->efd, timer->fd, EV_READABLE, timer_cb, timer, timer->llog, timer->llog_data); if (ret) goto err_close; *out = timer; return 0; err_close: close(timer->fd); err_free: free(timer); return ret; } /** * ev_timer_ref: * @timer: Timer object * * Increase reference count by 1. */ SHL_EXPORT void ev_timer_ref(struct ev_timer *timer) { if (!timer) return; if (!timer->ref) return llog_vEINVAL(timer); ++timer->ref; } /** * ev_timer_unref: * @timer: Timer object * * Decrease reference-count by 1 and destroy timer if it drops to 0. */ SHL_EXPORT void ev_timer_unref(struct ev_timer *timer) { if (!timer) return; if (!timer->ref) return llog_vEINVAL(timer); if (--timer->ref) return; ev_fd_unref(timer->efd); close(timer->fd); free(timer); } /** * ev_timer_enable: * @timer: Timer object * * Enable the timer. This calls ev_fd_enable() on the fd that implements this * timer. * * Returns: 0 on success negative error code on failure */ SHL_EXPORT int ev_timer_enable(struct ev_timer *timer) { if (!timer) return -EINVAL; return ev_fd_enable(timer->efd); } /** * ev_timer_disable: * @timer: Timer object * * Disable the timer. This calls ev_fd_disable() on the fd that implements this * timer. * * Returns: 0 on success and negative error code on failure */ SHL_EXPORT void ev_timer_disable(struct ev_timer *timer) { if (!timer) return; ev_fd_disable(timer->efd); } /** * ev_timer_is_enabled: * @timer: Timer object * * Checks whether the timer is enabled. * * Returns: true if timer is enabled, false otherwise */ SHL_EXPORT bool ev_timer_is_enabled(struct ev_timer *timer) { return timer && ev_fd_is_enabled(timer->efd); } /** * ev_timer_is_bound: * @timer: Timer object * * Checks whether the timer is bound to an event loop. * * Returns: true if the timer is bound, false otherwise. */ SHL_EXPORT bool ev_timer_is_bound(struct ev_timer *timer) { return timer && ev_fd_is_bound(timer->efd); } /** * ev_timer_set_cb_data: * @timer: Timer object * @cb: User callback or NULL * @data: User data or NULL * * This changes the user-supplied callback and data that is used for this timer * object. */ SHL_EXPORT void ev_timer_set_cb_data(struct ev_timer *timer, ev_timer_cb cb, void *data) { if (!timer) return; timer->cb = cb; timer->data = data; } /** * ev_timer_update: * @timer: Timer object * @spec: timespan * * This changes the timer timespan. See "man timerfd_settime" for information * on the @spec parameter. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_timer_update(struct ev_timer *timer, const struct itimerspec *spec) { int ret; if (!timer) return -EINVAL; if (!spec) spec = &ev_timer_zero; ret = timerfd_settime(timer->fd, 0, spec, NULL); if (ret) { llog_warn(timer, "cannot set timerfd (%d): %m", errno); return -EFAULT; } return 0; } /** * ev_timer_drain: * @timer: valid timer object * @expirations: destination to save result or NULL * * This reads the current expiration-count from the timer object @timer and * saves it in @expirations (if it is non-NULL). This can be used to clear the * timer after an idle-period or similar. * Note that the timer_cb() callback function automatically calls this before * calling the user-supplied callback. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_timer_drain(struct ev_timer *timer, uint64_t *expirations) { if (!timer) return -EINVAL; return timer_drain(timer, expirations); } /** * ev_eloop_new_timer: * @loop: event loop * @out: output where to store the new timer * @spec: timespan * @cb: user callback * @data: user-supplied data * * This is a combination of ev_timer_new() and ev_eloop_add_timer(). See both * for more information. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_eloop_new_timer(struct ev_eloop *loop, struct ev_timer **out, const struct itimerspec *spec, ev_timer_cb cb, void *data) { struct ev_timer *timer; int ret; if (!loop) return -EINVAL; if (!out) return llog_EINVAL(loop); ret = ev_timer_new(&timer, spec, cb, data, loop->llog, loop->llog_data); if (ret) return ret; ret = ev_eloop_add_timer(loop, timer); if (ret) { ev_timer_unref(timer); return ret; } ev_timer_unref(timer); *out = timer; return 0; } /** * ev_eloop_add_timer: * @loop: event loop * @timer: Timer source * * This adds @timer as source to @loop. @timer must be currently unbound, * otherwise, this will fail with -EALREADY. * * Returns: 0 on success, negative error code on failure */ SHL_EXPORT int ev_eloop_add_timer(struct ev_eloop *loop, struct ev_timer *timer) { int ret; if (!loop) return -EINVAL; if (!timer) return llog_EINVAL(loop); if (ev_fd_is_bound(timer->efd)) return -EALREADY; ret = ev_eloop_add_fd(loop, timer->efd); if (ret) return ret; ev_timer_ref(timer); return 0; } /** * ev_eloop_rm_timer: * @timer: Timer object * * If @timer is currently bound to an event loop, this will remove this bondage * again. */ SHL_EXPORT void ev_eloop_rm_timer(struct ev_timer *timer) { if (!timer || !ev_fd_is_bound(timer->efd)) return; ev_eloop_rm_fd(timer->efd); ev_timer_unref(timer); } /* * Counter Sources * Counter sources are a very basic event notification mechanism. It is based * around the eventfd() system call on linux machines. Internally, there is a * 64bit unsigned integer that can be increased by the caller. By default it is * set to 0. If it is non-zero, the event-fd will be notified and the * user-defined callback is called. The callback gets as argument the current * state of the counter and the counter is reset to 0. * * If the internal counter would overflow, an increase() fails silently so an * overflow will never occur, however, you may loose events this way. This can * be ignored when increasing with small values, only. */ static void counter_event(struct ev_fd *fd, int mask, void *data) { struct ev_counter *cnt = data; int ret; uint64_t val; if (mask & (EV_HUP | EV_ERR)) { llog_warning(fd, "HUP/ERR on eventfd"); if (cnt->cb) cnt->cb(cnt, 0, cnt->data); return; } if (!(mask & EV_READABLE)) return; ret = read(cnt->fd, &val, sizeof(val)); if (ret < 0) { if (errno != EAGAIN) { llog_warning(fd, "reading eventfd failed (%d): %m", errno); ev_counter_disable(cnt); if (cnt->cb) cnt->cb(cnt, 0, cnt->data); } } else if (ret == 0) { llog_warning(fd, "EOF on eventfd"); ev_counter_disable(cnt); if (cnt->cb) cnt->cb(cnt, 0, cnt->data); } else if (ret != sizeof(val)) { llog_warning(fd, "read %d bytes instead of 8 on eventfd", ret); ev_counter_disable(cnt); if (cnt->cb) cnt->cb(cnt, 0, cnt->data); } else if (val > 0) { if (cnt->cb) cnt->cb(cnt, val, cnt->data); } } /** * ev_counter_new: * @out: Where to store the new counter * @cb: user-supplied callback * @data: user-supplied data * @log: logging function or NULL * @log_data: logging function user-data * * This creates a new counter object and stores it in @out. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_counter_new(struct ev_counter **out, ev_counter_cb cb, void *data, ev_log_t log, void *log_data) { struct ev_counter *cnt; int ret; if (!out) return llog_dEINVAL(log, log_data); cnt = malloc(sizeof(*cnt)); if (!cnt) return llog_dENOMEM(log, log_data); memset(cnt, 0, sizeof(*cnt)); cnt->ref = 1; cnt->llog = log; cnt->llog_data = log_data; cnt->cb = cb; cnt->data = data; cnt->fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (cnt->fd < 0) { llog_error(cnt, "cannot create eventfd (%d): %m", errno); ret = -EFAULT; goto err_free; } ret = ev_fd_new(&cnt->efd, cnt->fd, EV_READABLE, counter_event, cnt, cnt->llog, cnt->llog_data); if (ret) goto err_close; *out = cnt; return 0; err_close: close(cnt->fd); err_free: free(cnt); return ret; } /** * ev_counter_ref: * @cnt: Counter object * * This increases the reference-count of @cnt by 1. */ SHL_EXPORT void ev_counter_ref(struct ev_counter *cnt) { if (!cnt) return; if (!cnt->ref) return llog_vEINVAL(cnt); ++cnt->ref; } /** * ev_counter_unref: * @cnt: Counter object * * This decreases the reference-count of @cnt by 1 and destroys the object if * it drops to 0. */ SHL_EXPORT void ev_counter_unref(struct ev_counter *cnt) { if (!cnt) return; if (!cnt->ref) return llog_vEINVAL(cnt); if (--cnt->ref) return; ev_fd_unref(cnt->efd); close(cnt->fd); free(cnt); } /** * ev_counter_enable: * @cnt: Counter object * * This enables the counter object. It calls ev_fd_enable() on the underlying * file-descriptor. * * Returns: 0 on success, negative error code on failure */ SHL_EXPORT int ev_counter_enable(struct ev_counter *cnt) { if (!cnt) return -EINVAL; return ev_fd_enable(cnt->efd); } /** * ev_counter_disable: * @cnt: Counter object * * This disables the counter. It calls ev_fd_disable() on the underlying * file-descriptor. */ SHL_EXPORT void ev_counter_disable(struct ev_counter *cnt) { if (!cnt) return; ev_fd_disable(cnt->efd); } /** * ev_counter_is_enabled: * @cnt: counter object * * Checks whether the counter is enabled. * * Returns: true if the counter is enabled, otherwise returns false. */ SHL_EXPORT bool ev_counter_is_enabled(struct ev_counter *cnt) { return cnt && ev_fd_is_enabled(cnt->efd); } /** * ev_counter_is_bound: * @cnt: Counter object * * Checks whether the counter is bound to an event loop. * * Returns: true if the counter is bound, otherwise false is returned. */ SHL_EXPORT bool ev_counter_is_bound(struct ev_counter *cnt) { return cnt && ev_fd_is_bound(cnt->efd); } /** * ev_counter_set_cb_data: * @cnt: Counter object * @cb: user-supplied callback * @data: user-supplied data * * This changes the user-supplied callback and data for the given counter * object. */ SHL_EXPORT void ev_counter_set_cb_data(struct ev_counter *cnt, ev_counter_cb cb, void *data) { if (!cnt) return; cnt->cb = cb; cnt->data = data; } /** * ev_counter_inc: * @cnt: Counter object * @val: Counter increase amount * * This increases the counter @cnt by @val. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_counter_inc(struct ev_counter *cnt, uint64_t val) { if (!cnt) return -EINVAL; return write_eventfd(cnt->llog, cnt->llog_data, cnt->fd, val); } /** * ev_eloop_new_counter: * @eloop: event loop * @out: output storage for new counter * @cb: user-supplied callback * @data: user-supplied data * * This combines ev_counter_new() and ev_eloop_add_counter() in one call. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_eloop_new_counter(struct ev_eloop *eloop, struct ev_counter **out, ev_counter_cb cb, void *data) { int ret; struct ev_counter *cnt; if (!eloop) return -EINVAL; if (!out) return llog_EINVAL(eloop); ret = ev_counter_new(&cnt, cb, data, eloop->llog, eloop->llog_data); if (ret) return ret; ret = ev_eloop_add_counter(eloop, cnt); if (ret) { ev_counter_unref(cnt); return ret; } ev_counter_unref(cnt); *out = cnt; return 0; } /** * ev_eloop_add_counter: * @eloop: Event loop * @cnt: Counter object * * This adds @cnt to the given event loop @eloop. If @cnt is already bound, * this will fail with -EALREADY. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_eloop_add_counter(struct ev_eloop *eloop, struct ev_counter *cnt) { int ret; if (!eloop) return -EINVAL; if (!cnt) return llog_EINVAL(eloop); if (ev_fd_is_bound(cnt->efd)) return -EALREADY; ret = ev_eloop_add_fd(eloop, cnt->efd); if (ret) return ret; ev_counter_ref(cnt); return 0; } /** * ev_eloop_rm_counter: * @cnt: Counter object * * If @cnt is bound to an event-loop, then this will remove this bondage again. */ SHL_EXPORT void ev_eloop_rm_counter(struct ev_counter *cnt) { if (!cnt || !ev_fd_is_bound(cnt->efd)) return; ev_eloop_rm_fd(cnt->efd); ev_counter_unref(cnt); } /* * Shared signals * This allows registering for shared signal events. See description of the * shared signal object above for more information how this works. Also see the * eloop description to see some drawbacks when nesting eloop objects with the * same shared signal sources. */ /** * ev_eloop_register_signal_cb: * @loop: event loop * @signum: Signal number * @cb: user-supplied callback * @data: user-supplied data * * This register a new callback for the given signal @signum. @cb must not be * NULL! * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_eloop_register_signal_cb(struct ev_eloop *loop, int signum, ev_signal_shared_cb cb, void *data) { struct ev_signal_shared *sig = NULL; int ret; struct shl_dlist *iter; if (!loop) return -EINVAL; if (signum < 0 || !cb) return llog_EINVAL(loop); shl_dlist_for_each(iter, &loop->sig_list) { sig = shl_dlist_entry(iter, struct ev_signal_shared, list); if (sig->signum == signum) break; sig = NULL; } if (!sig) { ret = signal_new(&sig, loop, signum); if (ret) return ret; } ret = shl_hook_add_cast(sig->hook, cb, data, false); if (ret) { signal_free(sig); return ret; } return 0; } /** * ev_eloop_unregister_signal_cb: * @loop: event loop * @signum: signal number * @cb: user-supplied callback * @data: user-supplied data * * This removes a previously registered signal-callback again. The arguments * must be the same as for the ev_eloop_register_signal_cb() call. If multiple * callbacks with the same arguments are registered, then only one callback is * removed. It doesn't matter which callback is removed as both are identical. */ SHL_EXPORT void ev_eloop_unregister_signal_cb(struct ev_eloop *loop, int signum, ev_signal_shared_cb cb, void *data) { struct ev_signal_shared *sig; struct shl_dlist *iter; if (!loop) return; shl_dlist_for_each(iter, &loop->sig_list) { sig = shl_dlist_entry(iter, struct ev_signal_shared, list); if (sig->signum == signum) { shl_hook_rm_cast(sig->hook, cb, data); if (!shl_hook_num(sig->hook)) signal_free(sig); return; } } } /* * Child reaper sources * If at least one child-reaper callback is registered, then the eloop object * listens for SIGCHLD and waits for all exiting children. The callbacks are * then notified for each PID that signaled an event. * Note that this cannot be done via the shared-signal sources as the waitpid() * call must not be done in callbacks. Otherwise, only one callback would see * the events while others will call waitpid() and get EAGAIN. */ SHL_EXPORT int ev_eloop_register_child_cb(struct ev_eloop *loop, ev_child_cb cb, void *data) { int ret; bool empty; if (!loop) return -EINVAL; empty = !shl_hook_num(loop->chlds); ret = shl_hook_add_cast(loop->chlds, cb, data, false); if (ret) return ret; if (empty) { ret = ev_eloop_register_signal_cb(loop, SIGCHLD, sig_child, loop); if (ret) { shl_hook_rm_cast(loop->chlds, cb, data); return ret; } } return 0; } SHL_EXPORT void ev_eloop_unregister_child_cb(struct ev_eloop *loop, ev_child_cb cb, void *data) { if (!loop || !shl_hook_num(loop->chlds)) return; shl_hook_rm_cast(loop->chlds, cb, data); if (!shl_hook_num(loop->chlds)) ev_eloop_unregister_signal_cb(loop, SIGCHLD, sig_child, loop); } /* * Idle sources * Idle sources are called every time when a next dispatch round is started. * That means, unless there is no idle source registered, the thread will * _never_ go to sleep. So please unregister your idle source if no longer * needed. */ /** * ev_eloop_register_idle_cb: * @eloop: event loop * @cb: user-supplied callback * @data: user-supplied data * @flags: flags * * This register a new idle-source with the given callback and data. @cb must * not be NULL!. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_eloop_register_idle_cb(struct ev_eloop *eloop, ev_idle_cb cb, void *data, unsigned int flags) { int ret; bool os = flags & EV_ONESHOT; if (!eloop || (flags & ~EV_IDLE_ALL)) return -EINVAL; if ((flags & EV_SINGLE)) ret = shl_hook_add_single_cast(eloop->idlers, cb, data, os); else ret = shl_hook_add_cast(eloop->idlers, cb, data, os); if (ret) return ret; ret = write_eventfd(eloop->llog, eloop->llog_data, eloop->idle_fd, 1); if (ret) { llog_warning(eloop, "cannot increase eloop idle-counter"); shl_hook_rm_cast(eloop->idlers, cb, data); return ret; } return 0; } /** * ev_eloop_unregister_idle_cb: * @eloop: event loop * @cb: user-supplied callback * @data: user-supplied data * @flags: flags * * This removes an idle-source. The arguments must be the same as for the * ev_eloop_register_idle_cb() call. If two identical callbacks are registered, * then only one is removed. It doesn't matter which one is removed, because * they are identical. */ SHL_EXPORT void ev_eloop_unregister_idle_cb(struct ev_eloop *eloop, ev_idle_cb cb, void *data, unsigned int flags) { if (!eloop || (flags & ~EV_IDLE_ALL)) return; if (flags & EV_SINGLE) shl_hook_rm_all_cast(eloop->idlers, cb, data); else shl_hook_rm_cast(eloop->idlers, cb, data); } /* * Pre-Dispatch Callbacks * A pre-dispatch cb is called before a single dispatch round is started. * You should avoid using them and instead not rely on any specific * dispatch-behavior but expect every event to be received asynchronously. * However, this hook is useful to integrate other limited APIs into this event * loop if they do not provide proper FD-abstractions. */ /** * ev_eloop_register_pre_cb: * @eloop: event loop * @cb: user-supplied callback * @data: user-supplied data * * This register a new pre-cb with the given callback and data. @cb must * not be NULL!. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_eloop_register_pre_cb(struct ev_eloop *eloop, ev_idle_cb cb, void *data) { if (!eloop) return -EINVAL; return shl_hook_add_cast(eloop->pres, cb, data, false); } /** * ev_eloop_unregister_pre_cb: * @eloop: event loop * @cb: user-supplied callback * @data: user-supplied data * * This removes a pre-cb. The arguments must be the same as for the * ev_eloop_register_pre_cb() call. If two identical callbacks are registered, * then only one is removed. It doesn't matter which one is removed, because * they are identical. */ SHL_EXPORT void ev_eloop_unregister_pre_cb(struct ev_eloop *eloop, ev_idle_cb cb, void *data) { if (!eloop) return; shl_hook_rm_cast(eloop->pres, cb, data); } /* * Post-Dispatch Callbacks * A post-dispatch cb is called whenever a single dispatch round is complete. * You should avoid using them and instead not rely on any specific * dispatch-behavior but expect every event to be received asynchronously. * However, this hook is useful to integrate other limited APIs into this event * loop if they do not provide proper FD-abstractions. */ /** * ev_eloop_register_post_cb: * @eloop: event loop * @cb: user-supplied callback * @data: user-supplied data * * This register a new post-cb with the given callback and data. @cb must * not be NULL!. * * Returns: 0 on success, negative error code on failure. */ SHL_EXPORT int ev_eloop_register_post_cb(struct ev_eloop *eloop, ev_idle_cb cb, void *data) { if (!eloop) return -EINVAL; return shl_hook_add_cast(eloop->posts, cb, data, false); } /** * ev_eloop_unregister_post_cb: * @eloop: event loop * @cb: user-supplied callback * @data: user-supplied data * * This removes a post-cb. The arguments must be the same as for the * ev_eloop_register_post_cb() call. If two identical callbacks are registered, * then only one is removed. It doesn't matter which one is removed, because * they are identical. */ SHL_EXPORT void ev_eloop_unregister_post_cb(struct ev_eloop *eloop, ev_idle_cb cb, void *data) { if (!eloop) return; shl_hook_rm_cast(eloop->posts, cb, data); }