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 | |