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 * Wait.
225 *
226 * Be sure to call this in a loop, testing some other condition that you
227 * are actually waiting for, as condition variable waits are subject to
228 * spurious wakeups.
229 *
230 * Must be called with the mutex @p om locked.
231 *
232 * Once the wait begins, the mutex @p om is unlocked, to allow another
233 * thread access to change the thing you're monitoring. By the time this
234 * returns, you once again own the lock.
235 *
236 * @public @memberof os_cond
237 */
238static inline void
239os_cond_wait(struct os_cond *oc, struct os_mutex *om)
240{
241 assert(oc->initialized);
242 pthread_cond_wait(&oc->cond, &om->mutex);
243}
244
245/*!
246 * Clean up.
247 *
248 * @public @memberof os_cond
249 */
250static inline void
252{
253 assert(oc->initialized);
254 pthread_cond_destroy(&oc->cond);
255#ifndef NDEBUG
256 oc->initialized = false;
257#endif
258}
259
260
261
262/*
263 *
264 * Thread.
265 *
266 */
267
268/*!
269 * A wrapper around a native thread.
270 */
272{
273 pthread_t thread;
274};
275
276/*!
277 * Run function.
278 *
279 * @public @memberof os_thread
280 */
281typedef void *(*os_run_func_t)(void *);
282
283/*!
284 * Init.
285 *
286 * @public @memberof os_thread
287 */
288static inline int
290{
291 return 0;
292}
293
294/*!
295 * Start thread.
296 *
297 * @public @memberof os_thread
298 */
299static inline int
300os_thread_start(struct os_thread *ost, os_run_func_t func, void *ptr)
301{
302 return pthread_create(&ost->thread, NULL, func, ptr);
303}
304
305/*!
306 * Join.
307 *
308 * @public @memberof os_thread
309 */
310static inline void
312{
313 void *retval;
314
315 pthread_join(ost->thread, &retval);
316 U_ZERO(&ost->thread);
317}
318
319/*!
320 * Destruction.
321 *
322 * @public @memberof os_thread
323 */
324static inline void
326{}
327
328/*!
329 * Make a best effort to name our thread.
330 *
331 * @public @memberof os_thread
332 */
333static inline void
334os_thread_name(struct os_thread *ost, const char *name)
335{
336#ifdef OS_THREAD_HAVE_SETNAME
337 pthread_setname_np(ost->thread, name);
338#else
339 (void)ost;
340 (void)name;
341#endif
342}
343
344/*
345 *
346 * Semaphore.
347 *
348 */
349
350/*!
351 * A wrapper around a native semaphore.
352 */
354{
355 sem_t sem;
356};
357
358/*!
359 * Init.
360 *
361 * @public @memberof os_semaphore
362 */
363static inline int
364os_semaphore_init(struct os_semaphore *os, int count)
365{
366 return sem_init(&os->sem, 0, count);
367}
368
369/*!
370 * Release.
371 *
372 * @public @memberof os_semaphore
373 */
374static inline void
376{
377 sem_post(&os->sem);
378}
379
380/*!
381 * Set @p ts to the current time, plus the timeout_ns value.
382 *
383 * Intended for use by the threading code only: the timestamps are not interchangeable with other sources of time.
384 *
385 * @public @memberof os_semaphore
386 */
387static inline int
388os_semaphore_get_realtime_clock(struct timespec *ts, uint64_t timeout_ns)
389{
390#if defined(XRT_OS_WINDOWS) && !defined(XRT_ENV_MINGW)
391 struct timespec relative;
392 os_ns_to_timespec(timeout_ns, &relative);
393 pthread_win32_getabstime_np(ts, &relative);
394 return 0;
395#else
396 struct timespec now;
397 if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
398 assert(false);
399 return -1;
400 }
401 uint64_t now_ns = os_timespec_to_ns(&now);
402 uint64_t when_ns = timeout_ns + now_ns;
403
404 os_ns_to_timespec(when_ns, ts);
405 return 0;
406#endif
407}
408
409/*!
410 * Wait, if @p timeout_ns is zero then waits forever.
411 *
412 * @public @memberof os_semaphore
413 */
414static inline void
415os_semaphore_wait(struct os_semaphore *os, uint64_t timeout_ns)
416{
417 if (timeout_ns == 0) {
418 sem_wait(&os->sem);
419 return;
420 }
421
422 struct timespec abs_timeout;
423 if (os_semaphore_get_realtime_clock(&abs_timeout, timeout_ns) == -1) {
424 assert(false);
425 }
426
427 sem_timedwait(&os->sem, &abs_timeout);
428}
429
430/*!
431 * Clean up.
432 *
433 * @public @memberof os_semaphore
434 */
435static inline void
437{
438 sem_destroy(&os->sem);
439}
440
441
442/*
443 *
444 * Fancy helper.
445 *
446 */
447
448/*!
449 * All in one helper that handles locking, waiting for change and starting a
450 * thread.
451 */
453{
454 pthread_t thread;
455 pthread_mutex_t mutex;
456 pthread_cond_t cond;
457
458 bool initialized;
459 bool running;
460};
461
462/*!
463 * Initialize the thread helper.
464 *
465 * @public @memberof os_thread_helper
466 */
467static inline int
469{
470 U_ZERO(oth);
471
472 int ret = pthread_mutex_init(&oth->mutex, NULL);
473 if (ret != 0) {
474 return ret;
475 }
476
477 ret = pthread_cond_init(&oth->cond, NULL);
478 if (ret) {
479 pthread_mutex_destroy(&oth->mutex);
480 return ret;
481 }
482 oth->initialized = true;
483
484 return 0;
485}
486
487/*!
488 * Start the internal thread.
489 *
490 * @public @memberof os_thread_helper
491 */
492static inline int
494{
495 pthread_mutex_lock(&oth->mutex);
496
497 assert(oth->initialized);
498 if (oth->running) {
499 pthread_mutex_unlock(&oth->mutex);
500 return -1;
501 }
502
503 int ret = pthread_create(&oth->thread, NULL, func, ptr);
504 if (ret != 0) {
505 pthread_mutex_unlock(&oth->mutex);
506 return ret;
507 }
508
509 oth->running = true;
510
511 pthread_mutex_unlock(&oth->mutex);
512
513 return 0;
514}
515
516/*!
517 * @brief Signal from within the thread that we are stopping.
518 *
519 * Call with mutex unlocked - it takes and releases the lock internally.
520 *
521 * @public @memberof os_thread_helper
522 */
523static inline int
525{
526 // The fields are protected.
527 pthread_mutex_lock(&oth->mutex);
528 assert(oth->initialized);
529
530 // Report we're stopping the thread.
531 oth->running = false;
532
533 // Wake up any waiting thread.
534 pthread_cond_signal(&oth->cond);
535
536 // No longer need to protect fields.
537 pthread_mutex_unlock(&oth->mutex);
538
539 return 0;
540}
541
542/*!
543 * @brief Stop the thread and wait for it to exit.
544 *
545 * Call with mutex unlocked - it takes and releases the lock internally.
546 *
547 * @public @memberof os_thread_helper
548 */
549static inline int
551{
552 void *retval = NULL;
553
554 // The fields are protected.
555 pthread_mutex_lock(&oth->mutex);
556 assert(oth->initialized);
557
558 if (!oth->running) {
559 // it already exited
560 pthread_mutex_unlock(&oth->mutex);
561 return 0;
562 }
563
564 // Stop the thread.
565 oth->running = false;
566
567 // Wake up the thread if it is waiting.
568 pthread_cond_signal(&oth->cond);
569
570 // No longer need to protect fields.
571 pthread_mutex_unlock(&oth->mutex);
572
573 // Wait for thread to finish.
574 pthread_join(oth->thread, &retval);
575
576 return 0;
577}
578
579/*!
580 * Destroy the thread helper, externally synchronizable.
581 *
582 * Integrates a call to @ref os_thread_helper_stop_and_wait, so you may just call this for full cleanup
583 *
584 * @public @memberof os_thread_helper
585 */
586static inline void
588{
589 assert(oth->initialized);
590 // Stop the thread.
592
593 // Destroy resources.
594 pthread_mutex_destroy(&oth->mutex);
595 pthread_cond_destroy(&oth->cond);
596 oth->initialized = false;
597}
598
599/*!
600 * Lock the helper.
601 *
602 * @public @memberof os_thread_helper
603 */
604static inline void
606{
607 pthread_mutex_lock(&oth->mutex);
608}
609
610/*!
611 * Unlock the helper.
612 *
613 * @public @memberof os_thread_helper
614 */
615static inline void
617{
618 pthread_mutex_unlock(&oth->mutex);
619}
620
621/*!
622 * Is the thread running, or supposed to be running.
623 *
624 * Call with mutex unlocked - it takes and releases the lock internally.
625 * If you already have a lock, use os_thread_helper_is_running_locked().
626 *
627 * @public @memberof os_thread_helper
628 */
629static inline bool
631{
633 assert(oth->initialized);
634 bool ret = oth->running;
636 return ret;
637}
638
639/*!
640 * Is the thread running, or supposed to be running.
641 *
642 * Must be called with the helper locked.
643 * If you don't have the helper locked for some other reason already,
644 * you can use os_thread_helper_is_running()
645 *
646 * @public @memberof os_thread_helper
647 */
648static inline bool
650{
651 return oth->running;
652}
653
654/*!
655 * Wait for a signal.
656 *
657 * Be sure to call this in a loop, testing some other condition that you
658 * are actually waiting for, as this is backed by a condition variable
659 * wait and is thus subject to spurious wakeups.
660 *
661 * Must be called with the helper locked.
662 *
663 * As this wraps a cond-var wait, once the wait begins, the helper is
664 * unlocked, to allow another thread access to change the thing you're
665 * monitoring. By the time this returns, you once again own the lock.
666 *
667 * @public @memberof os_thread_helper
668 */
669static inline void
671{
672 pthread_cond_wait(&oth->cond, &oth->mutex);
673}
674
675/*!
676 * Signal a waiting thread to wake up.
677 *
678 * Must be called with the helper locked.
679 *
680 * @public @memberof os_thread_helper
681 */
682static inline void
684{
685 pthread_cond_signal(&oth->cond);
686}
687
688/*!
689 * Make a best effort to name our thread.
690 *
691 * @public @memberof os_thread_helper
692 */
693static inline void
694os_thread_helper_name(struct os_thread_helper *oth, const char *name)
695{
696#ifdef OS_THREAD_HAVE_SETNAME
697 pthread_setname_np(oth->thread, name);
698#else
699 (void)oth;
700 (void)name;
701#endif
702}
703
704/*!
705 * @}
706 */
707
708
709#ifdef __cplusplus
710} // extern "C"
711#endif
712
713
714#ifdef __cplusplus
715namespace xrt::auxiliary::os {
716
717
718//! A class owning an @ref os_mutex
719class Mutex
720{
721public:
722 //! Construct a mutex
723 Mutex() noexcept
724 {
725 os_mutex_init(&inner_);
726 }
727 //! Destroy a mutex when it goes out of scope
728 ~Mutex()
729 {
730 os_mutex_destroy(&inner_);
731 }
732
733 //! Block until the lock can be taken.
734 void
735 lock() noexcept
736 {
737 os_mutex_lock(&inner_);
738 }
739
740 //! Take the lock and return true if possible, but do not block
741 bool
742 try_lock() noexcept
743 {
744 return 0 == os_mutex_trylock(&inner_);
745 }
746
747 //! Release the lock
748 void
749 unlock() noexcept
750 {
751 os_mutex_unlock(&inner_);
752 }
753
754 //! Get a pointer to the owned mutex: do not delete it!
755 os_mutex *
756 get_inner() noexcept
757 {
758 return &inner_;
759 }
760
761 // Do not copy or delete these mutexes.
762 Mutex(Mutex const &) = delete;
763 Mutex(Mutex &&) = delete;
764 Mutex &
765 operator=(Mutex const &) = delete;
766 Mutex &
767 operator=(Mutex &&) = delete;
768
769private:
770 os_mutex inner_{};
771};
772
773} // namespace xrt::auxiliary::os
774
775#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:649
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:493
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:388
static void os_thread_helper_signal_locked(struct os_thread_helper *oth)
Signal a waiting thread to wake up.
Definition: os_threading.h:683
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:239
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:550
static void os_thread_destroy(struct os_thread *ost)
Destruction.
Definition: os_threading.h:325
static void os_semaphore_release(struct os_semaphore *os)
Release.
Definition: os_threading.h:375
static void os_thread_join(struct os_thread *ost)
Join.
Definition: os_threading.h:311
static int os_thread_start(struct os_thread *ost, os_run_func_t func, void *ptr)
Start thread.
Definition: os_threading.h:300
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:524
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:334
static void os_thread_helper_wait_locked(struct os_thread_helper *oth)
Wait for a signal.
Definition: os_threading.h:670
static int os_semaphore_init(struct os_semaphore *os, int count)
Init.
Definition: os_threading.h:364
static int os_thread_init(struct os_thread *ost)
Init.
Definition: os_threading.h:289
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:616
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:694
static int os_thread_helper_init(struct os_thread_helper *oth)
Initialize the thread helper.
Definition: os_threading.h:468
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:587
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:630
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:281
static void os_semaphore_destroy(struct os_semaphore *os)
Clean up.
Definition: os_threading.h:436
static void os_thread_helper_lock(struct os_thread_helper *oth)
Lock the helper.
Definition: os_threading.h:605
static void os_cond_destroy(struct os_cond *oc)
Clean up.
Definition: os_threading.h:251
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:415
#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:354
All in one helper that handles locking, waiting for change and starting a thread.
Definition: os_threading.h:453
A wrapper around a native thread.
Definition: os_threading.h:272
Definition: u_worker.c:37
Very small misc utils.
Header holding common defines.
Auto detect OS and certain features.