2323#include " util/config/ConfigDefinition.hpp"
2424#include " util/log/Logger.hpp"
2525
26+ #include < atomic>
2627#include < chrono>
28+ #include < condition_variable>
2729#include < csignal>
2830#include < functional>
29- #include < optional>
31+ #include < mutex>
32+ #include < thread>
3033#include < utility>
3134
3235namespace util {
@@ -50,17 +53,11 @@ class SignalsHandlerStatic {
5053 }
5154
5255 static void
53- handleSignal (int signal)
56+ handleSignal (int /* signal */ )
5457 {
5558 ASSERT (installedHandler != nullptr , " SignalsHandler is not initialized" );
56- installedHandler->stopHandler_ (signal);
57- }
58-
59- static void
60- handleSecondSignal (int signal)
61- {
62- ASSERT (installedHandler != nullptr , " SignalsHandler is not initialized" );
63- installedHandler->secondSignalHandler_ (signal);
59+ installedHandler->signalReceived_ = true ;
60+ installedHandler->cv_ .notify_one ();
6461 }
6562};
6663
@@ -69,56 +66,109 @@ SignalsHandler* SignalsHandlerStatic::installedHandler = nullptr;
6966} // namespace impl
7067
7168SignalsHandler::SignalsHandler (config::ClioConfigDefinition const & config, std::function<void ()> forceExitHandler)
72- : gracefulPeriod_(0 )
73- , context_(1 )
74- , stopHandler_([this , forceExitHandler](int ) mutable {
75- LOG (LogService::info ()) << " Got stop signal. Stopping Clio. Graceful period is "
76- << std::chrono::duration_cast<std::chrono::milliseconds>(gracefulPeriod_).count ()
77- << " milliseconds." ;
78- setHandler (impl::SignalsHandlerStatic::handleSecondSignal);
79- timer_.emplace (context_.scheduleAfter (
80- gracefulPeriod_, [forceExitHandler = std::move (forceExitHandler)](auto && stopToken, bool canceled) {
81- // TODO: Update this after https://github.com/XRPLF/clio/issues/1380
82- if (not stopToken.isStopRequested () and not canceled) {
83- LOG (LogService::warn ()) << " Force exit at the end of graceful period." ;
84- forceExitHandler ();
85- }
86- }
87- ));
88- stopSignal_ ();
89- })
90- , secondSignalHandler_([this , forceExitHandler = std::move(forceExitHandler)](int ) {
91- LOG (LogService::warn ()) << " Force exit on second signal." ;
92- forceExitHandler ();
93- cancelTimer ();
94- setHandler ();
95- })
69+ : gracefulPeriod_(util::config::ClioConfigDefinition::toMilliseconds(config.get<float >(" graceful_period" )))
70+ , forceExitHandler_(std::move(forceExitHandler))
9671{
9772 impl::SignalsHandlerStatic::registerHandler (*this );
98-
99- gracefulPeriod_ = util::config::ClioConfigDefinition::toMilliseconds (config.get <float >(" graceful_period" ));
73+ workerThread_ = std::thread ([this ]() { runStateMachine (); });
10074 setHandler (impl::SignalsHandlerStatic::handleSignal);
10175}
10276
10377SignalsHandler::~SignalsHandler ()
10478{
105- cancelTimer ();
10679 setHandler ();
80+
81+ state_ = State::NormalExit;
82+ cv_.notify_one ();
83+
84+ if (workerThread_.joinable ())
85+ workerThread_.join ();
86+
10787 impl::SignalsHandlerStatic::resetHandler (); // This is needed mostly for tests to reset static state
10888}
10989
11090void
111- SignalsHandler::cancelTimer ()
91+ SignalsHandler::notifyGracefulShutdownComplete ()
11292{
113- if (timer_.has_value ())
114- timer_->abort ();
93+ if (state_ == State::GracefulShutdown) {
94+ LOG (LogService::info ()) << " Graceful shutdown completed successfully." ;
95+ state_ = State::NormalExit;
96+ cv_.notify_one ();
97+ }
11598}
11699
117100void
118101SignalsHandler::setHandler (void (*handler)(int ))
119102{
120- for (int const signal : kHANDLED_SIGNALS ) {
103+ for (int const signal : kHANDLED_SIGNALS )
121104 std::signal (signal, handler == nullptr ? SIG_DFL : handler);
105+ }
106+
107+ void
108+ SignalsHandler::runStateMachine ()
109+ {
110+ while (state_ != State::NormalExit) {
111+ auto currentState = state_.load ();
112+
113+ switch (currentState) {
114+ case State::WaitingForSignal: {
115+ {
116+ std::unique_lock<std::mutex> lock (mutex_);
117+ cv_.wait (lock, [this ]() { return signalReceived_ or state_ == State::NormalExit; });
118+ }
119+
120+ if (state_ == State::NormalExit)
121+ return ;
122+
123+ LOG (
124+ LogService::info ()
125+ ) << " Got stop signal. Stopping Clio. Graceful period is "
126+ << std::chrono::duration_cast<std::chrono::milliseconds>(gracefulPeriod_).count () << " milliseconds." ;
127+
128+ state_ = State::GracefulShutdown;
129+ signalReceived_ = false ;
130+
131+ stopSignal_ ();
132+ break ;
133+ }
134+
135+ case State::GracefulShutdown: {
136+ bool waitResult = false ;
137+ {
138+ std::unique_lock<std::mutex> lock (mutex_);
139+
140+ // Wait for either:
141+ // 1. Graceful period to elapse (timeout)
142+ // 2. Another signal (signalReceived_)
143+ // 3. Graceful shutdown completion (state changes to NormalExit)
144+ waitResult = cv_.wait_for (lock, gracefulPeriod_, [this ]() {
145+ return signalReceived_ or state_ == State::NormalExit;
146+ });
147+ }
148+
149+ if (state_ == State::NormalExit)
150+ break ;
151+
152+ if (signalReceived_) {
153+ LOG (LogService::warn ()) << " Force exit on second signal." ;
154+ state_ = State::ForceExit;
155+ signalReceived_ = false ;
156+ } else if (not waitResult) {
157+ LOG (LogService::warn ()) << " Force exit at the end of graceful period." ;
158+ state_ = State::ForceExit;
159+ }
160+ break ;
161+ }
162+
163+ case State::ForceExit: {
164+ forceExitHandler_ ();
165+ state_ = State::NormalExit;
166+ break ;
167+ }
168+
169+ case State::NormalExit:
170+ return ;
171+ }
122172 }
123173}
124174
0 commit comments