12 /*======================================================================*//**
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 (a virtual method for optionally catching and handling
18 all exceptions is also provided).
20 POSIX threads are the foundation of the rthread class, and the resulting C++
21 interface will probably seem familiar to those who are familliar with Java.
24 This is meant to be a safe and easy-to-use thread class for C++, which is
25 intended to make thread sub-classing straight-forward in a logical manner for
26 developers. Some of the key features are:
28 - Customizable constructor that doesn't automatically start the thread
29 - Better control with the @ref start() method to start a previously
31 - Abstract @ref run() method (required)
32 - This method contains the code that will be run in its own thread
33 - Customizable destructor that occurs only after the thread ends (which
34 is particularly useful for daemon threads {a.k.a., detached threads})
35 - Customizable exception handler method (just subclass it to use it)
36 - Most methods are designed to be thread-safe (e.g., status methods)
38 Some advanced features are planned that exceed what the basic thread
39 functions provide, but are also needed:
41 - thread groups (separate class), possibly with support for including
42 threads in multiple groups
43 - thread pool (separate class)
46 I created this class to make it easier to write internet server daemons. I
47 started out using C-style thread functions (because C++ doesn't come with a
48 thread class that can be sub-classed), and even with std::jthreads I ran into
49 a serious problem with the thread's destructor executing while the thread was
50 still runningd (the code that started it went out of scope) -- this is not
51 what I expected because the running thread should really be an additional
52 criteria for thread destruction (this is a serious concern since the
53 premature destruction of a running thread usually results in a segmentation
54 fault or other unpredictable behaviour that has the potential to cause the
55 Operating System to exhibit other strange behaviours or crash entire).
57 After looking for existing solutions (none of which resolved the ineherent
58 problems with threads in C++), I embarked on creating this rthread class,
59 which was highly educational but also wasn't difficult. The end result is a
60 small class that's easy to maintain, eliminates the reliability concerns, and
61 is easy to use primarily because C++ thoroughly supports subclassing.
63 My background in programming began when I was a young child, teaching myself
64 BASIC and then machine language (when I found BASIC to be too limited) before
65 moving on to other languages like Perl and Java many years later. Eventually
66 I circled around to C (which I chose to learn the hard way by writing some
67 PostgreSQL extensions) and then C++ a few years after that. I have a lot of
68 experience with programming threadded applications on a variety of platforms
69 that support pthreads (including Novell's NetWare), running application code
70 I created that ran hundreds of thousands of threads successfully serving a
71 similar number of users globally without slowdowns or crashes.
73 2022-Oct-22 rthread v1.00 by Randolf Richardson.
74 *///=========================================================================
77 pthread_t __rthread_pthread_id = 0;
78 std::atomic_bool __rthread_running = false;
79 std::atomic_bool __rthread_detached = false;
80 std::atomic_bool __rthread_death = false; // Destructor changes this to TRUE
81 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
82 std::atomic_ulong __rthread_used = 0; // Total number of times this thread was started
84 /*======================================================================*//**
85 Return Code check, and throws an rthread-specific exception if an error
86 occurred, which is indicated by @c -1 (a lot of example code shows @c < @c 1
87 comparisons, but we specifically test for @c -1 because the documentation
88 clearly states @c -1. This is important because a system that can support an
89 extremely high number of socket handles might, in theory, assign handles with
90 values that get interpreted as negative integers, and so less-than-zero tests
91 would result in dropped packets or dropped sockets (any such socket code that
92 allocates such handles obviously must not ever allocate @c -1 since this
93 would definitely be misinterpreted as an error).
95 If rc is not @c -1, then it is simply returned as is.
97 If n is nonzero and rc is 0, then n will override errno. (This option is
98 provided to accomodate the few socket library functions that return values
99 that are never errors, and expect the developer to rely on other means of
100 detecting whether an error occurred. This is an example of where Object
101 Oriented Programming is helpful in making things better.)
102 @returns Original value of @c rc (if no exceptions were thrown)
103 *///=========================================================================
104 const int __rc_check_pthread(
107 /// Exception handling flags (see @ref rex::REX_FLAGS for details)
108 const int flags = rex::rex::REX_FLAGS::REX_DEFAULT) {
110 randolf::rex::mk_exception("", rc, flags); // This function doesn't return (this code ends here)
113 }; // -x- int __rc_check_pthread -x-
116 /*======================================================================*//**
119 You can subclass this constructor and/or create parameterized constructors,
120 any of which may throw exceptions (even though this default one doesn't).
122 This is particularly useful for performing pre-run setup before starting the
123 thread (use the @ref run() and @ref start() methods for details), especially
124 with threads that start running in response to external events (such as user
125 interaction events, incoming internet socket connections, etc.).
126 *///=========================================================================
127 rthread() noexcept {}; // -x- constructor rthread -x-
129 /*======================================================================*//**
131 Default destructor. Called only after two conditions are met:
132 1. this rthread has gone out of scope
133 2. termination of the underlying pthread (this is different from the
134 standard C++ implementation of threads and jthreads, which don't have
137 Exceptions are handled by the @ref _rthread_exceptions() method before this
138 destructor is activated.
140 You can add your own destructor to safely ensure that files and sockets are
141 closed, and that resources are freed, deallocated, deleted, etc., which is
142 essential to preventing various types of resource leaks if your thread code
143 (in the @ref run() method) exited early due to an unexpected exception (it is
144 the developer's responsibility to ensure resources are managed properly, and
145 this destructor provides a means of handling this reliably).
147 Logging, re-queuing, semaphore completions, etc., can also be handled in this
148 method because it runs after the termination of the underlying pthread.
150 Do not throw exceptions from this method. If you're relying on a function or
151 a class that might throw exceptions (intended or otherwise), then you need to
152 take care to at least utilize a final try/catch block for @c std::exception.
153 *///=========================================================================
154 virtual ~rthread() noexcept { // Destructor (subclass destructor will run first if it's defined)
155 __rthread_death = true;
156 if (__rthread_running && !__rthread_detached) {
157 pthread_detach(__rthread_pthread_id);
158 __rthread_detached = true;
159 } // -x- if __rthread_running -x-
160 if (__rthread_running) pthread_cancel(__rthread_pthread_id); // This must be the very last; any commands after this will cause: terminate called without an active exception
161 // TODO: pthread_setcancelstate, pthread_setcanceltype, and pthread_testcancel
162 }; // -x- destructor rthread -x-
164 /*======================================================================*//**
166 Final rthread exception handler.
167 If an uncaught exception is thrown from the @ref run() method in a running
168 rthread, then it will be handled by this method (which is still technically
169 running on the underlying pthread). Override this method to handle known and
170 unknown exceptions gracefully, before the destructor (or reinitiator) runs.
172 If @c e is @c nullptr it means an unknown exception was thrown (e.g.,
173 internally we literally caught `(...)` (other exception), in which case
174 you'll need to use @c std::current_exception() to access it.
176 For a reuseable rthread, the @ref running status will be set to false during
177 @ref _rthread_reinitialize execution, which can be useful for handling those
178 exceptions separately within this code (by testing for the @ref running()
180 *///=========================================================================
181 virtual void _rthread_exceptions(
182 /// Exceptions (see warning, immediately above)
183 std::exception* e) noexcept {}; // -x- void rthread_exceptions -x-
185 /*======================================================================*//**
187 Re-initialize internal variables, refresh resources, etc., before re-running
188 a @ref reuseable rthread.
189 When utilizing the reuseable feature, this method can be thought of as having
190 similar functionality to that of the constructor, except that it is used to
191 do the following to bring internal variables and resources back to the same
192 state that the constructor originally initialized them to, and thus reducing
193 construction overhead (some variables may not need re-initializing, which can
194 also help to further-reduce overhead):
195 - close any open files, sockets, etc., that need to be closed (or reset
196 file pointers instead of re-opening files)
197 - reset/clear variables and memory structures to their expected defaults
198 (or free-and-allocate resources that can't be reset or cleared reliably)
199 - re-add @c this to a thread pool or concurrent queue of ready rthreads
202 If there is any clean-up, logging, etc., peformed in the destructor that also
203 needs to be performed here, the best approach is to create a private method
204 for those particular actions and then call it from this method as well as the
205 destructor for better operational consistency.
207 *///=========================================================================
208 virtual void _rthread_reinitialize() noexcept {}; // -x- void rthread_reinitialize -x-
210 /*======================================================================*//**
212 Daemonize this rthread.
214 If this rthread was previously detached, then this method has no effect (but
215 won't cause the randolf::rex::xEINVAL exception to be thrown; there may be
216 other reasons for this exception to be thrown).
218 This method is thread-safe.
220 @throws randolf::rex::xEINVAL Not a joinable rthread
221 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
222 couldn't be found (underlying pthread doesn't exist)
224 @returns The same rthread object so as to facilitate stacking
226 *///=========================================================================
228 if (__rthread_running && !__rthread_detached) {
229 __rc_check_pthread(pthread_detach(__rthread_pthread_id));
230 __rthread_detached = true;
231 } // -x- if __rthread_running -x-
233 }; // -x- rthread* detach -x-
235 /*======================================================================*//**
237 Find out whether this rthread is daemonized.
239 This method is thread-safe.
240 @returns TRUE = daemonized
241 @returns FALSE = not daemonized
242 *///=========================================================================
243 bool detached() noexcept {
244 return __rthread_detached;
245 }; // -x- bool detached -x-
247 /*======================================================================*//**
249 Join this rthread to the current thread that called this method. (This is
250 how non-daemonized threads are normally terminated.)
252 This method blocks until this rthread's underlying pthread is terminated.
254 This method is thread-safe.
256 @throws randolf::rex::xEDEADLK Deadlock detected because two threads are
257 queued to join with each other, or the current rthread is trying to
259 @throws randolf::rex::xEINVAL Not a joinable rthread
260 @throws randolf::rex::xEINVAL A different thread is already queued to join
262 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
263 couldn't be found (underlying pthread doesn't exist)
265 @returns The same rthread object so as to facilitate stacking
267 *///=========================================================================
269 if (__rthread_running) {
270 __rthread_running = false; // Do this before pthread_join to signal an immediate shutdown
271 __rc_check_pthread(pthread_join(__rthread_pthread_id, NULL));
272 __rthread_detached = true; // TODO: Determine if this should actually be set to FALSE
273 } // -x- if __rthread_running -x-
275 }; // -x- rthread* join -x-
277 /*======================================================================*//**
279 Find out what the underlying @c pthread_id is.
281 Advanced developers may want easy access to this ID for a variety of reasons.
284 This method is not thread-safe.
286 @returns ID number of underlying pthread (0 = not assigned, which is what you
287 should expect to find if this rthread didn't @ref start() yet)
288 *///=========================================================================
289 pthread_t pthread_id() noexcept {
290 return __rthread_pthread_id;
291 }; // -x- pthread_t pthread_id -x-
293 /*======================================================================*//**
295 Find out whether this rthread can be re-used.
297 This method is thread-safe.
298 @returns TRUE = re-useable
299 @returns FALSE = not re-useable
300 *///=========================================================================
301 bool reuseable() noexcept {
302 return __rthread_reuseable;
303 }; // -x- bool reuseable -x-
305 /*======================================================================*//**
307 Find out whether this rthread can be re-used.
309 This method is thread-safe.
310 @returns The same rthread object so as to facilitate stacking
311 @see _rthread_reinitialize
312 *///=========================================================================
314 /// TRUE = Set this rthread to be re-useable@n
315 /// FALSE = Set this rthread to not be re-useable
316 bool flag) noexcept {
317 __rthread_reuseable = flag;
319 }; // -x- rthread* reuseable -x-
321 /*======================================================================*//**
323 Thread functionality (subclassing this method is required).
325 Do not call this method directly. To properly begin rthread execution, use
326 either of the @ref start() or @ref start_detached() methods.
327 When subclassing rthread, you need to override this method with the code that
328 is to be executed as a separate thread. (You don't have to move all of your
329 code into this method; in fact, you can simply use this method to call out to
330 code elsewhere, or call methods on instantiated classes that were referenced
331 during the instantiation of your overriding rthread() constructor, etc.)
333 Minimally, this is the only method that's required when sub-classing rthread.
336 *///=========================================================================
337 virtual void run() = 0; // Not requiring this method in the subclass is completely and utterly pointless
339 /*======================================================================*//**
341 Find out how many times this rthread was started.
342 This method is only really meaningful for @ref reuseable rthreads.
344 This method is thread-safe.
346 @see _rthread_reinitialize
348 *///=========================================================================
349 ulong run_count() noexcept {
350 return __rthread_used;
351 }; // -x- ulong run_count -x-
353 /*======================================================================*//**
355 Find out whether this rthread is actively running.
357 This method is thread-safe.
358 @returns TRUE = running
359 @returns FALSE = not running
362 *///=========================================================================
363 bool running() noexcept {
364 return __rthread_running;
365 }; // -x- bool running -x-
367 /*======================================================================*//**
369 Commence execution of the @c run() method as a separate rthread.
371 This method is thread-safe.
373 @throws randolf::rex::xEWOULDBLOCK (xEAGAIN) Insufficient resources to start
374 the underlying pthread
375 @throws randolf::rex::xEWOULDBLOCK (xEAGAIN) Maximum number-of-threads limit
378 @returns The same rthread object so as to facilitate stacking
380 *///=========================================================================
382 __rc_check_pthread(pthread_create(&__rthread_pthread_id, NULL, __rthread_run, this));
383 __rthread_used++; // Increment thread run count
385 }; // -x- rsocket* start -x-
387 /*======================================================================*//**
389 Commence execution of the @c run() method as a separate thread in a
392 This method is thread-safe.
394 @throws randolf::rex::xEDEADLK Deadlock detected because two threads are
395 queued to join with each other, or the current rthread is trying to
397 @throws randolf::rex::xEINVAL Not a joinable rthread
398 @throws randolf::rex::xEINVAL A different thread is already queued to join
400 @throws randolf::rex::xENOMEM Insufficient memory (on Linux this normally
401 always succeeds, but there's not guarantee as the community suggests
402 that this could change in the future)
403 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
404 couldn't be found (underlying pthread doesn't exist)
406 @returns The same rthread object so as to facilitate stacking
408 *///=========================================================================
409 rthread* start_detached() { // Use this instead of calling detach() separately to prevent the highly improbable instance of detaching a thread after it already finished
411 __rc_check_pthread(pthread_attr_init(&attr));
412 __rc_check_pthread(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
413 __rc_check_pthread(pthread_create(&__rthread_pthread_id, &attr, __rthread_run, this));
414 __rthread_detached = true;
415 __rthread_used++; // Increment thread run count
417 }; // -x- rthread* start_detached -x-
419 /*======================================================================*//**
421 Request the graceful termination of this rthread.
423 One side-effect of this method is to daemonize this rthread because stop()
424 serves as an alternative to the @ref join() method.
426 It is the responsibility of developers to include periodic checks throughout
427 their code (assumedly at convenient points, which is common) by utilizing the
428 @ref running() method to determine whether to start winding things down.
430 This method is thread-safe.
432 @throws randolf::rex::xEINVAL Not a joinable rthread
433 @throws randolf::rex::xESRCH A pthread with the underlying @c pthread_id
434 couldn't be found (underlying pthread doesn't exist)
436 @returns The same rthread object so as to facilitate stacking
438 *///=========================================================================
439 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
440 if (__rthread_running) {
442 __rthread_running = false;
443 }// -x- if __rthread_running -x-
445 }; // -x- rthread* stop -x-
448 /*======================================================================*//**
450 This internal method starts the underlying pthread, and does the following:
452 1. catches any exceptions that the thread might have thrown (and, if any
453 were caught, in turn calls the @ref exceptions() method)
455 2. if this rthread is reuseable, makes preparations for re-use, including
456 calling the re-constructor method (if it was subclassed)
458 3. if this rthreads is not reuseable, affect its destruction
461 This method is thread-safe.
462 *///=========================================================================
463 static void* __rthread_run(
464 // Pointer to subclassed rthread object, which @c pthread_create() passes as type @c void*
465 void* arg) noexcept {
467 // --------------------------------------------------------------------------
468 // Alias (void*)arg to (rthread*)r so we can just write r-> instead of having
469 // write ((rthread*)arg)-> repeatedly (the compiler will effectively do this
471 // --------------------------------------------------------------------------
472 rthread* r = (rthread*)arg;
474 // --------------------------------------------------------------------------
475 // Indicate that this rthread is running.
477 // We must set this before starting the underlying thread to avoid a
478 // potential race condition if there's a loop within the thread that first
479 // checks whether the thread is in a running state.
480 // --------------------------------------------------------------------------
481 r->__rthread_running = true; // This will be set to false in the destructor (or the reinitiator)
483 // --------------------------------------------------------------------------
484 // Call the subclassed run() method.
485 // --------------------------------------------------------------------------
488 } catch (std::exception* e) { // Handle a known exception
489 r->_rthread_exceptions(e);
490 } catch (...) { // Handle an unknown exception
491 r->_rthread_exceptions(nullptr);
494 // --------------------------------------------------------------------------
495 // This is where it becomes possible to reuse an rthread. The amount of work
496 // required to just terminate a non-reusable (default) rthread is very little
497 // compared to preparing an rthread to be reuseable at this point, but the
498 // trade-off is worthwhile when performing a minimal (and probably partial)
499 // re-initialization whilst also avoiding re-instantiating an rthread object.
500 // --------------------------------------------------------------------------
501 if (r->__rthread_reuseable) { // This rthread is reuseable
502 r->__rthread_detached = false; // Reset internal flags because each thread will begin as not-detached
503 r->__rthread_running = false; // Clear the "running" status
505 r->_rthread_reinitialize();
506 } catch (std::exception* e) { // Handle a known exception
507 r->_rthread_exceptions(e);
508 } catch (...) { // Handle an unknown exception
509 r->_rthread_exceptions(nullptr);
511 } else { // This rthread is not reuseable
512 delete r; // Initiate destructor(s) since rthread is not reuseable
513 } // -x- if __rthread_reuseable -x-
515 return r; // Same as "return arg;" but in this case we're expressing intent by returning "r"
516 }; // -x- void* __rthread_run -x-
518 }; // -x- class rthread -x-
520} // -x- namespace randolf -x-