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