HNHacker News
TopNewBestAskShowJobs

tlsalmin

17 karma · joined February 17, 2022

submissionscomments
tlsalmin··on Way too many ways to wait on a child process with a timeout
First nitpick:

   static int pipe_fd[2] = {0};
0 is valid fd, so I recommend initializing fds to -1.

signalfd was just off-hand mentioned, but for writing anything larger, like lets say a daemon process, it keeps things close to all the other events being reacted to. E.g.

  #include <signal.h>
  #include <unistd.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <sys/timerfd.h>
  #include <sys/signalfd.h>
  #include <sys/epoll.h>

  static int signalfd_init(void)
    {
      sigset_t sigs, oldsigs;
      int sfd = -1;

      sigemptyset(&sigs);
      sigemptyset(&oldsigs);
      sigaddset(&sigs, SIGCHLD);
      if (!sigprocmask(SIG_BLOCK, &sigs, &oldsigs))
        {
          sfd = signalfd(-1, &sigs, SFD_CLOEXEC | SFD_NONBLOCK);
          if (sfd != -1)
            {
              // Success
              return sfd;
            }
          else
            {
              perror("signalfd");
            }
          sigprocmask(SIG_SETMASK, &oldsigs, NULL);
        }
      else
        {
          perror("sigprocmask");
        }
      return -1;
    }

  static int timerfd_init(void)
    {
      int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);

      if (tfd != -1)
        {
          struct itimerspec tv =
            {
              .it_value = 
                {
                  .tv_sec = 5
                }
            };
          if (!timerfd_settime(tfd, 0, &tv, NULL))
            {
              return tfd;
            }
          else
            {
              perror("timerfd_settime");
            }
          close(tfd);
        }
      else
        {
          perror("timerfd_create");
        }
      return -1;
    }

  static int epoll_init(int sfd, int tfd)
    {
      int efd;

      if (!sfd || !tfd)
        {
          return -1;
        }

      efd = epoll_create1(EPOLL_CLOEXEC);
      if (efd != -1)
        {
          struct epoll_event ev[2] =
            {
                {
                  .events = EPOLLIN,
                  .data =
                    {
                      .fd = sfd,
                    }
                },
                {
                  .events = EPOLLIN,
                  .data = 
                    {
                      .fd = tfd
                    }
                }
            };
          if (!epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &ev[0]) &&
              !epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev[1]))
            {
              return efd;
            }
          else
            {
              perror("epoll_ctl");
            }
          close(efd);
        }
      else
        {
          perror("epoll_create1");
        }
      return -1;
    }

  int main(int argc, char *argv[])
    {
      int exit_value = EXIT_FAILURE;
      int sfd = signalfd_init(),
          tfd = timerfd_init(),
          efd = epoll_init(sfd, tfd);

      if (sfd != -1 && tfd != -1 && efd != -1)
        {
          int child_pid = fork();

          if (child_pid != -1)
            {
              if (!child_pid)
                {
                  argv += 1;
                  if (-1 == execvp(argv[0], argv)) {
                      exit(EXIT_FAILURE);
                  }
                  __builtin_unreachable();
                }
              else
                {
                  int err;
                  struct epoll_event ev;

                  while ((err = epoll_wait(efd, &ev, 1, -1)) > 0)
                    {
                      if (ev.data.fd == tfd)
                        {
                          // Read the signalfd for the possible SIGCHLD and
                          exit_value = EXIT_SUCCESS;
                        }
                      else if (ev.data.fd == tfd)
                        {
                          // Timer triggered, kill the child process.
                        }
                    }
                  if (err == -1)
                    {
                      perror("epoll_wait");
                    }
                }
            }
          else
            {
              perror("fork");
            }
        }
      close(sfd);
      close(tfd);
      close(efd);
      exit(exit_value);
    }
tlsalmin··on Linux: What Can You Epoll?
This is getting into taste territory but sure just getting the signal in epoll_pwait removes the need for FD reading. But introduces the need for the thread local global context and the need to handle EINTR from epoll right? I can see how checking if signals were called in the EINTR-branch is nice though.

Anyway nice to know the p-version use case.

tlsalmin··on Linux: What Can You Epoll?
I have to disagree here. Not recommending signalfd for the mentioned use cases might be reasonable, just as reasonable as it is to use threads for a specific use case. For a single threaded non-blocking-FD using client/server signalfd removes the risk of doing too much in the signal handler and brings signals nicely into the event loop. This just happens to be 99% of the functionality I have to do.

I'd only use more than one signalfd if each signalfd only catches a specific signal. E.g. main context handles Sigterm and a background process library handles sigchld.

tlsalmin··on Linux: What Can You Epoll?
Just skimmed through the article, since I'm just here to testify that the most important revelation for me on writing APIs was that you can put and epoll_fd in an epoll_fd. This allows the API to have e.g. a single epoll_fd that contains all outbound connections, timers, signalfds and inotifys mentioned in the articled. Then the e.g. daemon using the APIs can have an epoll_fd per library it is using and just be sitting in the epoll_wait loop ready to fire library_x_process() call when events arrive.
tlsalmin··on My smart home 2021: A Home Assistant love story
This is why I opted for Shelly on my house build. The dimmers keep dimming and everything works regularly without a hub/HA/Wifi. Leaving the modules inside the light switches. No need for special bulbs and having switches with springs allows any light to be dimmed as long as the fixture/bulb supports it.