Add unix signal integration with the iomux
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include "ucore/tailq.h"
|
||||
#include "ucore/fd_utils.h"
|
||||
#include "ucore/iomux_signal.h"
|
||||
|
||||
/* Integrating of unix signals (man 7 signal) with
|
||||
* an IOMux event loop.
|
||||
* This is done by setting up a pipe, registring a handler with
|
||||
* the IOMux to handle the read end to that pipe, and writing
|
||||
* the signal number to that pipe in the signal handler.
|
||||
*
|
||||
* Currently the signal number is treated as an uint8_t to avoid
|
||||
* dealing with the extremely unlikely case of partial writes in
|
||||
* a signal handler. Currently this is more than enough for the max value of
|
||||
* signals on linux at least.
|
||||
*
|
||||
*/
|
||||
|
||||
struct IOMuxSignalEntry {
|
||||
struct TailQ entry; //within SignalData.head
|
||||
int signo;
|
||||
IOMuxSignalHandler callback;
|
||||
void *cookie;
|
||||
struct sigaction old_sa;
|
||||
};
|
||||
|
||||
struct SignalData {
|
||||
struct TailQ head; //of IOMuxSignalEntry
|
||||
struct IOMuxFD fd;
|
||||
int pipes[2];
|
||||
struct IOMux *mux;
|
||||
int initialized;
|
||||
};
|
||||
|
||||
static pthread_mutex_t g_signal_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
//protected by g_signal_lock
|
||||
struct SignalData g_signal_global;
|
||||
|
||||
static inline void uc_signal_lock(void)
|
||||
{
|
||||
int rc = pthread_mutex_lock(&g_signal_lock);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
static inline void uc_signal_unlock(void)
|
||||
{
|
||||
int rc = pthread_mutex_unlock(&g_signal_lock);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
//call with lock held
|
||||
static struct IOMuxSignalEntry *uc_find_signal_entry(int signo)
|
||||
{
|
||||
struct IOMuxSignalEntry *it;
|
||||
|
||||
UC_TAILQ_FOREACH_CONTAINER(it, entry, &g_signal_global.head) {
|
||||
if (it->signo == signo) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//call with lock held
|
||||
static void uc_deinitailize_iomux_signals(void)
|
||||
{
|
||||
assert(g_signal_global.mux != NULL);
|
||||
assert(g_signal_global.initialized == 1);
|
||||
assert(uc_tailq_empty(&g_signal_global.head));
|
||||
|
||||
iomux_unregister_fd(g_signal_global.mux, &g_signal_global.fd);
|
||||
|
||||
close(g_signal_global.pipes[0]);
|
||||
close(g_signal_global.pipes[1]);
|
||||
|
||||
g_signal_global.mux = NULL;
|
||||
g_signal_global.initialized = 0;
|
||||
g_signal_global.fd.callback = NULL;
|
||||
g_signal_global.fd.fd = -1;
|
||||
}
|
||||
|
||||
//IOMux fd callback, which dispatches the signal to the registered handler
|
||||
static void uc_iomux_signal_cb(struct IOMux *mux, struct IOMuxFD *fd, unsigned int events)
|
||||
{
|
||||
|
||||
uint8_t signo;
|
||||
ssize_t rc;
|
||||
struct IOMuxSignalEntry *entry = NULL;
|
||||
|
||||
uc_signal_lock();
|
||||
|
||||
rc = read(fd->fd, &signo, 1);
|
||||
if (rc == 1) {
|
||||
entry = uc_find_signal_entry(signo);
|
||||
}
|
||||
|
||||
assert(rc != -1 || errno == EWOULDBLOCK);
|
||||
|
||||
//need to unlock here, otherwise we deadlock if the callback
|
||||
//registers/unregisters a signal
|
||||
uc_signal_unlock();
|
||||
|
||||
if (entry != NULL) {
|
||||
entry->callback(mux, signo, entry->cookie);
|
||||
//note don't touch entry after this, the callback might
|
||||
//have unregistered itself
|
||||
}
|
||||
}
|
||||
|
||||
//unix signal handler
|
||||
static void uc_global_signal_handler(int signo)
|
||||
{
|
||||
uint8_t data = signo;
|
||||
int save_errno = errno;
|
||||
|
||||
write(g_signal_global.pipes[1], &data, 1);
|
||||
|
||||
errno = save_errno;
|
||||
}
|
||||
|
||||
//call with lock held
|
||||
static int uc_initailize_iomux_signals(struct IOMux *mux)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
assert(g_signal_global.mux == NULL);
|
||||
assert(g_signal_global.initialized == 0);
|
||||
|
||||
rc = pipe(g_signal_global.pipes);
|
||||
if (rc != 0) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
uc_set_nonblocking(g_signal_global.pipes[0]);
|
||||
uc_set_nonblocking(g_signal_global.pipes[1]);
|
||||
|
||||
uc_tailq_init(&g_signal_global.head);
|
||||
|
||||
g_signal_global.mux = mux;
|
||||
g_signal_global.fd.fd = g_signal_global.pipes[0];
|
||||
g_signal_global.fd.what = MUX_EV_READ;
|
||||
g_signal_global.fd.callback = uc_iomux_signal_cb;
|
||||
|
||||
rc = iomux_register_fd(mux, &g_signal_global.fd);
|
||||
if (rc == 0) {
|
||||
g_signal_global.initialized = 1;
|
||||
} else {
|
||||
uc_deinitailize_iomux_signals();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct IOMuxSignalEntry *uc_signal_entry_new(int signo,
|
||||
IOMuxSignalHandler handler,
|
||||
void *cookie)
|
||||
{
|
||||
struct IOMuxSignalEntry *entry;
|
||||
|
||||
entry = calloc(1, sizeof *entry);
|
||||
if (entry == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry->signo = signo;
|
||||
entry->callback = handler;
|
||||
entry->cookie = cookie;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
static int iomux_register_unixsignal_internal(struct IOMux *mux,
|
||||
int signo,
|
||||
IOMuxSignalHandler handler,
|
||||
void *cookie)
|
||||
{
|
||||
struct IOMuxSignalEntry *entry;
|
||||
int rc;
|
||||
struct sigaction sa;
|
||||
|
||||
if (!g_signal_global.initialized) {
|
||||
rc = uc_initailize_iomux_signals(mux);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
//Only one mux can handle signals
|
||||
if (g_signal_global.mux != mux) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
entry = uc_find_signal_entry(signo);
|
||||
if (entry != NULL) {
|
||||
return EEXIST;
|
||||
}
|
||||
|
||||
entry = uc_signal_entry_new(signo, handler, cookie);
|
||||
if (entry == NULL) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sa_handler = uc_global_signal_handler;
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigfillset(&sa.sa_mask);
|
||||
|
||||
rc = sigaction(signo, &sa, &entry->old_sa);
|
||||
if (rc == 0) {
|
||||
uc_tailq_insert_tail(&g_signal_global.head, &entry->entry);
|
||||
} else {
|
||||
rc = errno;
|
||||
free(entry);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int iomux_register_unixsignal(struct IOMux *mux,
|
||||
int signo,
|
||||
IOMuxSignalHandler handler,
|
||||
void *cookie)
|
||||
{
|
||||
int rc;
|
||||
|
||||
uc_signal_lock();
|
||||
|
||||
rc = iomux_register_unixsignal_internal(mux, signo, handler, cookie);
|
||||
|
||||
if (g_signal_global.initialized && uc_tailq_empty(&g_signal_global.head)) {
|
||||
//we might have initialized the iomux but otherwise failed
|
||||
uc_deinitailize_iomux_signals();
|
||||
}
|
||||
|
||||
uc_signal_unlock();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int iomux_unregister_unixsignal(struct IOMux *mux, int signo)
|
||||
{
|
||||
struct IOMuxSignalEntry *entry;
|
||||
int rc = ENOENT;
|
||||
|
||||
uc_signal_lock();
|
||||
|
||||
if (!g_signal_global.initialized || g_signal_global.mux != mux) {
|
||||
uc_signal_unlock();
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
|
||||
entry = uc_find_signal_entry(signo);
|
||||
if (entry != NULL) {
|
||||
uc_tailq_remove(&entry->entry);
|
||||
rc = sigaction(entry->signo, &entry->old_sa, NULL);
|
||||
assert(rc == 0);
|
||||
free(entry);
|
||||
}
|
||||
|
||||
if (uc_tailq_empty(&g_signal_global.head)) {
|
||||
uc_deinitailize_iomux_signals();
|
||||
}
|
||||
|
||||
uc_signal_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user