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