mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-04 04:06:06 -05:00
fdm: add support for managing signals
Add fdm_signal_add() and fdm_signal_del(). Signals added to the fdm will be monitored, and the provided callback called as “soon as possible” from the main context (i.e not from the signal handler context). Monitored signals are *blocked* by default. We use epoll_pwait() to unblock them while we’re polling. This allows us to do race-free signal detection. We use a single handler for all monitored signals; the handler simply updates the signal’s slot in a global array (sized to fit SIGRTMAX signals). When epoll_pwait() returns EINTR, we loop the global array. The callback associated with each signal that fired is called.
This commit is contained in:
parent
ac46e58448
commit
cf1335f258
3 changed files with 147 additions and 26 deletions
163
fdm.c
163
fdm.c
|
|
@ -7,6 +7,7 @@
|
|||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <sys/epoll.h>
|
||||
|
||||
|
|
@ -16,14 +17,20 @@
|
|||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
|
||||
struct handler {
|
||||
struct fd_handler {
|
||||
int fd;
|
||||
int events;
|
||||
fdm_handler_t callback;
|
||||
fdm_fd_handler_t callback;
|
||||
void *callback_data;
|
||||
bool deleted;
|
||||
};
|
||||
|
||||
struct sig_handler {
|
||||
int signo;
|
||||
fdm_signal_handler_t callback;
|
||||
void *callback_data;
|
||||
};
|
||||
|
||||
struct hook {
|
||||
fdm_hook_t callback;
|
||||
void *callback_data;
|
||||
|
|
@ -34,33 +41,58 @@ typedef tll(struct hook) hooks_t;
|
|||
struct fdm {
|
||||
int epoll_fd;
|
||||
bool is_polling;
|
||||
tll(struct handler *) fds;
|
||||
tll(struct handler *) deferred_delete;
|
||||
tll(struct fd_handler *) fds;
|
||||
tll(struct fd_handler *) deferred_delete;
|
||||
|
||||
sigset_t sigmask;
|
||||
struct sig_handler *signal_handlers;
|
||||
|
||||
hooks_t hooks_low;
|
||||
hooks_t hooks_normal;
|
||||
hooks_t hooks_high;
|
||||
};
|
||||
|
||||
static volatile sig_atomic_t *received_signals = NULL;
|
||||
|
||||
struct fdm *
|
||||
fdm_init(void)
|
||||
{
|
||||
sigset_t sigmask;
|
||||
if (sigprocmask(0, NULL, &sigmask) < 0) {
|
||||
LOG_ERRNO("failed to get process signal mask");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
||||
if (epoll_fd == -1) {
|
||||
LOG_ERRNO("failed to create epoll FD");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(received_signals == NULL); /* Only one FDM instance supported */
|
||||
received_signals = calloc(SIGRTMAX, sizeof(received_signals[0]));
|
||||
|
||||
struct fdm *fdm = malloc(sizeof(*fdm));
|
||||
if (unlikely(fdm == NULL)) {
|
||||
LOG_ERRNO("malloc() failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct sig_handler *sig_handlers = calloc(SIGRTMAX, sizeof(sig_handlers[0]));
|
||||
|
||||
if (sig_handlers == NULL) {
|
||||
LOG_ERRNO("failed to allocate signal handler array");
|
||||
free(fdm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*fdm = (struct fdm){
|
||||
.epoll_fd = epoll_fd,
|
||||
.is_polling = false,
|
||||
.fds = tll_init(),
|
||||
.deferred_delete = tll_init(),
|
||||
.sigmask = sigmask,
|
||||
.signal_handlers = sig_handlers,
|
||||
.hooks_low = tll_init(),
|
||||
.hooks_normal = tll_init(),
|
||||
.hooks_high = tll_init(),
|
||||
|
|
@ -77,6 +109,11 @@ fdm_destroy(struct fdm *fdm)
|
|||
if (tll_length(fdm->fds) > 0)
|
||||
LOG_WARN("FD list not empty");
|
||||
|
||||
for (int i = 0; i < SIGRTMAX; i++) {
|
||||
if (fdm->signal_handlers[i].callback != NULL)
|
||||
LOG_WARN("handler for signal %d not removed", i);
|
||||
}
|
||||
|
||||
if (tll_length(fdm->hooks_low) > 0 ||
|
||||
tll_length(fdm->hooks_normal) > 0 ||
|
||||
tll_length(fdm->hooks_high) > 0)
|
||||
|
|
@ -90,6 +127,9 @@ fdm_destroy(struct fdm *fdm)
|
|||
assert(tll_length(fdm->hooks_normal) == 0);
|
||||
assert(tll_length(fdm->hooks_high) == 0);
|
||||
|
||||
sigprocmask(SIG_SETMASK, &fdm->sigmask, NULL);
|
||||
free(fdm->signal_handlers);
|
||||
|
||||
tll_free(fdm->fds);
|
||||
tll_free(fdm->deferred_delete);
|
||||
tll_free(fdm->hooks_low);
|
||||
|
|
@ -97,19 +137,15 @@ fdm_destroy(struct fdm *fdm)
|
|||
tll_free(fdm->hooks_high);
|
||||
close(fdm->epoll_fd);
|
||||
free(fdm);
|
||||
|
||||
free((void *)received_signals);
|
||||
received_signals = NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
|
||||
fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t cb, void *data)
|
||||
{
|
||||
#if defined(_DEBUG)
|
||||
int flags = fcntl(fd, F_GETFL);
|
||||
if (!(flags & O_NONBLOCK)) {
|
||||
LOG_ERR("FD=%d is in blocking mode", fd);
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
tll_foreach(fdm->fds, it) {
|
||||
if (it->item->fd == fd) {
|
||||
LOG_ERR("FD=%d already registered", fd);
|
||||
|
|
@ -119,30 +155,30 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
|
|||
}
|
||||
#endif
|
||||
|
||||
struct handler *fd_data = malloc(sizeof(*fd_data));
|
||||
if (unlikely(fd_data == NULL)) {
|
||||
struct fd_handler *handler = malloc(sizeof(*handler));
|
||||
if (unlikely(handler == NULL)) {
|
||||
LOG_ERRNO("malloc() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
*fd_data = (struct handler) {
|
||||
*handler = (struct fd_handler) {
|
||||
.fd = fd,
|
||||
.events = events,
|
||||
.callback = handler,
|
||||
.callback = cb,
|
||||
.callback_data = data,
|
||||
.deleted = false,
|
||||
};
|
||||
|
||||
tll_push_back(fdm->fds, fd_data);
|
||||
tll_push_back(fdm->fds, handler);
|
||||
|
||||
struct epoll_event ev = {
|
||||
.events = events,
|
||||
.data = {.ptr = fd_data},
|
||||
.data = {.ptr = handler},
|
||||
};
|
||||
|
||||
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||
LOG_ERRNO("failed to register FD=%d with epoll", fd);
|
||||
free(fd_data);
|
||||
free(handler);
|
||||
tll_pop_back(fdm->fds);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -194,7 +230,7 @@ fdm_del_no_close(struct fdm *fdm, int fd)
|
|||
}
|
||||
|
||||
static bool
|
||||
event_modify(struct fdm *fdm, struct handler *fd, int new_events)
|
||||
event_modify(struct fdm *fdm, struct fd_handler *fd, int new_events)
|
||||
{
|
||||
if (new_events == fd->events)
|
||||
return true;
|
||||
|
|
@ -291,6 +327,69 @@ fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority)
|
|||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
signal_handler(int signo)
|
||||
{
|
||||
received_signals[signo] = true;
|
||||
}
|
||||
|
||||
bool
|
||||
fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data)
|
||||
{
|
||||
if (fdm->signal_handlers[signo].callback != NULL) {
|
||||
LOG_ERR("signal %d already has a handler", signo);
|
||||
return false;
|
||||
}
|
||||
|
||||
sigset_t mask, original;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, signo);
|
||||
|
||||
if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) {
|
||||
LOG_ERRNO("failed to block signal %d", signo);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sigaction action = {.sa_handler = &signal_handler};
|
||||
if (sigaction(signo, &action, NULL) < 0) {
|
||||
LOG_ERRNO("failed to set signal handler for signal %d", signo);
|
||||
sigprocmask(SIG_SETMASK, &original, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
received_signals[signo] = false;
|
||||
fdm->signal_handlers[signo].callback = handler;
|
||||
fdm->signal_handlers[signo].callback_data = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
fdm_signal_del(struct fdm *fdm, int signo)
|
||||
{
|
||||
if (fdm->signal_handlers[signo].callback == NULL)
|
||||
return false;
|
||||
|
||||
struct sigaction action = {.sa_handler = SIG_DFL};
|
||||
if (sigaction(signo, &action, NULL) < 0) {
|
||||
LOG_ERRNO("failed to restore signal handler for signal %d", signo);
|
||||
return false;
|
||||
}
|
||||
|
||||
received_signals[signo] = false;
|
||||
fdm->signal_handlers[signo].callback = NULL;
|
||||
fdm->signal_handlers[signo].callback_data = NULL;
|
||||
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, signo);
|
||||
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) {
|
||||
LOG_ERRNO("failed to unblock signal %d", signo);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
fdm_poll(struct fdm *fdm)
|
||||
{
|
||||
|
|
@ -324,10 +423,28 @@ fdm_poll(struct fdm *fdm)
|
|||
|
||||
struct epoll_event events[tll_length(fdm->fds)];
|
||||
|
||||
int r = epoll_wait(fdm->epoll_fd, events, tll_length(fdm->fds), -1);
|
||||
int r = epoll_pwait(
|
||||
fdm->epoll_fd, events, tll_length(fdm->fds), -1, &fdm->sigmask);
|
||||
|
||||
if (unlikely(r < 0)) {
|
||||
if (errno == EINTR)
|
||||
if (errno == EINTR) {
|
||||
/* TODO: is it possible to receive a signal without
|
||||
* getting EINTR here? */
|
||||
|
||||
for (int i = 0; i < SIGRTMAX; i++) {
|
||||
if (received_signals[i]) {
|
||||
|
||||
received_signals[i] = false;
|
||||
struct sig_handler *handler = &fdm->signal_handlers[i];
|
||||
|
||||
assert(handler->callback != NULL);
|
||||
if (!handler->callback(fdm, i, handler->callback_data))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_ERRNO("failed to epoll");
|
||||
return false;
|
||||
|
|
@ -337,7 +454,7 @@ fdm_poll(struct fdm *fdm)
|
|||
|
||||
fdm->is_polling = true;
|
||||
for (int i = 0; i < r; i++) {
|
||||
struct handler *fd = events[i].data.ptr;
|
||||
struct fd_handler *fd = events[i].data.ptr;
|
||||
if (fd->deleted)
|
||||
continue;
|
||||
|
||||
|
|
|
|||
8
fdm.h
8
fdm.h
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
struct fdm;
|
||||
|
||||
typedef bool (*fdm_handler_t)(struct fdm *fdm, int fd, int events, void *data);
|
||||
typedef bool (*fdm_fd_handler_t)(struct fdm *fdm, int fd, int events, void *data);
|
||||
typedef bool (*fdm_signal_handler_t)(struct fdm *fdm, int signo, void *data);
|
||||
typedef void (*fdm_hook_t)(struct fdm *fdm, void *data);
|
||||
|
||||
enum fdm_hook_priority {
|
||||
|
|
@ -16,7 +17,7 @@ enum fdm_hook_priority {
|
|||
struct fdm *fdm_init(void);
|
||||
void fdm_destroy(struct fdm *fdm);
|
||||
|
||||
bool fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data);
|
||||
bool fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t handler, void *data);
|
||||
bool fdm_del(struct fdm *fdm, int fd);
|
||||
bool fdm_del_no_close(struct fdm *fdm, int fd);
|
||||
|
||||
|
|
@ -27,4 +28,7 @@ bool fdm_hook_add(struct fdm *fdm, fdm_hook_t hook, void *data,
|
|||
enum fdm_hook_priority priority);
|
||||
bool fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority);
|
||||
|
||||
bool fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data);
|
||||
bool fdm_signal_del(struct fdm *fdm, int signo);
|
||||
|
||||
bool fdm_poll(struct fdm *fdm);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ async_write(int fd, const void *data, size_t len, size_t *idx)
|
|||
}
|
||||
|
||||
bool
|
||||
fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
|
||||
fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t handler, void *data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue