From 049d75cffb233a28e9928b5062945d8054e209d5 Mon Sep 17 00:00:00 2001 From: "Addisu Z. Taddese" Date: Fri, 26 Jul 2024 12:18:17 -0500 Subject: [PATCH] Use self-pipe trick to implement signal handlers This allows the rest of the Gazebo codebase to use callbacks that can use non async-signal-safe functions Signed-off-by: Addisu Z. Taddese --- src/SignalHandler.cc | 98 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/src/SignalHandler.cc b/src/SignalHandler.cc index 07b6d054..33750b19 100644 --- a/src/SignalHandler.cc +++ b/src/SignalHandler.cc @@ -19,8 +19,11 @@ // comments when upgrading to gz-cmake's "make codecheck" #include "gz/common/SignalHandler.hh" // NOLINT(*) #include +#include #include // NOLINT(*) #include // NOLINT(*) +#include +#include #include // NOLINT(*) #include // NOLINT(*) #include // NOLINT(*) @@ -31,13 +34,52 @@ using namespace gz; using namespace common; // A wrapper for the sigaction sa_handler. +// TODO(azeey) We should avoid using objects with non-trivial destructors as globals. GZ_COMMON_VISIBLE std::map> gOnSignalWrappers; std::mutex gWrapperMutex; +namespace +{ + +class SelfPipe +{ + public: + static int pipeFd[2]; + + public: + static void Initialize(); + + public: + ~SelfPipe(); + + private: + SelfPipe(); + + private: + void CheckPipe(); + + private: + std::thread checkPipeThread; + + private: + std::atomic running{false}; +}; + +int SelfPipe::pipeFd[2]; + +void onSignalTopHalf(int _value) +{ + auto valueByte = static_cast(_value); + if (write(SelfPipe::pipeFd[1], &valueByte, 1) == -1) + { + // TODO (azeey) Not clear what to do here. + } +} + ///////////////////////////////////////////////// /// \brief Callback to execute when a signal is received. /// \param[in] _value Signal number. -void onSignal(int _value) +void onSignalBottomHalf(int _value) { std::lock_guard lock(gWrapperMutex); // Send the signal to each wrapper @@ -47,6 +89,55 @@ void onSignal(int _value) } } +SelfPipe::SelfPipe() +{ + if (pipe(this->pipeFd)) + { + gzerr << "Unable to create pipe\n"; + } + + int flags = fcntl(this->pipeFd[1], F_GETFL); + fcntl(this->pipeFd[1], F_SETFL, flags | O_NONBLOCK); + // TODO(azeey) Handle errors + this->running = true; + std::cout << "Initialized self pipe " << running << std::endl; + this->checkPipeThread = std::thread(&SelfPipe::CheckPipe, this); +} + +SelfPipe::~SelfPipe() +{ + this->running = false; + onSignalTopHalf(127); + this->checkPipeThread.join(); +} +void SelfPipe::Initialize() +{ + // We actually need this object to be destructed in order to join the thread, + // so we can't use gz::utils::NeverDestroyed here. + static SelfPipe selfPipe; +} + +void SelfPipe::CheckPipe() +{ + while (this->running) + { + std::uint8_t signal; + if (read(SelfPipe::pipeFd[0], &signal, 1) != -1) + { + if (this->running) + { + onSignalBottomHalf(signal); + } + } + else + { + gzerr << errno << " " << std::strerror(errno) << std::endl; + } + } +} + +} // namespace + ///////////////////////////////////////////////// class common::SignalHandlerPrivate { @@ -74,14 +165,15 @@ SignalHandler::SignalHandler() static int counter = 0; std::lock_guard lock(gWrapperMutex); - if (std::signal(SIGINT, onSignal) == SIG_ERR) + SelfPipe::Initialize(); + if (std::signal(SIGINT, onSignalTopHalf) == SIG_ERR) { gzerr << "Unable to catch SIGINT.\n" << " Please visit http://community.gazebosim.org for help.\n"; return; } - if (std::signal(SIGTERM, onSignal) == SIG_ERR) + if (std::signal(SIGTERM, onSignalTopHalf) == SIG_ERR) { gzerr << "Unable to catch SIGTERM.\n" << " Please visit http://community.gazebosim.org for help.\n";