Monado OpenXR Runtime
os_threading.h
Go to the documentation of this file.
1// Copyright 2019-2022, Collabora, Ltd.
2// Copyright 2024-2025, NVIDIA CORPORATION.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief Wrapper around OS threading native functions.
7 * @author Jakob Bornecrantz <jakob@collabora.com>
8 *
9 * @ingroup aux_os
10 */
11
12#pragma once
13
14#include "xrt/xrt_compiler.h"
15#include "xrt/xrt_config_os.h"
16
17#include "util/u_misc.h"
18
19#include "os/os_time.h"
20
21#if defined(XRT_OS_OSX)
22#include <pthread.h>
23#include <assert.h>
24
25#elif defined(XRT_OS_LINUX) || defined(XRT_ENV_MINGW)
26#include <pthread.h>
27#include <semaphore.h>
28#include <assert.h>
29#define OS_THREAD_HAVE_SETNAME
30#define OS_THREAD_HAVE_SEMAPHORE
31
32#elif defined(XRT_OS_WINDOWS)
33#include <pthread.h>
34#include <sched.h>
35#include <semaphore.h>
36#include <assert.h>
37#define OS_THREAD_HAVE_SETNAME
38#define OS_THREAD_HAVE_SEMAPHORE
39
40#else
41
42#error "OS not supported"
43
44#endif
45
46#ifdef __cplusplus
47extern "C" {
48#endif
49
50
51/*!
52 * @addtogroup aux_os
53 * @{
54 */
55
56/*
57 *
58 * Mutex
59 *
60 */
61
62/*!
63 * A wrapper around a native mutex.
64 */
66{
67 pthread_mutex_t mutex;
68
69#ifndef NDEBUG
70 bool initialized;
71 bool recursive;
72#endif
73};
74
75/*!
76 * Init.
77 *
78 * @public @memberof os_mutex
79 */
80static inline int
82{
83 assert(!om->initialized);
84#ifndef NDEBUG
85 om->initialized = true;
86 om->recursive = false;
87#endif
88 return pthread_mutex_init(&om->mutex, NULL);
89}
90
91/*!
92 * Lock.
93 *
94 * @public @memberof os_mutex
95 */
96static inline void
98{
99 assert(om->initialized);
100 pthread_mutex_lock(&om->mutex);
101}
102
103/*!
104 * Try to lock, but do not block.
105 *
106 * @public @memberof os_mutex
107 */
108static inline int
110{
111 assert(om->initialized);
112 return pthread_mutex_trylock(&om->mutex);
113}
114
115/*!
116 * Unlock.
117 *
118 * @public @memberof os_mutex
119 */
120static inline void
122{
123 assert(om->initialized);
124 pthread_mutex_unlock(&om->mutex);
125}
126
127/*!
128 * Clean up.
129 *
130 * @public @memberof os_mutex
131 */
132static inline void
134{
135 assert(om->initialized);
136 assert(!om->recursive);
137
138 pthread_mutex_destroy(&om->mutex);
139
140#ifndef NDEBUG
141 om->initialized = false;
142 om->recursive = false;
143#endif
144}
145
146/*!
147 * Init.
148 *
149 * @public @memberof os_mutex
150 */
151static inline int
153{
154 assert(!om->initialized);
155
156#ifndef NDEBUG
157 om->initialized = true;
158 om->recursive = true;
159#endif
160
161 pthread_mutexattr_t attr;
162 pthread_mutexattr_init(&attr);
163 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
164 int ret = pthread_mutex_init(&om->mutex, &attr);
165 pthread_mutexattr_destroy(&attr);
166
167 return ret;
168}
169
170/*!
171 * Clean up.
172 *
173 * @public @memberof os_mutex
174 */
175static inline void
177{
178 assert(om->initialized);
179 assert(om->recursive);
180
181 pthread_mutex_destroy(&om->mutex);
182
183#ifndef NDEBUG
184 om->initialized = false;
185 om->recursive = false;
186#endif
187}
188
189
190/*
191 *
192 * Conditional variable.
193 *
194 */
195
196/*!
197 * A wrapper around a native conditional variable.
198 */
200{
201 pthread_cond_t cond;
202#ifndef NDEBUG
203 bool initialized;
204#endif
205};
206
207/*!
208 * Init.
209 *
210 * @public @memberof os_cond
211 */
212static inline int
214{
215 assert(!oc->initialized);
216#ifndef NDEBUG
217 oc->initialized = true;
218#endif
219 return pthread_cond_init(&oc->cond, NULL);
220}
221
222/*!
223 * Signal.
224 *
225 * @public @memberof os_cond
226 */
227static inline void
229{
230 assert(oc->initialized);
231 pthread_cond_signal(&oc->cond);
232}
233
234/*!
235 * Broadcast (signal to multiple threads).
236 *
237 * @public @memberof os_cond
238 */
239static inline int
241{
242 assert(oc->initialized);
243 return pthread_cond_broadcast(&oc->cond);
244}
245
246/*!
247 * Wait.
248 *
249 * Be sure to call this in a loop, testing some other condition that you
250 * are actually waiting for, as condition variable waits are subject to
251 * spurious wakeups.
252 *
253 * Must be called with the mutex @p om locked.
254 *
255 * Once the wait begins, the mutex @p om is unlocked, to allow another
256 * thread access to change the thing you're monitoring. By the time this
257 * returns, you once again own the lock.
258 *
259 * @public @memberof os_cond
260 */
261static inline void
262os_cond_wait(struct os_cond *oc, struct os_mutex *om)
263{
264 assert(oc->initialized);
265 pthread_cond_wait(&oc->cond, &om->mutex);
266}
267
268/*!
269 * Clean up.
270 *
271 * @public @memberof os_cond
272 */
273static inline void
275{
276 assert(oc->initialized);
277 pthread_cond_destroy(&oc->cond);
278#ifndef NDEBUG
279 oc->initialized = false;
280#endif
281}
282
283
284
285/*
286 *
287 * Thread.
288 *
289 */
290
291/*!
292 * A wrapper around a native thread.
293 */
295{
296 pthread_t thread;
297};
298
299/*!
300 * Run function.
301 *
302 * @public @memberof os_thread
303 */
304typedef void *(*os_run_func_t)(void *);
305
306/*!
307 * Init.
308 *
309 * @public @memberof os_thread
310 */
311static inline int
313{
314 return 0;
315}
316
317/*!
318 * Start thread.
319 *
320 * @public @memberof os_thread
321 */
322static inline int
323os_thread_start(struct os_thread *ost, os_run_func_t func, void *ptr)
324{
325 return pthread_create(&ost->thread, NULL, func, ptr);
326}
327
328/*!
329 * Join.
330 *
331 * @public @memberof os_thread
332 */
333static inline void
335{
336 void *retval;
337
338 pthread_join(ost->thread, &retval);
339 U_ZERO(&ost->thread);
340}
341
342/*!
343 * Destruction.
344 *
345 * @public @memberof os_thread
346 */
347static inline void
349{}
350
351/*!
352 * Make a best effort to name our thread.
353 *
354 * @public @memberof os_thread
355 */
356static inline void
357os_thread_name(struct os_thread *ost, const char *name)
358{
359#ifdef OS_THREAD_HAVE_SETNAME
360 pthread_setname_np(ost->thread, name);
361#else
362 (void)ost;
363 (void)name;
364#endif
365}
366
367#ifdef OS_THREAD_HAVE_SEMAPHORE
368/*
369 *
370 * Semaphore.
371 *
372 */
373
374/*!
375 * A wrapper around a native semaphore.
376 */
378{
379 sem_t sem;
380};
381
382/*!
383 * Init.
384 *
385 * @public @memberof os_semaphore
386 */
387static inline int
388os_semaphore_init(struct os_semaphore *os, int count)
389{
390 return sem_init(&os->sem, 0, count);
391}
392
393/*!
394 * Release.
395 *
396 * @public @memberof os_semaphore
397 */
398static inline void
400{
401 sem_post(&os->sem);
402}
403
404/*!
405 * Set @p ts to the current time, plus the timeout_ns value.
406 *
407 * Intended for use by the threading code only: the timestamps are not interchangeable with other sources of time.
408 *
409 * @public @memberof os_semaphore
410 */
411static inline int
412os_semaphore_get_realtime_clock(struct timespec *ts, uint64_t timeout_ns)
413{
414#if defined(XRT_OS_WINDOWS) && !defined(XRT_ENV_MINGW)
415 struct timespec relative;
416 os_ns_to_timespec(timeout_ns, &relative);
417 pthread_win32_getabstime_np(ts, &relative);
418 return 0;
419#else
420 struct timespec now;
421 if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
422 assert(false);
423 return -1;
424 }
425 uint64_t now_ns = os_timespec_to_ns(&now);
426 uint64_t when_ns = timeout_ns + now_ns;
427
428 os_ns_to_timespec(when_ns, ts);
429 return 0;
430#endif
431}
432
433/*!
434 * Wait, if @p timeout_ns is zero then waits forever.
435 *
436 * @public @memberof os_semaphore
437 */
438static inline void
439os_semaphore_wait(struct os_semaphore *os, uint64_t timeout_ns)
440{
441 if (timeout_ns == 0) {
442 sem_wait(&os->sem);
443 return;
444 }
445
446 struct timespec abs_timeout;
447 if (os_semaphore_get_realtime_clock(&abs_timeout, timeout_ns) == -1) {
448 assert(false);
449 }
450
451 sem_timedwait(&os->sem, &abs_timeout);
452}
453
454/*!
455 * Clean up.
456 *
457 * @public @memberof os_semaphore
458 */
459static inline void
461{
462 sem_destroy(&os->sem);
463}
464#endif // OS_THREAD_HAVE_SEMAPHORE
465
466
467/*
468 *
469 * Fancy helper.
470 *
471 */
472
473/*!
474 * All in one helper that handles locking, waiting for change and starting a
475 * thread.
476 */
478{
479 pthread_t thread;
480 pthread_mutex_t mutex;
481 pthread_cond_t cond;
482
483 bool initialized;
484 bool running;
485};
486
487/*!
488 * Initialize the thread helper.
489 *
490 * @public @memberof os_thread_helper
491 */
492static inline int
494{
495 U_ZERO(oth);
496
497 int ret = pthread_mutex_init(&oth->mutex, NULL);
498 if (ret != 0) {
499 return ret;
500 }
501
502 ret = pthread_cond_init(&oth->cond, NULL);
503 if (ret) {
504 pthread_mutex_destroy(&oth->mutex);
505 return ret;
506 }
507 oth->initialized = true;
508
509 return 0;
510}
511
512/*!
513 * Start the internal thread.
514 *
515 * @public @memberof os_thread_helper
516 */
517static inline int
519{
520 pthread_mutex_lock(&oth->mutex);
521
522 assert(oth->initialized);
523 if (oth->running) {
524 pthread_mutex_unlock(&oth->mutex);
525 return -1;
526 }
527
528 int ret = pthread_create(&oth->thread, NULL, func, ptr);
529 if (ret != 0) {
530 pthread_mutex_unlock(&oth->mutex);
531 return ret;
532 }
533
534 oth->running = true;
535
536 pthread_mutex_unlock(&oth->mutex);
537
538 return 0;
539}
540
541/*!
542 * @brief Signal from within the thread that we are stopping.
543 *
544 * Call with mutex unlocked - it takes and releases the lock internally.
545 *
546 * @public @memberof os_thread_helper
547 */
548static inline int
550{
551 // The fields are protected.
552 pthread_mutex_lock(&oth->mutex);
553 assert(oth->initialized);
554
555 // Report we're stopping the thread.
556 oth->running = false;
557
558 // Wake up any waiting thread.
559 pthread_cond_signal(&oth->cond);
560
561 // No longer need to protect fields.
562 pthread_mutex_unlock(&oth->mutex);
563
564 return 0;
565}
566
567/*!
568 * @brief Stop the thread and wait for it to exit.
569 *
570 * Call with mutex unlocked - it takes and releases the lock internally.
571 *
572 * @public @memberof os_thread_helper
573 */
574static inline int
576{
577 void *retval = NULL;
578
579 // The fields are protected.
580 pthread_mutex_lock(&oth->mutex);
581 assert(oth->initialized);
582
583 if (!oth->running) {
584 // it already exited
585 pthread_mutex_unlock(&oth->mutex);
586 return 0;
587 }
588
589 // Stop the thread.
590 oth->running = false;
591
592 // Wake up the thread if it is waiting.
593 pthread_cond_signal(&oth->cond);
594
595 // No longer need to protect fields.
596 pthread_mutex_unlock(&oth->mutex);
597
598 // Wait for thread to finish.
599 pthread_join(oth->thread, &retval);
600
601 return 0;
602}
603
604/*!
605 * Destroy the thread helper, externally synchronizable.
606 *
607 * Integrates a call to @ref os_thread_helper_stop_and_wait, so you may just call this for full cleanup
608 *
609 * @public @memberof os_thread_helper
610 */
611static inline void
613{
614 assert(oth->initialized);
615 // Stop the thread.
617
618 // Destroy resources.
619 pthread_mutex_destroy(&oth->mutex);
620 pthread_cond_destroy(&oth->cond);
621 oth->initialized = false;
622}
623
624/*!
625 * Lock the helper.
626 *
627 * @public @memberof os_thread_helper
628 */
629static inline void
631{
632 pthread_mutex_lock(&oth->mutex);
633}
634
635/*!
636 * Unlock the helper.
637 *
638 * @public @memberof os_thread_helper
639 */
640static inline void
642{
643 pthread_mutex_unlock(&oth->mutex);
644}
645
646/*!
647 * Is the thread running, or supposed to be running.
648 *
649 * Call with mutex unlocked - it takes and releases the lock internally.
650 * If you already have a lock, use os_thread_helper_is_running_locked().
651 *
652 * @public @memberof os_thread_helper
653 */
654static inline bool
656{
658 assert(oth->initialized);
659 bool ret = oth->running;
661 return ret;
662}
663
664/*!
665 * Is the thread running, or supposed to be running.
666 *
667 * Must be called with the helper locked.
668 * If you don't have the helper locked for some other reason already,
669 * you can use os_thread_helper_is_running()
670 *
671 * @public @memberof os_thread_helper
672 */
673static inline bool
675{
676 return oth->running;
677}
678
679/*!
680 * Wait for a signal.
681 *
682 * Be sure to call this in a loop, testing some other condition that you
683 * are actually waiting for, as this is backed by a condition variable
684 * wait and is thus subject to spurious wakeups.
685 *
686 * Must be called with the helper locked.
687 *
688 * As this wraps a cond-var wait, once the wait begins, the helper is
689 * unlocked, to allow another thread access to change the thing you're
690 * monitoring. By the time this returns, you once again own the lock.
691 *
692 * @public @memberof os_thread_helper
693 */
694static inline void
696{
697 pthread_cond_wait(&oth->cond, &oth->mutex);
698}
699
700/*!
701 * Signal a waiting thread to wake up.
702 *
703 * Must be called with the helper locked.
704 *
705 * @public @memberof os_thread_helper
706 */
707static inline void
709{
710 pthread_cond_signal(&oth->cond);
711}
712
713/*!
714 * Make a best effort to name our thread.
715 *
716 * @public @memberof os_thread_helper
717 */
718static inline void
719os_thread_helper_name(struct os_thread_helper *oth, const char *name)
720{
721#ifdef OS_THREAD_HAVE_SETNAME
722 pthread_setname_np(oth->thread, name);
723#else
724 (void)oth;
725 (void)name;
726#endif
727}
728
729/*!
730 * @}
731 */
732
733
734#ifdef __cplusplus
735} // extern "C"
736#endif
737
738
739#ifdef __cplusplus
740namespace xrt::auxiliary::os {
741
742
743//! A class owning an @ref os_mutex
744class Mutex
745{
746public:
747 //! Construct a mutex
748 Mutex() noexcept
749 {
750 os_mutex_init(&inner_);
751 }
752 //! Destroy a mutex when it goes out of scope
753 ~Mutex()
754 {
755 os_mutex_destroy(&inner_);
756 }
757
758 //! Block until the lock can be taken.
759 void
760 lock() noexcept
761 {
762 os_mutex_lock(&inner_);
763 }
764
765 //! Take the lock and return true if possible, but do not block
766 bool
767 try_lock() noexcept
768 {
769 return 0 == os_mutex_trylock(&inner_);
770 }
771
772 //! Release the lock
773 void
774 unlock() noexcept
775 {
776 os_mutex_unlock(&inner_);
777 }
778
779 //! Get a pointer to the owned mutex: do not delete it!
780 os_mutex *
781 get_inner() noexcept
782 {
783 return &inner_;
784 }
785
786 // Do not copy or delete these mutexes.
787 Mutex(Mutex const &) = delete;
788 Mutex(Mutex &&) = delete;
789 Mutex &
790 operator=(Mutex const &) = delete;
791 Mutex &
792 operator=(Mutex &&) = delete;
793
794private:
795 os_mutex inner_{};
796};
797
798} // namespace xrt::auxiliary::os
799
800#endif // __cplusplus
static int64_t os_timespec_to_ns(const struct timespec *spec)
Convert a timespec struct to nanoseconds.
Definition: os_time.h:273
static void os_ns_to_timespec(int64_t ns, struct timespec *spec)
Convert an nanosecond integer to a timespec struct.
Definition: os_time.h:282
static void os_mutex_recursive_destroy(struct os_mutex *om)
Clean up.
Definition: os_threading.h:176
static bool os_thread_helper_is_running_locked(struct os_thread_helper *oth)
Is the thread running, or supposed to be running.
Definition: os_threading.h:674
static int os_cond_broadcast(struct os_cond *oc)
Broadcast (signal to multiple threads).
Definition: os_threading.h:240
static int os_thread_helper_start(struct os_thread_helper *oth, os_run_func_t func, void *ptr)
Start the internal thread.
Definition: os_threading.h:518
static int os_mutex_init(struct os_mutex *om)
Init.
Definition: os_threading.h:81
static int os_semaphore_get_realtime_clock(struct timespec *ts, uint64_t timeout_ns)
Set ts to the current time, plus the timeout_ns value.
Definition: os_threading.h:412
static void os_thread_helper_signal_locked(struct os_thread_helper *oth)
Signal a waiting thread to wake up.
Definition: os_threading.h:708
static void os_mutex_lock(struct os_mutex *om)
Lock.
Definition: os_threading.h:97
static void os_cond_wait(struct os_cond *oc, struct os_mutex *om)
Wait.
Definition: os_threading.h:262
static int os_thread_helper_stop_and_wait(struct os_thread_helper *oth)
Stop the thread and wait for it to exit.
Definition: os_threading.h:575
static void os_thread_destroy(struct os_thread *ost)
Destruction.
Definition: os_threading.h:348
static void os_semaphore_release(struct os_semaphore *os)
Release.
Definition: os_threading.h:399
static void os_thread_join(struct os_thread *ost)
Join.
Definition: os_threading.h:334
static int os_thread_start(struct os_thread *ost, os_run_func_t func, void *ptr)
Start thread.
Definition: os_threading.h:323
static int os_thread_helper_signal_stop(struct os_thread_helper *oth)
Signal from within the thread that we are stopping.
Definition: os_threading.h:549
static int os_mutex_recursive_init(struct os_mutex *om)
Init.
Definition: os_threading.h:152
static void os_thread_name(struct os_thread *ost, const char *name)
Make a best effort to name our thread.
Definition: os_threading.h:357
static void os_thread_helper_wait_locked(struct os_thread_helper *oth)
Wait for a signal.
Definition: os_threading.h:695
static int os_semaphore_init(struct os_semaphore *os, int count)
Init.
Definition: os_threading.h:388
static int os_thread_init(struct os_thread *ost)
Init.
Definition: os_threading.h:312
static void os_cond_signal(struct os_cond *oc)
Signal.
Definition: os_threading.h:228
static int os_mutex_trylock(struct os_mutex *om)
Try to lock, but do not block.
Definition: os_threading.h:109
static void os_thread_helper_unlock(struct os_thread_helper *oth)
Unlock the helper.
Definition: os_threading.h:641
static void os_thread_helper_name(struct os_thread_helper *oth, const char *name)
Make a best effort to name our thread.
Definition: os_threading.h:719
static int os_thread_helper_init(struct os_thread_helper *oth)
Initialize the thread helper.
Definition: os_threading.h:493
static int os_cond_init(struct os_cond *oc)
Init.
Definition: os_threading.h:213
static void os_thread_helper_destroy(struct os_thread_helper *oth)
Destroy the thread helper, externally synchronizable.
Definition: os_threading.h:612
static bool os_thread_helper_is_running(struct os_thread_helper *oth)
Is the thread running, or supposed to be running.
Definition: os_threading.h:655
static void os_mutex_unlock(struct os_mutex *om)
Unlock.
Definition: os_threading.h:121
static void os_mutex_destroy(struct os_mutex *om)
Clean up.
Definition: os_threading.h:133
void *(* os_run_func_t)(void *)
Run function.
Definition: os_threading.h:304
static void os_semaphore_destroy(struct os_semaphore *os)
Clean up.
Definition: os_threading.h:460
static void os_thread_helper_lock(struct os_thread_helper *oth)
Lock the helper.
Definition: os_threading.h:630
static void os_cond_destroy(struct os_cond *oc)
Clean up.
Definition: os_threading.h:274
static void os_semaphore_wait(struct os_semaphore *os, uint64_t timeout_ns)
Wait, if timeout_ns is zero then waits forever.
Definition: os_threading.h:439
#define U_ZERO(PTR)
Zeroes the correct amount of memory based on the type pointed-to by the argument.
Definition: u_misc.h:68
Wrapper around OS native time functions.
A wrapper around a native conditional variable.
Definition: os_threading.h:200
A wrapper around a native mutex.
Definition: os_threading.h:66
A wrapper around a native semaphore.
Definition: os_threading.h:378
All in one helper that handles locking, waiting for change and starting a thread.
Definition: os_threading.h:478
A wrapper around a native thread.
Definition: os_threading.h:295
Definition: u_worker.c:38
Very small misc utils.
Header holding common defines.
Auto detect OS and certain features.