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
36 extern "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  */
54 struct os_mutex
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  */
69 static 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  */
85 static 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  */
97 static 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  */
109 static 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  */
121 static 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  */
140 static 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  */
164 static 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  */
188 struct os_cond
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  */
201 static 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  */
216 static 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  */
238 static inline void
239 os_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  */
250 static 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  */
271 struct os_thread
272 {
273  pthread_t thread;
274 };
275 
276 /*!
277  * Run function.
278  *
279  * @public @memberof os_thread
280  */
281 typedef void *(*os_run_func_t)(void *);
282 
283 /*!
284  * Init.
285  *
286  * @public @memberof os_thread
287  */
288 static inline int
290 {
291  return 0;
292 }
293 
294 /*!
295  * Start thread.
296  *
297  * @public @memberof os_thread
298  */
299 static inline int
300 os_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  */
310 static 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  */
324 static inline void
326 {}
327 
328 /*!
329  * Make a best effort to name our thread.
330  *
331  * @public @memberof os_thread
332  */
333 static inline void
334 os_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  */
363 static inline int
364 os_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  */
374 static 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  */
387 static inline int
388 os_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  */
414 static inline void
415 os_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  */
435 static 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  */
467 static 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  */
492 static 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  */
523 static 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  */
549 static 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  */
586 static 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  */
604 static inline void
606 {
607  pthread_mutex_lock(&oth->mutex);
608 }
609 
610 /*!
611  * Unlock the helper.
612  *
613  * @public @memberof os_thread_helper
614  */
615 static 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  */
629 static 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  */
648 static 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  */
669 static 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  */
682 static 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  */
693 static inline void
694 os_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
715 namespace xrt::auxiliary::os {
716 
717 
718 //! A class owning an @ref os_mutex
719 class Mutex
720 {
721 public:
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 
769 private:
770  os_mutex inner_{};
771 };
772 
773 } // namespace xrt::auxiliary::os
774 
775 #endif // __cplusplus
static uint64_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(uint64_t ns, struct timespec *spec)
Convert an nanosecond integer to a timespec struct.
Definition: os_time.h:271
void *(* os_run_func_t)(void *)
Run function.
Definition: os_threading.h:281
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
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.