1/*
2 * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#pragma once
27
28#include <wtf/Noncopyable.h>
29#include <wtf/ParkingLot.h>
30#include <wtf/TimeWithDynamicClockType.h>
31
32namespace WTF {
33
34// This is a condition variable that is suitable for use with any lock-like object, including
35// our own WTF::Lock. It features standard wait()/notifyOne()/notifyAll() methods in addition to
36// a variety of wait-with-timeout methods. This includes methods that use WTF's own notion of
37// time, like wall-clock time (i.e. WallTime) and monotonic time (i.e. MonotonicTime). This is
38// a very efficient condition variable. It only requires one byte of memory. notifyOne() and
39// notifyAll() require just a load and branch for the fast case where no thread is waiting.
40// This condition variable, when used with WTF::Lock, can outperform a system condition variable
41// and lock by up to 58x.
42class Condition {
43 WTF_MAKE_NONCOPYABLE(Condition);
44public:
45 // Condition will accept any kind of time and convert it internally, but this typedef tells
46 // you what kind of time Condition would be able to use without conversions. However, if you
47 // are unlikely to be affected by the cost of conversions, it is better to use MonotonicTime.
48 using Time = ParkingLot::Time;
49
50 constexpr Condition() = default;
51
52 // Wait on a parking queue while releasing the given lock. It will unlock the lock just before
53 // parking, and relock it upon wakeup. Returns true if we woke up due to some call to
54 // notifyOne() or notifyAll(). Returns false if we woke up due to a timeout. Note that this form
55 // of waitUntil() has some quirks:
56 //
57 // No spurious wake-up: in order for this to return before the timeout, some notifyOne() or
58 // notifyAll() call must have happened. No scenario other than timeout or notify can lead to this
59 // method returning. This means, for example, that you can't use pthread cancelation or signals to
60 // cause early return.
61 //
62 // Past timeout: it's possible for waitUntil() to be called with a timeout in the past. In that
63 // case, waitUntil() will still release the lock and reacquire it. waitUntil() will always return
64 // false in that case. This is subtly different from some pthread_cond_timedwait() implementations,
65 // which may not release the lock for past timeout. But, this behavior is consistent with OpenGroup
66 // documentation for timedwait().
67 template<typename LockType>
68 bool waitUntil(LockType& lock, const TimeWithDynamicClockType& timeout)
69 {
70 bool result;
71 if (timeout < timeout.nowWithSameClock()) {
72 lock.unlock();
73 result = false;
74 } else {
75 result = ParkingLot::parkConditionally(
76 &m_hasWaiters,
77 [this] () -> bool {
78 // Let everyone know that we will be waiting. Do this while we hold the queue lock,
79 // to prevent races with notifyOne().
80 m_hasWaiters.store(true);
81 return true;
82 },
83 [&lock] () { lock.unlock(); },
84 timeout).wasUnparked;
85 }
86 lock.lock();
87 return result;
88 }
89
90 // Wait until the given predicate is satisfied. Returns true if it is satisfied in the end.
91 // May return early due to timeout.
92 template<typename LockType, typename Functor>
93 bool waitUntil(
94 LockType& lock, const TimeWithDynamicClockType& timeout, const Functor& predicate)
95 {
96 while (!predicate()) {
97 if (!waitUntil(lock, timeout))
98 return predicate();
99 }
100 return true;
101 }
102
103 // Wait until the given predicate is satisfied. Returns true if it is satisfied in the end.
104 // May return early due to timeout.
105 template<typename LockType, typename Functor>
106 bool waitFor(
107 LockType& lock, Seconds relativeTimeout, const Functor& predicate)
108 {
109 return waitUntil(lock, MonotonicTime::now() + relativeTimeout, predicate);
110 }
111
112 template<typename LockType>
113 bool waitFor(LockType& lock, Seconds relativeTimeout)
114 {
115 return waitUntil(lock, MonotonicTime::now() + relativeTimeout);
116 }
117
118 template<typename LockType>
119 void wait(LockType& lock)
120 {
121 waitUntil(lock, Time::infinity());
122 }
123
124 template<typename LockType, typename Functor>
125 void wait(LockType& lock, const Functor& predicate)
126 {
127 while (!predicate())
128 wait(lock);
129 }
130
131 // Note that this method is extremely fast when nobody is waiting. It is not necessary to try to
132 // avoid calling this method. This returns true if someone was actually woken up.
133 bool notifyOne()
134 {
135 if (!m_hasWaiters.load()) {
136 // At this exact instant, there is nobody waiting on this condition. The way to visualize
137 // this is that if unparkOne() ran to completion without obstructions at this moment, it
138 // wouldn't wake anyone up. Hence, we have nothing to do!
139 return false;
140 }
141
142 bool didNotifyThread = false;
143 ParkingLot::unparkOne(
144 &m_hasWaiters,
145 [&] (ParkingLot::UnparkResult result) -> intptr_t {
146 if (!result.mayHaveMoreThreads)
147 m_hasWaiters.store(false);
148 didNotifyThread = result.didUnparkThread;
149 return 0;
150 });
151 return didNotifyThread;
152 }
153
154 void notifyAll()
155 {
156 if (!m_hasWaiters.load()) {
157 // See above.
158 return;
159 }
160
161 // It's totally safe for us to set this to false without any locking, because this thread is
162 // guaranteed to then unparkAll() anyway. So, if there is a race with some thread calling
163 // wait() just before this store happens, that thread is guaranteed to be awoken by the call to
164 // unparkAll(), below.
165 m_hasWaiters.store(false);
166
167 ParkingLot::unparkAll(&m_hasWaiters);
168 }
169
170private:
171 Atomic<bool> m_hasWaiters { false };
172};
173
174using StaticCondition = Condition;
175
176} // namespace WTF
177
178using WTF::Condition;
179using WTF::StaticCondition;
180