Exercise: Graceful Termination Via SIGTERM/SIGINT#
Exercise#
Our database application still does not commit the database when terminated like processes are usually terminated,
Ctrl-Cfrom the terminal, when run in foreground (SIGINTis delivered)kill <PID>from another process; the shell, usually (SIGTERMis 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;
};