homer512 icon

Defer signal

homer512 | PRO | 11/07/15 05:58:21 PM UTC | 0 ⭐ | 720 👁️ | Never ⏰ | []
C++ |

7.64 KB

|

None

|

0 👍

/

0 👎

/* Copyright 2015 Florian Philipp
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
#include <signal.h>
// using sigaction, pthread_sigmask
 
#include <arpa/inet.h>
// using htons
#include <netinet/ip.h>
// using accept, bind, listen, socket
 
#include <unistd.h>
// using close
 
#include <atomic>
// using std::atomic_signal_fence
#include <cstring>
// using std::memset
#include <system_error>
// using std::system_error, std::system_category
#include <cerrno>
// using errno
#include <iostream>
// using std::cout
#include <array>
// using std::array
#include <cassert>
// using assert
 
 
namespace {
 
  /**
   * Throws an std::system_error using errno
   */
  void make_system_error(const char* operation)
  {
    throw std::system_error(errno, std::system_category(), operation);
  }
 
  /**
   * RAII for an installed signal handler. Dtor restores old behavior
   */
  class AlteredSignal
  {
    int signal;
    struct sigaction orig;
  public:
    typedef void (*signal_handler_t)(int);
 
    AlteredSignal() noexcept : signal(-1) {}
    AlteredSignal(int signal, signal_handler_t handler)
      : signal(signal)
    {
      struct sigaction action;
      std::memset(&action, 0, sizeof(action));
      action.sa_handler = handler;
      if(sigaction(signal, &action, &orig))
    make_system_error("sigaction");
    }
    AlteredSignal(const AlteredSignal&) = delete;
    AlteredSignal(AlteredSignal&& o) noexcept
    {
      signal = o.signal;
      o.signal = -1;
      orig = o.orig;
    }
    ~AlteredSignal()
    {
      if(signal >= 0)
    sigaction(signal, &orig, nullptr);
    }
    AlteredSignal& operator=(const AlteredSignal&) = delete;
    AlteredSignal& operator=(AlteredSignal&& o)
    {
      if(signal >= 0 && sigaction(signal, &orig, nullptr))
    make_system_error("sigaction");
      signal = o.signal;
      if(o.signal >= 0) {
    orig = o.orig;
    o.signal = -1;
      }
      return *this;
    }
  };
 
  /**
   * RAII for blocked signals. Dtor unblocks signals again
   */
  class SignalBlock
  {
    sigset_t orig;
    bool active;
  public:
    SignalBlock() noexcept : orig(), active(false) {}
    explicit SignalBlock(const sigset_t& mask)
      : orig(), active(true)
    {
      if(pthread_sigmask(SIG_BLOCK, &mask, &orig))
    make_system_error("pthread_sigmask");
    }
    SignalBlock(const SignalBlock&) = delete;
    SignalBlock(SignalBlock&& o) noexcept
      : orig(o.orig), active(o.active)
    {
      o.active = false;
    }
    ~SignalBlock()
    {
      if(active)
    pthread_sigmask(SIG_SETMASK, &orig, nullptr);
    }
    SignalBlock& operator=(const SignalBlock&) = delete;
    SignalBlock& operator=(SignalBlock&& o)
    {
      if(active && pthread_sigmask(SIG_SETMASK, &orig, nullptr))
    make_system_error("pthread_sigmask");
      active = o.active;
      if(o.active) {
    orig = o.orig;
    o.active = false;
      }
      return *this;
    }
    /**
     * Signal mask that can be used to unblock signals in pselect or ppoll
     */
    const sigset_t& unmask_set() const noexcept
    { return orig; }
  };
 
  /**
   * Installs a signal handler that sets a flag on SIGINT and SIGTERM
   */
  class TerminationFlag
  {
    static bool termflag;
    static const std::array<int, 2> caught_signals;
 
    std::array<AlteredSignal, 2> altered;
 
    /**
     * Signal handler
     */
    static void term_action(int) noexcept
    {
      termflag = true;
      std::atomic_signal_fence(std::memory_order_release);
    }
  public:
    TerminationFlag()
    {
      for(std::size_t i = 0; i < caught_signals.size(); ++i)
    altered[i] = AlteredSignal(caught_signals[i], term_action);
    }
 
    /**
     * Blocks SIGTERM and SIGINT until the returned object is destroyed
     */
    SignalBlock block()
    {
      sigset_t mask;
      if(sigemptyset(&mask))
    make_system_error("sigemptyset");
      for(int sig: caught_signals)
    if(sigaddset(&mask, sig))
      make_system_error("sigaddset");
      return SignalBlock(mask);
    }
 
    /**
     * True if SIGTERM or SIGINT have been received
     */
    bool triggered() const noexcept
    {
      bool val = termflag;
      std::atomic_signal_fence(std::memory_order_acquire);
      return val;
    }
  };
 
  bool TerminationFlag::termflag;
  const std::array<int, 2> TerminationFlag::caught_signals = {
    SIGTERM, SIGINT
  };
  
  /**
   * RAII for a socket or any kind of file descriptor
   */
  struct Socket
  {
    int fd;
    explicit Socket(int fd = -1) noexcept
      : fd(fd) {}
    ~Socket()
    {
      if(fd >= 0)
    close(fd);
    }
    Socket(const Socket&) = delete;
    Socket(Socket&& o) noexcept
      : fd(o.fd)
    {
      o.fd = -1;
    }
    explicit operator bool() const noexcept
    { return fd >= 0; }
    Socket& operator=(const Socket&) = delete;
    Socket& operator=(Socket&& o)
    {
      using std::swap;
      swap(fd, o.fd);
      return *this;
    }
  };
 
  /**
   * Listens on 127.0.0.1:1337 for connections
   */
  class Listener
  {
    Socket sock;
  public:
    Listener()
    {
      if((sock.fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    make_system_error("socket");
      struct sockaddr_in addr;
      std::memset(&addr, 0, sizeof(addr));
      addr.sin_family = AF_INET;
      addr.sin_port = htons(1337);
      addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
      if(bind(sock.fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)))
    make_system_error("bind");
      if(listen(sock.fd, 128))
    make_system_error("listen");
    }
 
    /**
     * Blocking call to accept a new connection
     */
    Socket accept()
    {
      int fd;
      if((fd = ::accept(sock.fd, nullptr, 0)) < 0)
    make_system_error("accept");
      return Socket(fd);
    }
 
    /**
     * Blocking, interruptible call to accept a new connection
     *
     * \param sigmask signal mask for pselect
     */
    Socket poll(const sigset_t& sigmask)
    {
      fd_set read_fds;
      FD_ZERO(&read_fds);
      FD_SET(sock.fd, &read_fds);
      int n_fds;
      if((n_fds = pselect(sock.fd + 1, &read_fds, nullptr /*write*/,
              nullptr /*except*/, nullptr /*timeout*/, &sigmask))
     < 0)
    make_system_error("pselect");
      assert(n_fds == 1);
      assert(FD_ISSET(sock.fd, &read_fds));
      return accept();
    }
  };
 
  /**
   * TODO: Actually process the client.
   */
  void handle_client(Socket)
  {}
}
 
int main()
{
  TerminationFlag termination;
  Listener listener;
  while(1) {
    Socket sock;
    {
      SignalBlock block = termination.block();
      /* important: Block signals before checking them
       * to avoid race condition
       */
      if(termination.triggered())
    break;
      try {
    /* Now use pselect or ppoll to wait for a client with unmasked signals.
     * A signal will cause an EINTR. For simplicity, we check this in the
     * next iteration
     */
    sock = listener.poll(block.unmask_set());
      } catch(std::system_error& err) {
    if(err.code().value() == EINTR)
      continue;
    throw;
      }
    }
    handle_client(std::move(sock));
  }
  std::cout << "Clean termination\n";
}

Comments