randolf.ca  1.00
Randolf Richardson's C++ classes
Loading...
Searching...
No Matches
rthread
1#pragma once
2
3#include <pthread.h>
4
5#include <atomic>
6#include <exception>
7
8#include <randolf/rex>
9
10namespace randolf {
11
12 /*======================================================================*//**
13 @brief
14 This @ref rthread thread library provides an easier and sufficiently-complete
15 object-oriented thread interface class for C++ that adds a properly-honoured
16 destructor, and without terminating the entire application when the thread
17 throws an exception. This class also provides a virtual method for
18 optionally handling all exceptions (that are otherwise ignored by default) in
19 a penultimate stage prior to commencing onward to the final destructor stage.
20
21 POSIX threads are the foundation of the rthread class, and the resulting C++
22 interface will probably seem familiar to those who are familliar with Java.
23
24 @par Features
25 This is meant to be a safe and easy-to-use thread class for C++, which is
26 intended to make thread sub-classing straight-forward in a logical manner for
27 developers. Some of the key features are:
28
29 - Customizable constructor that doesn't automatically start the thread
30 - Better control with the @ref start() method to start a previously
31 constructed rthread
32 - Abstract @ref run() method (required)
33 - This method contains the code that will be run in its own thread
34 - Customizable destructor that occurs only after the thread ends (which
35 is particularly useful for daemon threads {a.k.a., detached threads})
36 - Customizable exception handler method (just subclass it to use it)
37 - Most methods are designed to be thread-safe (e.g., status methods)
38
39 Some advanced features are planned that exceed what the basic thread
40 functions provide, but are also needed:
41
42 - thread groups (separate class), possibly with support for including
43 threads in multiple groups
44 - thread pool (separate class)
45
46 @par Background
47 I created this class to make it easier to write internet server daemons. I
48 started out using C-style thread functions (because C++ doesn't come with a
49 thread class that can be sub-classed), and even with std::jthreads I ran into
50 a serious problem with the thread's destructor executing while the thread was
51 still running (the code that started it went out of scope) -- this is not
52 what I expected because the running thread should really be an additional
53 criteria for thread destruction (this is a serious concern since the
54 premature destruction of a running thread usually results in a segmentation
55 fault or other unpredictable behaviour that has the potential to cause the
56 Operating System to exhibit other strange behaviours or even crash entirely).
57
58 After looking for existing solutions (none of which resolved the ineherent
59 problems with threads in C++), I embarked on creating this rthread class,
60 which was highly educational but also wasn't difficult. The end result is a
61 small class that's easy to maintain, eliminates the reliability concerns, and
62 is easy to use primarily because C++ thoroughly supports subclassing.
63
64 My background in programming began when I was a young child, teaching myself
65 BASIC and then machine language (when I found BASIC to be too limited) before
66 moving on to other languages like Perl and Java many years later. Eventually
67 I circled around to C (which I chose to learn the hard way by writing some
68 PostgreSQL extensions) and then C++ a few years after that. I have a lot of
69 experience with programming threadded applications on a variety of platforms
70 that support pthreads (including Novell's NetWare), running application code
71 I created that ran hundreds of thousands of threads successfully serving a
72 similar number of users globally without slowdowns or crashes.
73 @par History
74 - 2022-Oct-22 v1.00 Initial version
75 - 2024-Oct-23 v1.00 Various minor improvements to the documentation since
76 the previous update
77 - 2025-Feb-03 v1.00 Increased use of references and pointers
78 - 2025-Feb-15 v1.00 Improved exception handling
79 @version 1.00
80 @author Randolf Richardson
81
82 *///=========================================================================
83 class rthread {
84 private:
85 pthread_t __rthread_pthread_id = 0;
86 std::atomic_bool __rthread_running = false;
87 std::atomic_bool __rthread_detached = false;
88 std::atomic_bool __rthread_death = false; // Destructor changes this to TRUE
89 std::atomic_bool __rthread_reuseable = false; // Change this to a total-run-count // If set to TRUE, then this thread can be run again without needing to be re-constructed
90 std::atomic_ulong __rthread_used = 0; // Total number of times this thread was started
91
92 /*======================================================================*//**
93 Return Code check, and throws an rthread-specific exception if an error
94 occurred, which is indicated by @c -1 (a lot of example code shows @c < @c 1
95 comparisons, but we specifically test for @c -1 because the documentation
96 clearly states @c -1. This is important because a system that can support an
97 extremely high number of socket handles might, in theory, assign handles with
98 values that get interpreted as negative integers, and so less-than-zero tests
99 would result in dropped packets or dropped sockets (any such socket code that
100 allocates such handles obviously must not ever allocate @c -1 since this
101 would definitely be misinterpreted as an error).
102
103 If rc is not @c -1, then it is simply returned as is.
104
105 If n is nonzero and rc is 0, then n will override errno. (This option is
106 provided to accomodate the few socket library functions that return values
107 that are never errors, and expect the developer to rely on other means of
108 detecting whether an error occurred. This is an example of where Object
109 Oriented Programming is helpful in making things better.)
110 @returns Original value of @c rc (if no exceptions were thrown)
111 *///=========================================================================
112 int __rc_check_pthread(
113 /// Return code
114 const int rc,
115 /// Exception handling flags (see @ref rex::REX_FLAGS for details)
116 const int flags = rex::rex::REX_FLAGS::REX_DEFAULT) {
117 if (rc != 0) {
118 randolf::rex::mk_exception("", rc, flags); // This function doesn't return (this code ends here)
119 } // -x- if rc -x-
120 return rc;
121 } // -x- int __rc_check_pthread -x-
122
123 public:
124 /*======================================================================*//**
125 @brief
126 Default constructor.
127 You can subclass this constructor and/or create parameterized constructors,
128 any of which may throw exceptions (even though this default one doesn't).
129
130 This is particularly useful for performing pre-run setup before starting the
131 thread (see the @ref run() and @ref start() methods for details), especially
132 with threads that start running in response to external events (such as user
133 interaction events, incoming internet socket connections, etc.).
134 *///=========================================================================
135 rthread() noexcept {} // -x- constructor rthread -x-
136
137 /*======================================================================*//**
138 @brief
139 Default destructor. Called only after two conditions are met:
140 1. this rthread has gone out of scope
141 2. termination of the underlying pthread (this is different from the
142 standard C++ implementation of threads and jthreads, which don't have
143 this criteria)
144
145 Exceptions are handled by the @ref _rthread_exceptions() method before this
146 destructor is activated.
147
148 You can add your own destructor to safely ensure that files and sockets are
149 closed, and that resources are freed, deallocated, deleted, etc., which is
150 essential to preventing various types of resource leaks if your thread code
151 (in the @ref run() method) exited early due to an unexpected exception (it is
152 the developer's responsibility to ensure resources are managed properly, and
153 this destructor provides a means of handling this reliably).
154
155 Logging, re-queuing, semaphore completions, etc., can also be handled in this
156 method because it runs after the termination of the underlying pthread.
157 @warning
158 Do not throw exceptions from this method. If you're relying on a function or
159 a class that might throw exceptions (intended or otherwise), then you need to
160 take care to at least utilize a final try/catch block for @c std::exception.
161 *///=========================================================================
162 virtual ~rthread() noexcept { // Destructor (subclass destructor will run first if it's defined)
163 __rthread_death = true;
164 if (__rthread_running && !__rthread_detached) {
165 pthread_detach(__rthread_pthread_id);
166 __rthread_detached = true;
167 } // -x- if __rthread_running -x-
168//std::cout << "rthread " << __rthread_pthread_id << " destruction completed / __rthread_running=" << __rthread_running << std::endl;
169 if (__rthread_running) {
170//pthread_attr_t attr;
171//pthread_getattr_np(__rthread_pthread_id, &attr);
172//pthread_attr_destroy(&attr);
173//std::cout << "__rthread_pthread_id=" << __rthread_pthread_id << " pthread_self()=" << pthread_self() << std::endl; // These are the same, as they should be
174 //int temp;
175 //pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr);
176 //pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr);
177 pthread_testcancel();
178 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr);
179 pthread_cancel(__rthread_pthread_id); // This must be the very last; any commands after this will cause: terminate called without an active exception
180 // TODO: pthread_setcancelstate, pthread_setcanceltype, and pthread_testcancel
181// pthread_join(pthread_self(), nullptr); // Join to self to trigger release of resources
182// pthread_detach(__rthread_pthread_id);
183//pthread_kill(__rthread_pthread_id, 3);
184 } // -x- if __rthread_running -x-
185 } // -x- destructor rthread -x-
186
187 /*======================================================================*//**
188 @brief
189 Final rthread exception handler.
190 If an uncaught exception is thrown from the @ref run() method in a running
191 rthread, then it will be handled by this method (which is still technically
192 running on the underlying pthread). Override this method to handle known and
193 unknown exceptions gracefully, which will run before the destructor (or
194 reinitiator) runs.
195 @warning
196 If @c e is @c nullptr it means an unknown exception was thrown (e.g.,
197 internally we literally caught `(...)` (other exception), in which case
198 you'll need to use @c std::current_exception() to access it.
199 @note
200 For a reuseable rthread, the @ref running status will be set to false during
201 @ref _rthread_reinitialize execution, which can be useful for handling those
202 exceptions separately within this code (by testing for the @ref running()
203 condition).
204 *///=========================================================================
205 virtual void _rthread_exceptions(
206 /// Exceptions (see warning, immediately above)
207 [[maybe_unused]] const std::exception* e) noexcept {} // -x- void rthread_exceptions -x-
208
209 /*======================================================================*//**
210 @brief
211 Re-initialize internal variables, refresh resources, etc., before re-running
212 a @ref reuseable rthread.
213 When utilizing the reuseable feature, this method can be thought of as having
214 similar functionality to that of the constructor, except that it is used to
215 do the following to bring internal variables and resources back to the same
216 state that the constructor originally initialized them to, and thus reducing
217 construction overhead (some variables may not need re-initializing, which can
218 also help to further-reduce overhead):
219 - close any open files, sockets, etc., that need to be closed (or reset
220 file pointers instead of re-opening files)
221 - reset/clear variables and memory structures to their expected defaults
222 (or free-and-allocate resources that can't be reset or cleared reliably)
223 - re-add @c this to a thread pool or concurrent queue of ready rthreads
224
225 @note
226 If there is any clean-up, logging, etc., peformed in the destructor that also
227 needs to be performed here, the best approach is to create a private method
228 for those particular actions and then call it from this method as well as the
229 destructor for better operational consistency.
230 @see reuseable
231 *///=========================================================================
232 virtual void _rthread_reinitialize() noexcept {} // -x- void rthread_reinitialize -x-
233
234 /*======================================================================*//**
235 @brief
236 Daemonize this rthread.
237
238 If this rthread was previously detached, then this method has no effect (but
239 won't cause the randolf::rex::xEINVAL exception to be thrown; there may be
240 other reasons for this exception to be thrown).
241 @par Threads
242 This method is thread-safe.
243
244 @throws randolf::rex::xEINVAL Not a joinable rthread
245 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
246 couldn't be found (underlying pthread doesn't exist)
247
248 @returns The same rthread object so as to facilitate stacking
249 @see detached
250 @see start_detached
251 *///=========================================================================
252 rthread& detach() {
253 if (__rthread_running && !__rthread_detached) {
254 __rc_check_pthread(pthread_detach(__rthread_pthread_id));
255 __rthread_detached = true;
256 } // -x- if __rthread_running -x-
257 return *this;
258 } // -x- rthread& detach -x-
259
260 /*======================================================================*//**
261 @brief
262 Find out whether this rthread is daemonized.
263 @par Threads
264 This method is thread-safe.
265 @returns TRUE = daemonized
266 @returns FALSE = not daemonized
267 @see detach
268 @see start_detached
269 *///=========================================================================
270 bool detached() noexcept {
271 return __rthread_detached;
272 } // -x- bool detached -x-
273
274 /*======================================================================*//**
275 @brief
276 Join this rthread to the current thread that called this method. (This is
277 how non-daemonized threads are normally terminated.)
278 @note
279 This method blocks until this rthread's underlying pthread is terminated.
280 @par Threads
281 This method is thread-safe.
282
283 @throws randolf::rex::xEDEADLK Deadlock detected because two threads are
284 queued to join with each other, or the current rthread is trying to
285 join with itself
286 @throws randolf::rex::xEINVAL Not a joinable rthread
287 @throws randolf::rex::xEINVAL A different thread is already queued to join
288 with this rthread
289 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
290 couldn't be found (underlying pthread doesn't exist)
291
292 @returns The same rthread object so as to facilitate stacking
293 @see stop
294 *///=========================================================================
295 rthread& join() {
296 if (__rthread_running) {
297 __rthread_running = false; // Do this before pthread_join to signal an immediate shutdown
298 __rc_check_pthread(pthread_join(__rthread_pthread_id, NULL));
299 __rthread_detached = true; // TODO: Determine if this should actually be set to FALSE
300 } // -x- if __rthread_running -x-
301 return *this;
302 } // -x- rthread& join -x-
303
304 /*======================================================================*//**
305 @brief
306 Find out what the underlying @c pthread_id is.
307
308 Advanced developers may want easy access to this ID for a variety of reasons.
309
310 @warning
311 This method is not thread-safe.
312
313 @returns ID number of underlying pthread (0 = not assigned, which is what you
314 should expect to find if this rthread didn't @ref start() yet)
315 *///=========================================================================
316 pthread_t pthread_id() noexcept {
317 return __rthread_pthread_id;
318 } // -x- pthread_t pthread_id -x-
319
320 /*======================================================================*//**
321 @brief
322 Find out whether this rthread can be re-used.
323 @par Threads
324 This method is thread-safe.
325 @returns TRUE = re-useable
326 @returns FALSE = not re-useable
327 *///=========================================================================
328 bool reuseable() noexcept {
329 return __rthread_reuseable;
330 } // -x- bool reuseable -x-
331
332 /*======================================================================*//**
333 @brief
334 Configure whether this rthread can be re-used.
335 @par Threads
336 This method is thread-safe.
337 @returns The same rthread object so as to facilitate stacking
338 @see _rthread_reinitialize
339 *///=========================================================================
340 rthread& reuseable(
341 /// TRUE = Set this rthread to be re-useable@n
342 /// FALSE = Set this rthread to not be re-useable
343 bool flag) noexcept {
344 __rthread_reuseable = flag;
345 return *this;
346 } // -x- rthread& reuseable -x-
347
348 /*======================================================================*//**
349 @brief
350 Thread functionality (subclassing this method is required).
351 @warning
352 Do not call this method directly. To properly begin rthread execution, use
353 either of the @ref start() or @ref start_detached() methods.
354 When subclassing rthread, you need to override this method with the code that
355 is to be executed as a separate thread. (You don't have to move all of your
356 code into this method; in fact, you can simply use this method to call out to
357 code elsewhere, or call methods on instantiated classes that were referenced
358 during the instantiation of your overriding rthread() constructor, etc.)
359 @note
360 Minimally, this is the only method that's required when sub-classing rthread.
361 @see detach
362 @see detached
363 @see start
364 @see start_detached
365 *///=========================================================================
366 virtual void run() = 0; // Not requiring this method in the subclass is completely and utterly pointless
367
368 /*======================================================================*//**
369 @brief
370 Find out how many times this rthread was started.
371 This method is only really meaningful for @ref reuseable rthreads.
372 @par Threads
373 This method is thread-safe.
374 @returns Run count
375 @see _rthread_reinitialize
376 @see reuseable
377 *///=========================================================================
378 ulong run_count() noexcept {
379 return __rthread_used;
380 } // -x- ulong run_count -x-
381
382 /*======================================================================*//**
383 @brief
384 Find out whether this rthread is actively running.
385 @par Threads
386 This method is thread-safe.
387 @returns TRUE = running
388 @returns FALSE = not running
389 @see start
390 @see stop
391 *///=========================================================================
392 bool running() noexcept {
393 return __rthread_running;
394 } // -x- bool running -x-
395
396 /*======================================================================*//**
397 @brief
398 Commence execution of the @c run() method as a separate rthread.
399 @par Threads
400 This method is thread-safe.
401
402 @throws randolf::rex::xEWOULDBLOCK (xEAGAIN) Insufficient resources to start
403 the underlying pthread
404 @throws randolf::rex::xEWOULDBLOCK (xEAGAIN) Maximum number-of-threads limit
405 reached
406
407 @returns The same rthread object so as to facilitate stacking
408 @see detach
409 @see detached
410 @see start_detached
411 @see stop
412 *///=========================================================================
413 rthread& start() {
414 __rc_check_pthread(pthread_create(&__rthread_pthread_id, nullptr, __rthread_run, this));
415 __rthread_used++; // Increment thread run count
416 return *this;
417 } // -x- rsocket& start -x-
418
419 /*======================================================================*//**
420 @brief
421 Commence execution of the @c run() method as a separate thread in a
422 daemonized state.
423 @par Threads
424 This method is thread-safe.
425
426 @throws randolf::rex::xEDEADLK Deadlock detected because two threads are
427 queued to join with each other, or the current rthread is trying to
428 join with itself
429 @throws randolf::rex::xEINVAL Not a joinable rthread
430 @throws randolf::rex::xEINVAL A different thread is already queued to join
431 with this rthread
432 @throws randolf::rex::xENOMEM Insufficient memory (on Linux this normally
433 always succeeds, but there's not guarantee as the community suggests
434 that this could change in the future)
435 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
436 couldn't be found (underlying pthread doesn't exist)
437
438 @returns The same rthread object so as to facilitate stacking
439 @see detach
440 @see detached
441 @see start
442 @see stop
443 *///=========================================================================
444 rthread& start_detached() { // Use this instead of calling detach() separately to prevent the highly improbable instance of detaching a thread after it already finished
445 pthread_attr_t attr; // Attributes structure for pthreads
446 __rc_check_pthread(pthread_attr_init(&attr)); // Overwrite contents of "attr" structure
447 __rc_check_pthread(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
448 __rc_check_pthread(pthread_create(&__rthread_pthread_id, &attr, __rthread_run, this));
449 __rc_check_pthread(pthread_attr_destroy(&attr)); // Make sure that resources are properly deallocated
450 __rthread_detached = true;
451 __rthread_used++; // Increment thread run count
452 return *this;
453 } // -x- rthread& start_detached -x-
454
455 /*======================================================================*//**
456 @brief
457 Request the graceful termination of this rthread.
458 @warning
459 One side-effect of this method is to daemonize this rthread because stop()
460 serves as an alternative to the @ref join() method.
461 @note
462 It is the responsibility of developers to include periodic checks throughout
463 their code (assumedly at convenient points, which is common) by utilizing the
464 @ref running() method to determine whether to start winding things down.
465 @par Threads
466 This method is thread-safe.
467
468 @throws randolf::rex::xEINVAL Not a joinable rthread
469 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
470 couldn't be found (underlying pthread doesn't exist)
471
472 @returns The same rthread object so as to facilitate stacking
473 @see join
474 @see start
475 @see start_detached
476 *///=========================================================================
477 rthread& stop() noexcept { // This doesn't cancel the thread; it just indicates that it needs to stop running -- it's up to the code in the subclassed run() method to check periodically if it should stop by using the running() method
478 if (__rthread_running) {
479 detach();
480 __rthread_running = false;
481 }// -x- if __rthread_running -x-
482 return *this;
483 } // -x- rthread& stop -x-
484
485 private:
486 /*======================================================================*//**
487 @brief
488 This internal method starts the underlying pthread, and does the following:
489
490 1. catches any exceptions that the thread might have thrown (and, if any
491 were caught, in turn calls the @ref exceptions() method)
492
493 2. if this rthread is reuseable, makes preparations for re-use, including
494 calling the re-constructor method (if it was subclassed)
495
496 3. if this rthreads is not reuseable, affect its destruction
497
498 @par Threads
499 This method is thread-safe.
500 *///=========================================================================
501 static void* __rthread_run(
502 // Pointer to subclassed rthread object, which @c pthread_create() passes as type @c void*
503 void* arg) noexcept {
504
505 // --------------------------------------------------------------------------
506 // Alias (void*)arg to (rthread*)r so we can just write r-> instead of having
507 // write ((rthread*)arg)-> repeatedly (the compiler will effectively do this
508 // for us).
509 // --------------------------------------------------------------------------
510 rthread* r = (rthread*)arg;
511
512 // --------------------------------------------------------------------------
513 // Indicate that this rthread is running.
514 //
515 // We must set this before starting the underlying thread to avoid a
516 // potential race condition if there's a loop within the thread that first
517 // checks whether the thread is in a running state.
518 // --------------------------------------------------------------------------
519 r->__rthread_running = true; // This will be set to false in the destructor (or the reinitiator)
520
521 // --------------------------------------------------------------------------
522 // Call the subclassed run() method.
523 // --------------------------------------------------------------------------
524 try {
525 r->run();
526 } catch (std::exception& e) { // Handle a known exception
527 r->_rthread_exceptions(&e);
528 } catch (...) { // Handle an unknown exception
529 r->_rthread_exceptions(nullptr);
530 }
531
532 // --------------------------------------------------------------------------
533 // This is where it becomes possible to reuse an rthread. The amount of work
534 // required to just terminate a non-reusable (default) rthread is very little
535 // compared to preparing an rthread to be reuseable at this point, but the
536 // trade-off is worthwhile when performing a minimal (and probably partial)
537 // re-initialization whilst also avoiding re-instantiating an rthread object.
538 // --------------------------------------------------------------------------
539 if (r->__rthread_reuseable) { // This rthread is reuseable
540//std::cout << "Reusable thread" << std::endl; // Debug
541 r->__rthread_detached = false; // Reset internal flags because each thread will begin as not-detached
542 r->__rthread_running = false; // Clear the "running" status
543 try {
544 r->_rthread_reinitialize();
545 } catch (std::exception& e) { // Handle a known exception
546 r->_rthread_exceptions(&e);
547 } catch (...) { // Handle an unknown exception
548 r->_rthread_exceptions(nullptr);
549 }
550
551 // --------------------------------------------------------------------------
552 // If an rthread is not flagged as re-useable, then it needs to be deleted.
553 // --------------------------------------------------------------------------
554 } else { // This rthread is not reuseable
555//std::cout << "Deleted thread" << std::endl; // Debug
556 delete r; // Initiate destructor(s) since rthread is not reuseable
557 } // -x- if __rthread_reuseable -x-
558
559 return r; // Same as "return arg;" but in this case we're expressing intent by returning "r"
560 } // -x- void* __rthread_run -x-
561
562 }; // -x- class rthread -x-
563
564} // -x- namespace randolf -x-