Mercurial > dovecot > original-hg > dovecot-1.2
view src/lib/lib-signals.c @ 9236:a878a0897eb9 HEAD
lib-signals: Redesigned how delayed signals are handled.
Fixes signal handler hanging infinitely in write() when many signals were
sent rapidly.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 21 Jul 2009 17:14:41 -0400 |
parents | 5040d7cb1335 |
children | cc4af8e31175 |
line wrap: on
line source
/* Copyright (c) 2001-2009 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "fd-close-on-exec.h" #include "lib-signals.h" #include <signal.h> #include <unistd.h> #define MAX_SIGNAL_VALUE 31 struct signal_handler { signal_handler_t *handler; void *context; bool delayed; struct signal_handler *next; }; /* Remember that these are accessed inside signal handler which may be called even while we're initializing/deinitializing. Try hard to keep everything in consistent state. */ static struct signal_handler *signal_handlers[MAX_SIGNAL_VALUE+1] = { NULL, }; static int sig_pipe_fd[2] = { -1, -1 }; static bool signals_initialized = FALSE; static struct io *io_sig = NULL; static siginfo_t pending_signals[MAX_SIGNAL_VALUE+1]; static bool have_pending_signals = FALSE; const char *lib_signal_code_to_str(int signo, int sicode) { /* common */ switch (sicode) { case SI_USER: return "kill"; #ifdef SI_KERNEL case SI_KERNEL: return "kernel"; #endif case SI_TIMER: return "timer"; } /* If SEGV_MAPERR is supported, the rest of them must be too. FreeBSD 6 at least doesn't support these. */ #ifdef SEGV_MAPERR switch (signo) { case SIGSEGV: switch (sicode) { case SEGV_MAPERR: return "address not mapped"; case SEGV_ACCERR: return "invalid permissions"; } break; case SIGBUS: switch (sicode) { case BUS_ADRALN: return "invalid address alignment"; case BUS_ADRERR: return "nonexistent physical address"; case BUS_OBJERR: return "object-specific hardware error"; } } #endif return t_strdup_printf("unknown %d", sicode); } static void sig_handler(int signo, siginfo_t *si, void *context ATTR_UNUSED) { struct signal_handler *h; char c = 0; if (signo < 0 || signo > MAX_SIGNAL_VALUE) return; /* remember that we're inside a signal handler which might have been called at any time. don't do anything that's unsafe. */ for (h = signal_handlers[signo]; h != NULL; h = h->next) { if (!h->delayed) h->handler(si, h->context); else if (pending_signals[signo].si_signo == 0) { pending_signals[signo] = *si; if (!have_pending_signals) { int saved_errno = errno; if (write(sig_pipe_fd[1], &c, 1) != 1) i_error("write(sigpipe) failed: %m"); have_pending_signals = TRUE; errno = saved_errno; } } } } static void sig_ignore(int signo ATTR_UNUSED, siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) { /* if we used SIG_IGN instead of this function, the system call might be restarted */ } static void signal_read(void *context ATTR_UNUSED) { siginfo_t signals[MAX_SIGNAL_VALUE+1]; sigset_t fullset, oldset; struct signal_handler *h; char buf[2]; int signo; ssize_t ret; if (sigfillset(&fullset) < 0) i_fatal("sigfillset() failed: %m"); if (sigprocmask(SIG_BLOCK, &fullset, &oldset) < 0) i_fatal("sigprocmask() failed: %m"); ret = read(sig_pipe_fd[0], buf, sizeof(buf)); i_assert(ret <= 1); if (ret > 0) { memcpy(signals, pending_signals, sizeof(signals)); memset(pending_signals, 0, sizeof(pending_signals)); have_pending_signals = FALSE; } else if (ret < 0) { if (errno != EAGAIN) i_fatal("read(sigpipe) failed: %m"); } else { i_fatal("read(sigpipe) failed: EOF"); } if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) i_fatal("sigprocmask() failed: %m"); if (ret < 0) return; /* call the delayed handlers after signals are copied and unblocked */ for (signo = 0; signo < MAX_SIGNAL_VALUE; signo++) { if (signals[signo].si_signo == 0) continue; for (h = signal_handlers[signo]; h != NULL; h = h->next) { if (h->delayed) h->handler(&signals[signo], h->context); } } } static void lib_signals_set(int signo, bool ignore) { struct sigaction act; if (sigemptyset(&act.sa_mask) < 0) i_fatal("sigemptyset(): %m"); act.sa_flags = SA_SIGINFO; act.sa_sigaction = ignore ? sig_ignore : sig_handler; if (sigaction(signo, &act, NULL) < 0) i_fatal("sigaction(%d): %m", signo); } void lib_signals_set_handler(int signo, bool delayed, signal_handler_t *handler, void *context) { struct signal_handler *h; i_assert(handler != NULL); if (signo < 0 || signo > MAX_SIGNAL_VALUE) { i_panic("Trying to set signal %d handler, but max is %d", signo, MAX_SIGNAL_VALUE); } if (signal_handlers[signo] == NULL && signals_initialized) lib_signals_set(signo, FALSE); if (delayed && sig_pipe_fd[0] == -1) { /* first delayed handler */ if (pipe(sig_pipe_fd) < 0) i_fatal("pipe() failed: %m"); fd_close_on_exec(sig_pipe_fd[0], TRUE); fd_close_on_exec(sig_pipe_fd[1], TRUE); if (signals_initialized) { io_sig = io_add(sig_pipe_fd[0], IO_READ, signal_read, NULL); } } h = i_new(struct signal_handler, 1); h->handler = handler; h->context = context; h->delayed = delayed; /* atomically set to signal_handlers[] list */ h->next = signal_handlers[signo]; signal_handlers[signo] = h; } void lib_signals_ignore(int signo, bool restart_syscalls) { struct sigaction act; if (signo < 0 || signo > MAX_SIGNAL_VALUE) { i_panic("Trying to ignore signal %d, but max is %d", signo, MAX_SIGNAL_VALUE); } i_assert(signal_handlers[signo] == NULL); if (sigemptyset(&act.sa_mask) < 0) i_fatal("sigemptyset(): %m"); if (restart_syscalls) { act.sa_flags = SA_RESTART; act.sa_handler = SIG_IGN; } else { act.sa_flags = SA_SIGINFO; act.sa_sigaction = sig_ignore; } if (sigaction(signo, &act, NULL) < 0) i_fatal("sigaction(%d): %m", signo); } void lib_signals_unset_handler(int signo, signal_handler_t *handler, void *context) { struct signal_handler *h, **p; for (p = &signal_handlers[signo]; *p != NULL; p = &(*p)->next) { if ((*p)->handler == handler && (*p)->context == context) { h = *p; *p = h->next; i_free(h); return; } } i_panic("lib_signals_unset_handler(%d, %p, %p): handler not found", signo, (void *)handler, context); } void lib_signals_reset_ioloop(void) { if (io_sig != NULL) { io_remove(&io_sig); io_sig = io_add(sig_pipe_fd[0], IO_READ, signal_read, NULL); } } void lib_signals_init(void) { int i; signals_initialized = TRUE; /* add signals that were already registered */ for (i = 0; i < MAX_SIGNAL_VALUE; i++) { if (signal_handlers[i] != NULL) lib_signals_set(i, FALSE); } if (sig_pipe_fd[0] != -1) io_sig = io_add(sig_pipe_fd[0], IO_READ, signal_read, NULL); } void lib_signals_deinit(void) { struct signal_handler *handlers, *h; int i; for (i = 0; i < MAX_SIGNAL_VALUE; i++) { if (signal_handlers[i] != NULL) { /* atomically remove from signal_handlers[] list */ handlers = signal_handlers[i]; signal_handlers[i] = NULL; while (handlers != NULL) { h = handlers; handlers = h->next; i_free(h); } } } if (io_sig != NULL) io_remove(&io_sig); if (sig_pipe_fd[0] != -1) { if (close(sig_pipe_fd[0]) < 0) i_error("close(sigpipe) failed: %m"); if (close(sig_pipe_fd[1]) < 0) i_error("close(sigpipe) failed: %m"); } }