* @{
*/
-/** global state when logger is not interrupted by any handled signals */
-static volatile sig_atomic_t g_logger_run = 1;
-
static const int DEFAULT_EPOLL_TIME_MS = 1000;
static const int DEFAULT_LAZY_POLLING_TOTAL_MS = 0;
#ifndef UNIT_TEST
static
#endif
-int logger_create(struct logger_config_data *data, struct logger *l)
+int logger_create(struct logger_config_data *data, struct logger *l, struct signal_markup *sigmar)
{
memset(l, 0, sizeof *l);
l->epoll_socket.fd = -1;
logger_add_writer(l, wr);
}
- return 0;
-}
-
-/**
- * @brief Handle interrupting/terminating signals
- * @details Clears global flag to stop main loop
- * @param[in] signo signal number
- */
-static void handle_signals(int signo)
-{
- (void) signo;
- g_logger_run = 0;
-}
+ add_fd_entity(&l->epoll_socket, &sigmar->wakeup_fde);
+ add_fd_entity(&l->epoll_common, &sigmar->wakeup_fde);
-void setup_signals(struct logger *server)
-{
- struct sigaction action = {
- .sa_handler = handle_signals,
- .sa_flags = 0
- };
- sigemptyset(&action.sa_mask);
+ l->sigmar = sigmar;
- static const int handled_signals[] = { SIGINT, SIGTERM };
- for (unsigned u = 0; u < NELEMS(handled_signals); ++u)
- sigaction(handled_signals[u], &action, NULL);
+ return 0;
}
static bool do_logger_one_iteration(struct logger *server, bool *use_lazy_polling)
#endif
int do_logger(struct logger *server)
{
- setup_signals(server);
-
/* Note, negative values are applied here, but not in the check in
* the inner function. This leaves lazy polling enabled forever
* and is a feature. */
bool use_lazy_polling = (g_backend.lazy_polling_total != 0);
- while (g_logger_run)
+ while (!server->sigmar->exit_signal_received)
if (!do_logger_one_iteration(server, &use_lazy_polling))
break;
}
data->epoll_time = log_config_get_int(&conf, "epoll_time_ms", DEFAULT_EPOLL_TIME_MS);
+
+ /* See the comments near `epoll_wait` in `logger_internal.c`,
+ * the tl;dr is trouble with buffering and signals. */
+ if (data->epoll_time < 0)
+ data->epoll_time = DEFAULT_EPOLL_TIME_MS;
+
g_backend.lazy_polling_total = 0; // Android Logger backend only, read below
struct qos_module qos = {0, };
g_backend.lazy_polling_sleep = log_config_get_int(&conf, "lazy_polling_sleep_ms", DEFAULT_LAZY_POLLING_SLEEP_MS);
/* The total can be 0 (no lazy polling) or negative (infinite),
- * but the sleep length has to be positive. */
+ * but the sleep length has to be positive. An infinite sleep
+ * not only has a potential to oversleep but also causes issues
+ * with the signal wakeup mechanism. A zero-length sleep would
+ * not let the mode ever end because of our naive timekeeping. */
if (g_backend.lazy_polling_sleep < 1)
g_backend.lazy_polling_sleep = 1;
if (!reader)
continue;
- assert(reader->common.subs != NULL);
return false;
}
return logger->writers == NULL;
}
+static void *signal_thread(void *userdata)
+{
+ struct signal_markup *const sigmar = (struct signal_markup *)userdata;
+
+ int signum;
+ sigwait(&sigmar->sigset, &signum);
+ sigmar->exit_signal_received = true;
+
+ /* Other threads are most likely sleeping on epoll. They're in for
+ * a rude awakening. Now you know why you fear the night. */
+ int r = write(sigmar->wakeup_pipe[1], "wake up, time to die", 1);
+ if (r < 0) {
+ /* This can't realistically fail, but even if somehow
+ * it does, it's not a problem because threads will
+ * wake up regardless after their timeouts complete,
+ * it will just take a moment longer than usual. */
+ }
+
+ return NULL;
+}
+
+static void null_dispatch(struct logger *server, struct epoll_event *event, void *userdata)
+{
+ /* This function handles the wake-up pipe, whose data
+ * is not supposed to be read, just to be acknowledged.
+ * Consuming would prevent other threads from seeing it. */
+}
+
+int setup_signals(struct signal_markup *sigmar)
+{
+ sigmar->exit_signal_received = false;
+
+ /* Broken pipes are somewhat normal, that's what happens when
+ * a client quits (for example a dlogutil instance is manually
+ * closed via Ctrl+C). */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* The pipe is going to be used to notify other threads about the
+ * signal, since that is the only way to interrupt a poll call. */
+ int r = pipe(sigmar->wakeup_pipe);
+ if (r < 0)
+ return r;
+ init_fd_entity(&sigmar->wakeup_fde, null_dispatch, NULL);
+ set_read_fd_entity(&sigmar->wakeup_fde, sigmar->wakeup_pipe[0]);
+
+ /* Setup a dedicated signal handling thread to simplify things
+ * and avoid having to handle it potentially everywhere else. */
+ sigemptyset(&sigmar->sigset);
+ sigaddset(&sigmar->sigset, SIGINT);
+ sigaddset(&sigmar->sigset, SIGTERM);
+ r = pthread_sigmask(SIG_BLOCK, &sigmar->sigset, NULL);
+ if (r < 0)
+ goto cleanup;
+ r = pthread_create(&sigmar->thread, NULL, signal_thread, sigmar);
+ if (r < 0)
+ goto cleanup;
+
+ return 0;
+
+cleanup:
+ close(sigmar->wakeup_pipe[0]);
+ close(sigmar->wakeup_pipe[1]);
+ return r;
+}
+
/**
* @brief The logger
* @return 0 on success, nonzero on failure
*/
int main(int argc, char **argv)
{
- int r, ret;
-
- signal(SIGPIPE, SIG_IGN);
+ struct signal_markup sigmar;
+ int r = setup_signals(&sigmar);
+ if (r < 0) {
+ errno = -r;
+ printf("Unable to setup signals (%m). Exiting.\n");
+ return DLOG_EXIT_ERR_RUNTIME;
+ }
r = reset_self_privileges();
if (r < 0) {
precreate_logctl_file(&data);
+ int ret;
struct logger server;
- if ((r = logger_create(&data, &server)) < 0) {
+ if ((r = logger_create(&data, &server, &sigmar)) < 0) {
errno = -r;
printf("Unable to initialize logger with provided configuration (%m). Exiting.\n");
ret = DLOG_EXIT_ERR_CONFIG;
goto cleanup;
}
+ pthread_join(sigmar.thread, NULL);
+
ret = DLOG_EXIT_SUCCESS;
cleanup: