jaulib v1.5.0
Jau Support Library (C++, Java, ..)
Loading...
Searching...
No Matches
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#include <jau/type_concepts.hpp>
27
28extern "C" {
29 #include <unistd.h>
30 #include <sys/socket.h>
31 #include <poll.h>
32 #include <signal.h>
33 #include <pthread.h>
34}
35
36#include <jau/debug.hpp>
37
38#include <jau/basic_algos.hpp>
39#include <jau/secmem.hpp>
40#include <jau/os/os_support.hpp>
41
42using namespace jau;
43
44void service_runner::service_thread() {
45 {
46 const std::lock_guard<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
47 try {
48 service_init_locked(*this);
49 running = true;
50 jau_DBG_PRINT("%s::worker Started", name_.c_str());
51 } catch(const std::exception &e) {
52 thread_id_ = 0;
53 running = false;
54 jau_ERR_PRINT2("%s::worker Exception @ service_init_locked: %s", name_.c_str(), e.what());
55 }
56 }
57 cv_init.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
58
59 if( !running ) {
60 return;
61 }
62
63 thread_local jau::call_on_release thread_cleanup([&]() { // NOLINT(misc-use-internal-linkage)
64 jau_DBG_PRINT("%s::worker::ThreadCleanup: serviceRunning %d -> 0", name_.c_str(), running.load());
65 running = false;
66 cv_init.notify_all();
67 });
68
69 while( !shall_stop_ ) {
70 try {
71 service_work(*this);
72 } catch(const std::exception &e) {
73 shall_stop_ = true;
74 jau_ERR_PRINT2("%s::worker Exception @ service_work: %s", name_.c_str(), e.what());
75 }
76 }
77 {
78 const std::lock_guard<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
79 jau_WORDY_PRINT("%s::worker: Ended", name_.c_str());
80 try {
81 service_end_locked(*this);
82 } catch(const std::exception &e) {
83 jau_ERR_PRINT2("%s::worker Exception @ service_end_locked: %s", name_.c_str(), e.what());
84 }
85 thread_id_ = 0;
86 running = false;
87 thread_cleanup.set_released();
88 }
89 cv_init.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
90}
91
92const ::pid_t service_runner::pid_self = ::getpid();
93
94static void sigaction_handler(int sig, siginfo_t *info, void *ucontext) noexcept {
95 bool pidMatch = info->si_pid == service_runner::pid_self;
96 jau_WORDY_PRINT("service_runner.sigaction: sig %d, info[code %d, errno %d, signo %d, pid %d, uid %u], pid-self %d (match %d)",
97 sig, info->si_code, info->si_errno, info->si_signo,
98 info->si_pid, info->si_uid,
99 service_runner::pid_self, pidMatch);
100 (void)ucontext;
101
102 if( !pidMatch || SIGALRM != sig ) {
103 return;
104 }
105#if 0
106 // We do not de-install the handler on single use,
107 // as we act for multiple SIGALRM events within direct-bt
108 remove_sighandler();
109#endif
110}
111
112bool service_runner::install_sighandler() noexcept {
113 struct sigaction sa_setup;
114 jau::zero_bytes_sec(&sa_setup, sizeof(sa_setup));
115 sa_setup.sa_sigaction = sigaction_handler;
116 ::sigemptyset(&(sa_setup.sa_mask));
117 sa_setup.sa_flags = SA_SIGINFO;
118 if( 0 != ::sigaction( SIGALRM, &sa_setup, nullptr ) ) {
119 jau_ERR_PRINT("service_runner::install_sighandler: Setting sighandler");
120 return false;
121 }
122 jau_DBG_PRINT("service_runner::install_sighandler: OK");
123 return true;
124}
125
127 struct sigaction sa_setup;
128 jau::zero_bytes_sec(&sa_setup, sizeof(sa_setup));
129 sa_setup.sa_handler = SIG_DFL;
130 ::sigemptyset(&(sa_setup.sa_mask));
131 sa_setup.sa_flags = 0;
132 if( 0 != ::sigaction( SIGALRM, &sa_setup, nullptr ) ) {
133 jau_ERR_PRINT("service_runner::remove_sighandler: Resetting sighandler");
134 return false;
135 }
136 jau_DBG_PRINT("service_runner::remove_sighandler: OK");
137 return true;
138}
139
142 Callback service_work_,
143 Callback service_init_locked_,
144 Callback service_end_locked_) noexcept
145: name_( std::move(name__) ),
146 service_shutdown_timeout_( service_shutdown_timeout ),
147 service_work( std::move(service_work_) ),
148 service_init_locked( std::move(service_init_locked_) ),
149 service_end_locked( std::move(service_end_locked_) ),
150 shall_stop_(true), running(false),
151 thread_id_(0)
152{
153 jau_DBG_PRINT("%s::ctor", name_.c_str());
154}
155
157 jau_DBG_PRINT("%s::dtor: Begin", name_.c_str());
158 stop();
159 jau_DBG_PRINT("%s::dtor: End", name_.c_str());
160}
161
163 {
164 const std::lock_guard<std::mutex> lock_stop(mtx_shall_stop_); // RAII-style acquire and relinquish via destructor
165 shall_stop_ = true;
166 }
167 cv_shall_stop_.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
168}
169
170void service_runner::start() noexcept {
171 jau_DBG_PRINT("%s::start: Begin: %s", name_.c_str(), toString().c_str());
172 /**
173 * We utilize a global SIGALRM handler, since we only can install one handler.
174 */
175 std::unique_lock<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
176 {
177 const std::lock_guard<std::mutex> lock_stop(mtx_shall_stop_); // RAII-style acquire and relinquish via destructor
178 shall_stop_ = false;
179 }
180 cv_shall_stop_.notify_all(); // have mutex unlocked before notify_all to avoid pessimistic re-block of notified wait() thread.
181
182 if( running ) {
183 jau_DBG_PRINT("%s::start: End.0: %s", name_.c_str(), toString().c_str());
184 return;
185 }
186
187 std::thread t(&service_runner::service_thread, this); // @suppress("Invalid arguments")
188 thread_id_ = t.native_handle();
189 // Avoid 'terminate called without an active exception'
190 // as t may end due to I/O errors.
191 t.detach();
192
193 while( false == running && false == shall_stop_ ) {
194 cv_init.wait(lock);
195 }
196 jau_DBG_PRINT("%s::start: End.X: %s", name_.c_str(), toString().c_str());
197}
198
199bool service_runner::stop() noexcept {
200 jau_DBG_PRINT("%s::stop: Begin: %s", name_.c_str(), toString().c_str());
201
202 std::unique_lock<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
203 const ::pthread_t tid_service = thread_id_;
204 const bool is_service = tid_service == ::pthread_self();
205 jau_DBG_PRINT("%s::stop: service[running %d, shall_stop %d, is_service %d, tid %p)",
206 name_.c_str(), running.load(), shall_stop_.load(), is_service, (void*)tid_service); // NOLINT(performance-no-int-to-ptr)
208 bool result;
209 if( running ) {
210 if( !is_service ) {
211 if( 0 != tid_service ) {
212 #if JAU_OS_HAS_PTHREAD
213 if constexpr ( jau::os::has_pthread() ) {
214 int kerr;
215 if( 0 != ( kerr = ::pthread_kill(tid_service, SIGALRM) ) ) {
216 jau_ERR_PRINT("%s::stop: pthread_kill %p FAILED: %d", name_.c_str(), (void*)tid_service, kerr); // NOLINT(performance-no-int-to-ptr)
217 }
218 } else
219 #endif
220 {
221 jau_INFO_PRINT("%s::stop: pthread_kill n/a, service %p running", name_.c_str(), (void*)tid_service); // NOLINT(performance-no-int-to-ptr)
222 }
223 }
224 // Ensure the reader thread has ended, no runaway-thread using *this instance after destruction
225 result = true;
226 const fraction_timespec timeout_time = getMonotonicTime() + fraction_timespec(service_shutdown_timeout_);
227 while( true == running && result ) {
228 std::cv_status s { std::cv_status::no_timeout };
229 if( fractions_i64::zero < service_shutdown_timeout_ ) {
230 s = wait_until(cv_init, lock, timeout_time );
231 } else {
232 cv_init.wait(lock);
233 }
234 if( std::cv_status::timeout == s && true == running ) {
235 jau_ERR_PRINT("%s::stop: Timeout (force !running): %s", name_.c_str(), toString().c_str());
236 result = false; // bail out w/ false
237 }
238 }
239 } else {
240 // is_service
241 result = false; // initiated, but not stopped yet
242 }
243 } else {
244 result = true;
245 }
246 jau_DBG_PRINT("%s::stop: End: Result %d, %s", name_.c_str(), result, toString().c_str());
247 return result;
248}
249
250bool service_runner::join() noexcept {
251 jau_DBG_PRINT("%s::join: Begin: %s", name_.c_str(), toString().c_str());
252 std::unique_lock<std::mutex> lock(mtx_lifecycle); // RAII-style acquire and relinquish via destructor
253
254 const bool is_service = thread_id_ == ::pthread_self();
255 jau_DBG_PRINT("%s::join: is_service %d, %s", name_.c_str(), is_service, toString().c_str());
256 bool result;
257 if( running ) {
258 if( !is_service ) {
259 // Ensure the reader thread has ended, no runaway-thread using *this instance after destruction
260 result = true;
261 const fraction_timespec timeout_time = getMonotonicTime() + fraction_timespec(service_shutdown_timeout_);
262 while( true == running && result ) {
263 std::cv_status s { std::cv_status::no_timeout };
264 if( fractions_i64::zero < service_shutdown_timeout_ ) {
265 s = wait_until(cv_init, lock, timeout_time );
266 } else {
267 cv_init.wait(lock);
268 }
269 if( std::cv_status::timeout == s && true == running ) {
270 jau_ERR_PRINT("%s::join: Timeout (force !running): %s", name_.c_str(), toString().c_str());
271 result = false; // bail out w/ false
272 }
273 }
274 } else {
275 // is_service
276 result = false;
277 }
278 } else {
279 result = true;
280 }
281 jau_DBG_PRINT("%s::join: End: Result %d, %s", name_.c_str(), result, toString().c_str());
282 return result;
283}
284
285std::string service_runner::toString() const noexcept {
286 return "ServiceRunner["+name_+", running "+std::to_string(is_running())+", shall_stop "+std::to_string(shall_stop_)+
287 ", thread_id "+toHexString((void*)thread_id_)+"]"; // NOLINT(performance-no-int-to-ptr)
288}
bool stop() noexcept
Stops this service, if running.
function< void(service_runner_ref)> Callback
static bool remove_sighandler() noexcept
Remove the sighandler.
fraction_i64 service_shutdown_timeout() const noexcept
Returns maximum duration in fractions of seconds to wait for service to stop at stop() and join(),...
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.
static const ::pid_t pid_self
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 jau_INFO_PRINT(fmt,...)
Use for unconditional informal messages, prefix '[elapsed_time] Info: '.
Definition debug.hpp:165
#define jau_DBG_PRINT(fmt,...)
Use for environment-variable environment::DEBUG conditional debug messages, prefix '[elapsed_time] De...
Definition debug.hpp:100
#define jau_ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition debug.hpp:150
#define jau_WORDY_PRINT(...)
Use for environment-variable environment::VERBOSE conditional verbose messages, prefix '[elapsed_time...
Definition debug.hpp:118
#define jau_ERR_PRINT2(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition debug.hpp:153
fraction_timespec getMonotonicTime() noexcept
Returns current monotonic time since Unix Epoch 00:00:00 UTC on 1970-01-01.
fraction< int64_t > fraction_i64
fraction using int64_t as integral type
constexpr bool has_pthread() noexcept
Evaluates true if platform supports posix compatible threading.
void zero_bytes_sec(void *s, size_t n) noexcept __attrdecl_no_optimize__
Wrapper to ::explicit_bzero(), ::bzero() or ::memset(), whichever is available in that order.
std::string toHexString(const void *data, const nsize_t length, const lb_endian_t byteOrder=lb_endian_t::big, const LoUpCase capitalization=LoUpCase::lower, const PrefixOpt prefix=PrefixOpt::prefix) noexcept
Produce a hexadecimal string representation of the given lsb-first byte values.
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...