jaulib v1.3.0
Jau Support Library (C++, Java, ..)
service_runner.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2022 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24
26
27extern "C" {
28 #include <unistd.h>
29 #include <sys/socket.h>
30 #include <poll.h>
31 #include <signal.h>
32 #include <pthread.h>
33}
34
35#include <jau/debug.hpp>
36
37#include <jau/basic_algos.hpp>
38
39using namespace jau;
40
41void service_runner::service_thread() {
42 {
43 const std::lock_guard<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
44 service_init_locked(*this);
45 running = true;
46 DBG_PRINT("%s::worker Started", name_.c_str());
47 }
48 cv_init.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
49
50 thread_local jau::call_on_release thread_cleanup([&]() {
51 DBG_PRINT("%s::worker::ThreadCleanup: serviceRunning %d -> 0", name_.c_str(), running.load());
52 running = false;
53 cv_init.notify_all();
54 });
55
56 while( !shall_stop_ ) {
57 service_work(*this);
58 }
59 {
60 const std::lock_guard<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
61 WORDY_PRINT("%s::worker: Ended", name_.c_str());
62 service_end_locked(*this);
63 thread_id_ = 0;
64 running = false;
65 thread_cleanup.set_released();
66 }
67 cv_init.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
68}
69
70const ::pid_t service_runner::pid_self = ::getpid();
71
72static void sigaction_handler(int sig, siginfo_t *info, void *ucontext) noexcept {
73 bool pidMatch = info->si_pid == service_runner::pid_self;
74 WORDY_PRINT("service_runner.sigaction: sig %d, info[code %d, errno %d, signo %d, pid %d, uid %d], pid-self %d (match %d)",
75 sig, info->si_code, info->si_errno, info->si_signo,
76 info->si_pid, info->si_uid,
77 service_runner::pid_self, pidMatch);
78 (void)ucontext;
79
80 if( !pidMatch || SIGALRM != sig ) {
81 return;
82 }
83#if 0
84 // We do not de-install the handler on single use,
85 // as we act for multiple SIGALRM events within direct-bt
86 remove_sighandler();
87#endif
88}
89
90bool service_runner::install_sighandler() noexcept {
91 struct sigaction sa_setup;
92 ::bzero(&sa_setup, sizeof(sa_setup));
93 sa_setup.sa_sigaction = sigaction_handler;
94 ::sigemptyset(&(sa_setup.sa_mask));
95 sa_setup.sa_flags = SA_SIGINFO;
96 if( 0 != ::sigaction( SIGALRM, &sa_setup, nullptr ) ) {
97 ERR_PRINT("service_runner::install_sighandler: Setting sighandler");
98 return false;
99 }
100 DBG_PRINT("service_runner::install_sighandler: OK");
101 return true;
102}
103
105 struct sigaction sa_setup;
106 ::bzero(&sa_setup, sizeof(sa_setup));
107 sa_setup.sa_handler = SIG_DFL;
108 ::sigemptyset(&(sa_setup.sa_mask));
109 sa_setup.sa_flags = 0;
110 if( 0 != ::sigaction( SIGALRM, &sa_setup, nullptr ) ) {
111 ERR_PRINT("service_runner::remove_sighandler: Resetting sighandler");
112 return false;
113 }
114 DBG_PRINT("service_runner::remove_sighandler: OK");
115 return true;
116}
117
119 fraction_i64 service_shutdown_timeout,
120 Callback service_work_,
121 Callback service_init_locked_,
122 Callback service_end_locked_) noexcept
123: name_( std::move(name__) ),
124 service_shutdown_timeout_( service_shutdown_timeout ),
125 service_work( std::move(service_work_) ),
126 service_init_locked( std::move(service_init_locked_) ),
127 service_end_locked( std::move(service_end_locked_) ),
128 shall_stop_(true), running(false),
129 thread_id_(0)
130{
131 DBG_PRINT("%s::ctor", name_.c_str());
132}
133
135 DBG_PRINT("%s::dtor: Begin", name_.c_str());
136 stop();
137 DBG_PRINT("%s::dtor: End", name_.c_str());
138}
139
141 {
142 const std::lock_guard<std::mutex> lock_stop(mtx_shall_stop_); // RAII-style acquire and relinquish via destructor
143 shall_stop_ = true;
144 }
145 cv_shall_stop_.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
146}
147
148void service_runner::start() noexcept {
149 DBG_PRINT("%s::start: Begin: %s", name_.c_str(), toString().c_str());
150 /**
151 * We utilize a global SIGALRM handler, since we only can install one handler.
152 */
153 std::unique_lock<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
154 {
155 const std::lock_guard<std::mutex> lock_stop(mtx_shall_stop_); // RAII-style acquire and relinquish via destructor
156 shall_stop_ = false;
157 }
158 cv_shall_stop_.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
159
160 if( running ) {
161 DBG_PRINT("%s::start: End.0: %s", name_.c_str(), toString().c_str());
162 return;
163 }
164
165 std::thread t(&service_runner::service_thread, this); // @suppress("Invalid arguments")
166 thread_id_ = t.native_handle();
167 // Avoid 'terminate called without an active exception'
168 // as t may end due to I/O errors.
169 t.detach();
170
171 while( false == running && false == shall_stop_ ) {
172 cv_init.wait(lock);
173 }
174 DBG_PRINT("%s::start: End.X: %s", name_.c_str(), toString().c_str());
175}
176
177bool service_runner::stop() noexcept {
178 DBG_PRINT("%s::stop: Begin: %s", name_.c_str(), toString().c_str());
179
180 std::unique_lock<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
181 const ::pthread_t tid_service = thread_id_;
182 const bool is_service = tid_service == ::pthread_self();
183 DBG_PRINT("%s::stop: service[running %d, shall_stop %d, is_service %d, tid %p)",
184 name_.c_str(), running.load(), shall_stop_.load(), is_service, (void*)tid_service); // NOLINT(performance-no-int-to-ptr)
186 bool result;
187 if( running ) {
188 if( !is_service ) {
189 if( 0 != tid_service ) {
190 int kerr;
191 if( 0 != ( kerr = ::pthread_kill(tid_service, SIGALRM) ) ) {
192 ERR_PRINT("%s::stop: pthread_kill %p FAILED: %d", name_.c_str(), (void*)tid_service, kerr); // NOLINT(performance-no-int-to-ptr)
193 }
194 }
195 // Ensure the reader thread has ended, no runaway-thread using *this instance after destruction
196 result = true;
197 const fraction_timespec timeout_time = getMonotonicTime() + fraction_timespec(service_shutdown_timeout_);
198 while( true == running && result ) {
199 std::cv_status s { std::cv_status::no_timeout };
200 if( fractions_i64::zero < service_shutdown_timeout_ ) {
201 s = wait_until(cv_init, lock, timeout_time );
202 } else {
203 cv_init.wait(lock);
204 }
205 if( std::cv_status::timeout == s && true == running ) {
206 ERR_PRINT("%s::stop: Timeout (force !running): %s", name_.c_str(), toString().c_str());
207 result = false; // bail out w/ false
208 }
209 }
210 } else {
211 // is_service
212 result = false; // initiated, but not stopped yet
213 }
214 } else {
215 result = true;
216 }
217 DBG_PRINT("%s::stop: End: Result %d, %s", name_.c_str(), result, toString().c_str());
218 return result;
219}
220
221bool service_runner::join() noexcept {
222 DBG_PRINT("%s::join: Begin: %s", name_.c_str(), toString().c_str());
223 std::unique_lock<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
224
225 const bool is_service = thread_id_ == ::pthread_self();
226 DBG_PRINT("%s::join: is_service %d, %s", name_.c_str(), is_service, toString().c_str());
227 bool result;
228 if( running ) {
229 if( !is_service ) {
230 // Ensure the reader thread has ended, no runaway-thread using *this instance after destruction
231 result = true;
232 const fraction_timespec timeout_time = getMonotonicTime() + fraction_timespec(service_shutdown_timeout_);
233 while( true == running && result ) {
234 std::cv_status s { std::cv_status::no_timeout };
235 if( fractions_i64::zero < service_shutdown_timeout_ ) {
236 s = wait_until(cv_init, lock, timeout_time );
237 } else {
238 cv_init.wait(lock);
239 }
240 if( std::cv_status::timeout == s && true == running ) {
241 ERR_PRINT("%s::join: Timeout (force !running): %s", name_.c_str(), toString().c_str());
242 result = false; // bail out w/ false
243 }
244 }
245 } else {
246 // is_service
247 result = false;
248 }
249 } else {
250 result = true;
251 }
252 DBG_PRINT("%s::join: End: Result %d, %s", name_.c_str(), result, toString().c_str());
253 return result;
254}
255
256std::string service_runner::toString() const noexcept {
257 return "ServiceRunner["+name_+", running "+std::to_string(is_running())+", shall_stop "+std::to_string(shall_stop_)+
258 ", thread_id "+to_hexstring((void*)thread_id_)+"]"; // NOLINT(performance-no-int-to-ptr)
259}
Call on release allows the user to pass a function to be called at destruction of this instance.
Definition: basic_algos.hpp:65
bool stop() noexcept
Stops this service, if running.
static bool remove_sighandler() noexcept
Remove the sighandler.
static const ::pid_t pid_self
void set_shall_stop() noexcept
Marks the service thread to stop in due process by flagging shall stop to true.
service_runner(std::string name, fraction_i64 service_shutdown_timeout, Callback service_work, Callback service_init_locked=Callback(), Callback service_end_locked=Callback()) noexcept
Service runner constructor.
bool is_running() const noexcept
Returns true if service is running.
bool join() noexcept
Blocks the current thread until service is stopped or returns immediately if not running or called fr...
std::string toString() const noexcept
Returns a string representation of this service.
~service_runner() noexcept
Service runner destructor.
void start() noexcept
Starts this service, if not running already.
#define ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition: debug.hpp:109
#define WORDY_PRINT(...)
Use for environment-variable environment::VERBOSE conditional verbose messages, prefix '[elapsed_time...
Definition: debug.hpp:68
#define DBG_PRINT(...)
Use for environment-variable environment::DEBUG conditional debug messages, prefix '[elapsed_time] De...
Definition: debug.hpp:52
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
fraction_timespec getMonotonicTime() noexcept
Returns current monotonic time since Unix Epoch 00:00:00 UTC on 1970-01-01.
Definition: basic_types.cpp:52
std::string to_hexstring(value_type const &v) noexcept
Produce a lower-case hexadecimal string representation of the given pointer.
constexpr const jau::fraction_i64 zero(0l, 1lu)
zero is 0/1
__pack(...): Produces MSVC, clang and gcc compatible lead-in and -out macros.
Definition: backtrace.hpp:32
std::cv_status wait_until(std::condition_variable &cv, std::unique_lock< std::mutex > &lock, const fraction_timespec &absolute_time, const bool monotonic=true) noexcept
wait_until causes the current thread to block until the condition variable is notified,...
static void sigaction_handler(int sig, siginfo_t *info, void *ucontext) noexcept
Timespec structure using int64_t for its components in analogy to struct timespec_t on 64-bit platfor...
CXX_ALWAYS_INLINE _Tp load() const noexcept