xref: /aosp_15_r20/external/llvm-libc/src/__support/threads/linux/CndVar.cpp (revision 71db0c75aadcf003ffe3238005f61d7618a3fead)
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 
wait(Mutex * m)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 
notify_one()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 
broadcast()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