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