Exercise: Graceful Termination Via SIGTERM/SIGINT#

Exercise#

Our database application still does not commit the database when terminated like processes are usually terminated,

../../../../../../../_images/eventloop-idea.svg
  • Ctrl-C from the terminal, when run in foreground (SIGINT is delivered)

  • kill <PID> from another process; the shell, usually (SIGTERM is delivered)

Add to the application another InputHandler implementation (lets give it the name GracefulTerminator). That implementation uses a signalfd instance to request SIGINT and SIGTERM delivery via a file descriptor.

Add a method GracefulTerminator::hookup(Eventloop&) which does everything that is necessary to make the event loop aware of the file descriptor. See class StdinToDatabase and class UDPToDatabase for how such a hookup() method is usually implemented

Solution#

#pragma once

#include "eventloop.h"

class GracefulTerminator : public InputHandler
{
public:
    GracefulTerminator();
    ~GracefulTerminator() override;

    void hookup(Eventloop&);
    EventAction ready(int fd) override;

private:
    int _fd;
};
#include "graceful-term.h"

#include <format>
#include <stdexcept>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <sys/signalfd.h>

GracefulTerminator::GracefulTerminator()
{
    sigset_t sigmask;
    sigemptyset(&sigmask);
    sigaddset(&sigmask, SIGTERM);
    sigaddset(&sigmask, SIGINT);

    _fd = signalfd(-1, &sigmask, 0);
    if (_fd == -1)
        throw std::runtime_error(std::format("signalfd: {} ({})", strerror(errno), errno));

    int rv = sigprocmask(SIG_BLOCK, &sigmask, nullptr);
    if (rv == -1) {
        close(_fd);
        _fd = -1;
        throw std::runtime_error(std::format("sigprocmask: {} ({})", strerror(errno), errno));
    }
}

GracefulTerminator::~GracefulTerminator()
{
    if (_fd == -1)
        close(_fd);
}

void GracefulTerminator::hookup(Eventloop& loop)
{
    loop.register_input(_fd, this);
}

EventAction GracefulTerminator::ready(int fd)
{
    assert(fd == _fd);

    signalfd_siginfo signals[2];                       // <-- SIGTERM, SIGINT
    ssize_t nread = read(fd, signals, sizeof(signals));
    if (nread == -1)
        throw std::runtime_error(std::format("read: {} ({})", strerror(errno), errno));

    for (size_t i=0; i < nread / sizeof(signalfd_siginfo); i++)
        switch (signals[i].ssi_signo) {
            case SIGTERM:
            case SIGINT:
                return EventAction::Quit;              // <-- what if the other signal is pending?
            default:
                assert(!"receiving signal that I did not request");
                break;
        }

    return EventAction::Continue;
}
#include "database.h"
#include "eventloop.h"
#include "udp-db.h"
#include "stdin-db.h"
#include "graceful-term.h"

int main()
{
    Eventloop loop;                                    // <-- main loop
    Database db;

    UDPToDatabase udp("0.0.0.0", 1234, db);            // <-- satellite
    StdinToDatabase stdin(db);                         // <-- satellite
    GracefulTerminator terminator;                     // <-- satellite

    udp.hookup(loop);                                  // <-- register with loop
    stdin.hookup(loop);                                // <-- register with loop
    terminator.hookup(loop);                           // <-- register with loop

    loop.run();                                        // <-- run until quit

    db.commit();
    return 0;
};