【问题标题】:How to catch SIGTERM in a multi threaded Linux program using signalfd如何使用 signalfd 在多线程 Linux 程序中捕获 SIGTERM
【发布时间】:2020-04-22 16:52:27
【问题描述】:

相关问题herehere

在 Linux GPLv3+ 项目 https://github.com/bstarynk/helpcovid/(多线程、C++17、Web 服务器应用程序)中提交 b616defc5e54ba869。需要能够优雅地终止这样的 Web 服务器(使用 libpqxx 与一些 PostGreSQL 数据库交互)。所以issue#35

我确实读过signal(7)signal-safety(7)signalfd(2)。我知道criticisms against signalfd

我在几个文件描述符上有一个单一的背景 C++ std::thread poll(2)-ing。请参见文件hcv_background.cc,其中hcv_start_background_thread 函数从文件hcv_main.cc 中的main 函数调用,并且创建的std::thread 运行hcv_background_thread_body 函数(一个执行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 从未被调用。

我做错了什么?

【问题讨论】:

  • “我做错了什么?” - 用 C++ 编写 Web 应用程序框架?这可能是一个有趣的智力练习,但如果您的目标是提供有用的东西,那么提供一些东西 是有价值的,这样您就可以学习和迭代它。选择许可证和所有这些都很棒,但它可以每秒处理零个请求,直到用户可以部署(并认为“部署到 Heroku”按钮而不是 Linux 系统管理员)并尝试一下!
  • 好吧,其他小伙伴不知道ocsigen,我不知道也不想学PHP或Python。有几个人建议用 C++ 编写 Web 应用程序。
  • 我并不是说这不可能,或者人们不想这样做(……十年前),只是如果你想做的实际上是帮助人们,那么 生产中的软件胜过无休止的研究项目。
  • 它既不是生产也不是研究项目。

标签: linux multithreading signals c++17


【解决方案1】:

以下代码:

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

应该是:

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

man signalfd:

通常,通过 文件描述符应使用sigprocmask(2)阻止,以防止 正在根据其默认处置处理的信号。

节目来源

...
/* 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。

【讨论】:

  • 谢谢。我在提交2ae9549245a 中尝试过这个,但它仍然不起作用
  • @BasileStarynkevitch 为您添加了一个工作示例。
  • @BasileStarynkevitch 我的代码确实使用了std::thread。您的代码在其他地方一定有错误。
  • @BasileStarynkevitch 在您的代码中发现另一个错误,已更新。
  • 非常感谢您的帮助
猜你喜欢
  • 2019-11-05
  • 1970-01-01
  • 2022-01-20
  • 2018-10-29
  • 1970-01-01
  • 2022-01-16
  • 1970-01-01
  • 2019-01-26
  • 1970-01-01
相关资源
最近更新 更多