0

Related question here and here.

In the Linux GPLv3+ project 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.

I did read both signal(7) and signal-safety(7) and signalfd(2). And I am aware of criticisms against signalfd.

I have a single background 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)....)

So hcv_start_background_thread has:

  {
    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);
  }

and the hcv_background_thread_body function has an event loop doing

  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);

and later in the same event loop

 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);
                }

But hcv_process_SIGTERM_signal never got called.

What am I doing wrong?

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • *"What am I doing wrong?"* - writing a web app framework in C++? It might be an interesting intellectual exercise, but if your goal is to deliver something useful then there's value to *delivering something* so you can learn and iterate on it. Its great to choose a license and all, but it can handle zero requests per second until users can deploy (and think "Deploy to Heroku" button rather than Linux sysadmin) and try it! – jonrsharpe Apr 22 '20 at 17:07
  • Well, other partners don't know [ocsigen](https://ocsigen.org/) and I don't know and don't want to learn PHP or Python. There are several people suggesting writing a web application in C++. – Basile Starynkevitch Apr 22 '20 at 17:15
  • Also, see https://stackoverflow.com/q/2055175/841108 – Basile Starynkevitch Apr 22 '20 at 17:28
  • I'm not saying it's not possible, or that people don't want to do it (...ten years ago), just that if what you want to do is actually help people then *software in production* is better than an endless research project. – jonrsharpe Apr 22 '20 at 17:40
  • It is neither in production nor a research project. – Basile Starynkevitch Apr 22 '20 at 19:41
  • It's OK, there's nothing wrong with learning for its own sake, but be realistic about the value you're delivering. If you want to have an impact you may want to expand your network to people who can actually ship software products to end users, not just modern dev skills but design and product too. – jonrsharpe Apr 22 '20 at 20:44
  • Post a [minimal, complete, and verifiable example](https://stackoverflow.com/help/mcve). – Maxim Egorushkin Apr 23 '20 at 07:58
  • @MaximEgorushkin: I don't have any MCVE (and I don't have neither the time nor the motivation to make one). I do have however a reproducible test case. I am considering using techniques inspired by [calling Qt functions from unix signal handlers](https://doc.qt.io/qt-5/unix-signals.html) – Basile Starynkevitch Apr 23 '20 at 08:00
  • @BasileStarynkevitch Your code have errors elsewhere, `singnalfd` and `poll` work as expected. – Maxim Egorushkin Apr 23 '20 at 08:10
  • Yes, thanks.... Any debugging hints to debug a multi-threaded program? – Basile Starynkevitch Apr 23 '20 at 08:21

1 Answers1

1

The following code:

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

Should be:

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");
...

Working example:

#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";
}

Output:

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

Another error in your code is:

      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 should probably use index 2.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271