107 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "src/__support/threads/CndVar.h"10#include "src/__support/CPP/mutex.h"11#include "src/__support/OSUtil/syscall.h" // syscall_impl12#include "src/__support/macros/config.h"13#include "src/__support/threads/linux/futex_word.h" // FutexWordType14#include "src/__support/threads/linux/raw_mutex.h" // RawMutex15#include "src/__support/threads/mutex.h" // Mutex16 17#include <sys/syscall.h> // For syscall numbers.18 19namespace LIBC_NAMESPACE_DECL {20 21int CndVar::wait(Mutex *m) {22 // The goal is to perform "unlock |m| and wait" in an23 // atomic operation. However, it is not possible to do it24 // in the true sense so we do it in spirit. Before unlocking25 // |m|, a new waiter object is added to the waiter queue with26 // the waiter queue locked. Iff a signalling thread signals27 // the waiter before the waiter actually starts waiting, the28 // wait operation will not begin at all and the waiter immediately29 // 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-existing46 // waiter. Note also that we do this with |qmtx| locked. This47 // ensures that another thread will not signal the withdrawing48 // waiter.49 waitq_back = old_back;50 if (waitq_back == nullptr)51 waitq_front = nullptr;52 else53 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 the62 // queued up waiter would have been removed from the queue.63 auto err = m->lock();64 return err == MutexError::NONE ? 0 : -1;65}66 67void CndVar::notify_one() {68 // We don't use an RAII locker in this method as we want to unlock69 // |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 directly82 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 88void 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 to95 // atomically update the waiter status to WS_Signalled before waking96 // up the waiter. A dummy location is used for the other futex of97 // 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_DECL107