The open source OpenXR runtime
at main 787 lines 14 kB view raw
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 */ 54struct 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 */ 69static inline int 70os_mutex_init(struct os_mutex *om) 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 86os_mutex_lock(struct os_mutex *om) 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 98os_mutex_trylock(struct os_mutex *om) 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 110os_mutex_unlock(struct os_mutex *om) 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 122os_mutex_destroy(struct os_mutex *om) 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 141os_mutex_recursive_init(struct os_mutex *om) 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 165os_mutex_recursive_destroy(struct os_mutex *om) 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 */ 188struct 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 */ 201static inline int 202os_cond_init(struct os_cond *oc) 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 217os_cond_signal(struct os_cond *oc) 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 229os_cond_broadcast(struct os_cond *oc) 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 263os_cond_destroy(struct os_cond *oc) 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 */ 283struct os_thread 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 301os_thread_init(struct os_thread *ost) 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 323os_thread_join(struct os_thread *ost) 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 337os_thread_destroy(struct os_thread *ost) 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 */ 365struct os_semaphore 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 387os_semaphore_release(struct os_semaphore *os) 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 448os_semaphore_destroy(struct os_semaphore *os) 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 */ 464struct os_thread_helper 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 480os_thread_helper_init(struct os_thread_helper *oth) 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 505os_thread_helper_start(struct os_thread_helper *oth, os_run_func_t func, void *ptr) 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 536os_thread_helper_signal_stop(struct os_thread_helper *oth) 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 562os_thread_helper_stop_and_wait(struct os_thread_helper *oth) 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 599os_thread_helper_destroy(struct os_thread_helper *oth) 600{ 601 assert(oth->initialized); 602 // Stop the thread. 603 os_thread_helper_stop_and_wait(oth); 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 617os_thread_helper_lock(struct os_thread_helper *oth) 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 628os_thread_helper_unlock(struct os_thread_helper *oth) 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 642os_thread_helper_is_running(struct os_thread_helper *oth) 643{ 644 os_thread_helper_lock(oth); 645 assert(oth->initialized); 646 bool ret = oth->running; 647 os_thread_helper_unlock(oth); 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 661os_thread_helper_is_running_locked(struct os_thread_helper *oth) 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 682os_thread_helper_wait_locked(struct os_thread_helper *oth) 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 695os_thread_helper_signal_locked(struct os_thread_helper *oth) 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