1 //===-- Utility condition variable class ------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "src/__support/threads/CndVar.h" 10 #include "src/__support/CPP/mutex.h" 11 #include "src/__support/OSUtil/syscall.h" // syscall_impl 12 #include "src/__support/macros/config.h" 13 #include "src/__support/threads/linux/futex_word.h" // FutexWordType 14 #include "src/__support/threads/linux/raw_mutex.h" // RawMutex 15 #include "src/__support/threads/mutex.h" // Mutex 16 17 #include <sys/syscall.h> // For syscall numbers. 18 19 namespace LIBC_NAMESPACE_DECL { 20 21 int CndVar::wait(Mutex *m) { 22 // The goal is to perform "unlock |m| and wait" in an 23 // atomic operation. However, it is not possible to do it 24 // in the true sense so we do it in spirit. Before unlocking 25 // |m|, a new waiter object is added to the waiter queue with 26 // the waiter queue locked. Iff a signalling thread signals 27 // the waiter before the waiter actually starts waiting, the 28 // wait operation will not begin at all and the waiter immediately 29 // returns. 30 31 CndWaiter waiter; 32 { 33 cpp::lock_guard ml(qmtx); 34 CndWaiter *old_back = nullptr; 35 if (waitq_front == nullptr) { 36 waitq_front = waitq_back = &waiter; 37 } else { 38 old_back = waitq_back; 39 waitq_back->next = &waiter; 40 waitq_back = &waiter; 41 } 42 43 if (m->unlock() != MutexError::NONE) { 44 // If we do not remove the queued up waiter before returning, 45 // then another thread can potentially signal a non-existing 46 // waiter. Note also that we do this with |qmtx| locked. This 47 // ensures that another thread will not signal the withdrawing 48 // waiter. 49 waitq_back = old_back; 50 if (waitq_back == nullptr) 51 waitq_front = nullptr; 52 else 53 waitq_back->next = nullptr; 54 55 return -1; 56 } 57 } 58 59 waiter.futex_word.wait(WS_Waiting, cpp::nullopt, true); 60 61 // At this point, if locking |m| fails, we can simply return as the 62 // queued up waiter would have been removed from the queue. 63 auto err = m->lock(); 64 return err == MutexError::NONE ? 0 : -1; 65 } 66 67 void CndVar::notify_one() { 68 // We don't use an RAII locker in this method as we want to unlock 69 // |qmtx| and signal the waiter using a single FUTEX_WAKE_OP signal. 70 qmtx.lock(); 71 if (waitq_front == nullptr) 72 qmtx.unlock(); 73 74 CndWaiter *first = waitq_front; 75 waitq_front = waitq_front->next; 76 if (waitq_front == nullptr) 77 waitq_back = nullptr; 78 79 qmtx.reset(); 80 81 // this is a special WAKE_OP, so we use syscall directly 82 LIBC_NAMESPACE::syscall_impl<long>( 83 FUTEX_SYSCALL_ID, &qmtx.get_raw_futex(), FUTEX_WAKE_OP, 1, 1, 84 &first->futex_word.val, 85 FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting)); 86 } 87 88 void CndVar::broadcast() { 89 cpp::lock_guard ml(qmtx); 90 uint32_t dummy_futex_word; 91 CndWaiter *waiter = waitq_front; 92 waitq_front = waitq_back = nullptr; 93 while (waiter != nullptr) { 94 // FUTEX_WAKE_OP is used instead of just FUTEX_WAKE as it allows us to 95 // atomically update the waiter status to WS_Signalled before waking 96 // up the waiter. A dummy location is used for the other futex of 97 // FUTEX_WAKE_OP. 98 LIBC_NAMESPACE::syscall_impl<long>( 99 FUTEX_SYSCALL_ID, &dummy_futex_word, FUTEX_WAKE_OP, 1, 1, 100 &waiter->futex_word.val, 101 FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting)); 102 waiter = waiter->next; 103 } 104 } 105 106 } // namespace LIBC_NAMESPACE_DECL 107