Program Listing for File SigtermGuard.hpp

↰ Return to documentation for file (src/utils/SigtermGuard.hpp)

#ifndef SIGTERMGUARD_HPP
#define SIGTERMGUARD_HPP

#include <csignal>
#include <stdexcept>
#include <string>

namespace utils
{

    // Set (only) by signalHandler() below; read by sigtermWasReceived().
    // volatile sig_atomic_t is the one type the C++ standard guarantees
    // safe to write from inside a signal handler without risking
    // undefined behavior -- but that guarantee is about signal-handler
    // safety on whichever single thread the signal happened to be
    // delivered to (POSIX picks one, not necessarily this process's
    // main thread -- see SigtermGuard's own comment for why that
    // doesn't matter here), not about the value becoming visible to
    // every OpenMP worker thread's own read of it: that cross-thread
    // visibility is what sigtermWasReceived()'s own "#pragma omp
    // atomic read" actually provides. inline (a C++17 inline variable)
    // so SimCluster::run() and SimGalaxy::run() -- the only two
    // callers, never both running at once within a single process (see
    // SigtermGuard's own comment) -- share exactly one instance of
    // this flag, matching how a signal disposition installed via
    // std::signal() is itself process-wide, not per-translation-unit.
    inline volatile std::sig_atomic_t sigtermReceived = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -- a signal handler can only communicate through global/static state; see this variable's own comment

    inline void signalHandler(int /*signum*/)
    {
        sigtermReceived = 1;
    }

    inline auto sigtermWasReceived() -> bool
    {
        std::sig_atomic_t value = 0;
#ifdef _OPENMP
#pragma omp atomic read
#endif
        value = sigtermReceived;
        return value != 0;
    }

    class SigtermGuard
    {
    public:

        explicit SigtermGuard(const std::string& installErrorMessage)
        {
            // Reset the flag before installing the handler, not after
            // (and so deliberately not via a member initializer for
            // previousHandler_, despite what clang-tidy's
            // cppcoreguidelines-prefer-member-initializer suggests --
            // that would run the install first): a SIGTERM arriving
            // between the two would otherwise have this immediately
            // clear the very flag it just set, silently losing the
            // signal.
            sigtermReceived = 0;
            previousHandler_ = std::signal(SIGTERM, signalHandler); // NOLINT(cppcoreguidelines-prefer-member-initializer)
            if (previousHandler_ == SIG_ERR)
            {
                throw std::runtime_error(installErrorMessage);
            }
        }

        ~SigtermGuard()
        {
            // Best-effort restore -- this runs from a destructor, so
            // it must not throw, and there is nothing more useful to
            // do with a failure here anyway
            (void)std::signal(SIGTERM, previousHandler_); // NOLINT(cert-err33-c)
        }

        SigtermGuard(const SigtermGuard&) = delete;
        auto operator=(const SigtermGuard&) -> SigtermGuard& = delete;
        SigtermGuard(SigtermGuard&&) = delete;
        auto operator=(SigtermGuard&&) -> SigtermGuard& = delete;

    private:
        using Handler = void (*)(int);
        Handler previousHandler_ = nullptr;
    };

} // namespace utils

#endif // SIGTERMGUARD_HPP