如何使用 signalfd 在多线程 Linux 程序中捕获 SIGTERM

How to catch SIGTERM in a multi threaded Linux program using signalfd

相关问题here and here

在 Linux GPLv3+ 项目中 https://github.com/bstarynk/helpcovid/ (multi-threaded, C++17, web server application) commit b616defc5e54ba869. The need is to be able to terminate gracefully such a web server (interacting with some PostGreSQL database using libpqxx). So issue#35

我都读了signal(7) and signal-safety(7) and signalfd(2). And I am aware of criticisms against signalfd

我有一个单一的背景 C++ std::thread poll(2)-ing on several file descriptors. See file hcv_background.cc where the hcv_start_background_thread function is called from main function in file hcv_main.cc and the created std::thread runs the hcv_background_thread_body function (an event loop doing the poll(2).....)

所以 hcv_start_background_thread 有:

  {
    sigset_t  sigmaskbits;
    memset (&sigmaskbits, 0, sizeof(sigmaskbits));
    sigemptyset(&sigmaskbits);
    sigaddset(&sigmaskbits, SIGTERM);
    sigaddset(&sigmaskbits, SIGHUP);
    sigaddset(&sigmaskbits, SIGXCPU);
    sigaddset(&sigmaskbits, SIGPIPE);
    /// http://man7.org/linux/man-pages/man2/sigprocmask.2.html
    if (sigprocmask(SIG_UNBLOCK, &sigmaskbits, nullptr))
      HCV_FATALOUT("hcv_start_background_thread: sigprocmask failure");
    HCV_DEBUGOUT("hcv_start_background_thread sigprocmask done");
    hcv_bg_signal_fd = signalfd(-1, &sigmaskbits, SFD_NONBLOCK | SFD_CLOEXEC);
    if (hcv_bg_signal_fd < 0)
      HCV_FATALOUT("hcv_start_background_thread: signalfd failure");
    HCV_DEBUGOUT("hcv_start_background_thread hcv_bg_signal_fd=" << hcv_bg_signal_fd);
  }

并且hcv_background_thread_body函数有一个事件循环做

  while (!hcv_should_stop_bg_thread.load())
    {
      struct pollfd polltab[4];
      memset(&polltab, 0, sizeof(polltab));
      polltab[0].fd = hcv_bg_event_fd;
      polltab[0].events = POLL_IN;
      polltab[1].fd = hcv_bg_signal_fd;
      polltab[1].events = POLL_IN;
      polltab[1].fd = hcv_bg_timer_fd;
      polltab[1].events = POLL_IN;
      HCV_DEBUGOUT("hcv_background_thread_body before poll");
      int nbfd = poll(polltab, 3,
                      hcv_debugging.load()?(2*HCV_BACKGROUND_TICK_TIMEOUT):HCV_BACKGROUND_TICK_TIMEOUT);

之后在同一事件循环中

 if (nbfd>0)   /* some file descriptor is readable */
        {
          HCV_DEBUGOUT("hcv_background_thread_body: after poll nbfd:" << nbfd);
          if ((polltab[0].revents & POLL_IN) && polltab[0].fd == hcv_bg_event_fd)
            {
              int64_t evrk=0;

              HCV_DEBUGOUT("hcv_background_thread_body pollable hcv_bg_event_fd="
                           << hcv_bg_event_fd);
              int byrd = read (hcv_bg_event_fd, &evrk, sizeof(evrk));
              if (byrd==sizeof(evrk))
                {
                  HCV_DEBUGOUT("hcv_background_thread_body: got " << evrk
                               << " from hcv_bg_event_fd=" << hcv_bg_event_fd);
                  hcv_bg_do_event(evrk);
                }
              else
                HCV_SYSLOGOUT(LOG_WARNING,
                              "hcv_background_thread_body read hcv_bg_event_fd#" <<hcv_bg_event_fd << " failed, byrd=" << byrd);
            };
          if ((polltab[1].revents & POLL_IN) && polltab[1].fd == hcv_bg_signal_fd)
            {
              HCV_DEBUGOUT("hcv_background_thread_body pollable hcv_bg_signal_fd="
                           << hcv_bg_signal_fd);
              struct signalfd_siginfo signalinfo;
              memset (&signalinfo, 0, sizeof(signalinfo));
              int byrd = read(hcv_bg_signal_fd, &signalinfo, sizeof(signalinfo));
              if (byrd < 0)
                HCV_FATALOUT("hcv_background_thread_body: failed read of hcv_bg_signal_fd="
                             << hcv_bg_signal_fd);
              else if (byrd != sizeof(signalinfo))
                // should never happen... see signalfd(2)
                HCV_FATALOUT("hcv_background_thread_body: corrupted read of hcv_bg_signal_fd="
                             << hcv_bg_signal_fd << ", byrd=" << byrd);
              HCV_DEBUGOUT("hcv_background_thread_body: got signalinfo #" << signalinfo.ssi_signo
                           << " from hcv_bg_signal_fd=" << hcv_bg_signal_fd);
              if (signalinfo.ssi_signo == SIGTERM)
                {
                  HCV_SYSLOGOUT(LOG_NOTICE, "hcv_background_thread_body got SIGTERM at "
                                << (hcv_monotonic_real_time() - hcv_monotonic_start_time)
                                << " elapsed seconds");
                  hcv_process_SIGTERM_signal();
                  hcv_should_stop_bg_thread.store (true);
                }

但是 hcv_process_SIGTERM_signal 从来没有接到电话。

我做错了什么?

以下代码:

if (sigprocmask(SIG_UNBLOCK, &sigmaskbits, nullptr))

应该是:

if (sigprocmask(SIG_BLOCK, &sigmaskbits, nullptr))

man signalfd:

Normally, the set of signals to be received via the file descriptor should be blocked using sigprocmask(2), to prevent the signals being handled according to their default dispositions.

Program source

...
/* Block signals so that they aren't handled
   according to their default dispositions */
   if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
       handle_error("sigprocmask");
...

工作示例:

#include <iostream>
#include <thread>

#include <sys/signalfd.h>
#include <signal.h>
#include <poll.h>
#include <unistd.h>

void another_thread(int sfd) {
    pollfd fds[2];
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    fds[1].fd = sfd;
    fds[1].events = POLLIN;

    for(;;) {
        int n = poll(fds, 2, -1);
        if(n <= 0)
            throw;
        if(fds[0].revents & POLLIN) {
            char buf[1024];
            ssize_t r = read(fds[0].fd, buf, sizeof buf);
            if(r > 0)
                std::cout << "Read " << r << " bytes\n";
            else if(!r) {
                std::cout << "Read EOF\n";
                break;
            }
            else
                throw;
        }
        if(fds[1].revents & POLLIN) {
            signalfd_siginfo s;
            ssize_t r = read(fds[1].fd, &s, sizeof s);
            if(r != sizeof s)
                throw;
            std::cout << "Received signal " << s.ssi_signo << '\n';
            break;
        }
    }
}

int main() {
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGQUIT);
    if(sigprocmask(SIG_BLOCK, &mask, nullptr) == -1)
        throw;

    int sfd = signalfd(-1, &mask, 0);
    if(sfd == -1)
        throw;

    std::thread(another_thread, sfd).join();

    std::cout << "Terminated successfully\n";
}

输出:

[max@supernova:~/src/test] $ ./test 
  C-c C-cReceived signal 2
Terminated successfully

您的代码中的另一个错误是:

      polltab[0].events = POLL_IN;
      polltab[1].fd = hcv_bg_signal_fd;
      polltab[1].events = POLL_IN;
      polltab[1].fd = hcv_bg_timer_fd;
      polltab[1].events = POLL_IN;

hcv_bg_timer_fd 应该使用索引 2。