1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "flutter/fml/synchronization/waitable_event.h" |
6 | |
7 | #include "flutter/fml/logging.h" |
8 | #include "flutter/fml/time/time_delta.h" |
9 | #include "flutter/fml/time/time_point.h" |
10 | |
11 | #include <errno.h> |
12 | #include <time.h> |
13 | |
14 | namespace fml { |
15 | |
16 | // Waits with a timeout on |condition()|. Returns true on timeout, or false if |
17 | // |condition()| ever returns true. |condition()| should have no side effects |
18 | // (and will always be called with |*mutex| held). |
19 | template <typename ConditionFn> |
20 | bool WaitWithTimeoutImpl(std::unique_lock<std::mutex>* locker, |
21 | std::condition_variable* cv, |
22 | ConditionFn condition, |
23 | TimeDelta timeout) { |
24 | FML_DCHECK(locker->owns_lock()); |
25 | |
26 | if (condition()) { |
27 | return false; |
28 | } |
29 | |
30 | // We may get spurious wakeups. |
31 | TimeDelta wait_remaining = timeout; |
32 | TimePoint start = TimePoint::Now(); |
33 | while (true) { |
34 | if (std::cv_status::timeout == |
35 | cv->wait_for(*locker, std::chrono::nanoseconds( |
36 | wait_remaining.ToNanoseconds()))) { |
37 | return true; // Definitely timed out. |
38 | } |
39 | |
40 | // We may have been awoken. |
41 | if (condition()) { |
42 | return false; |
43 | } |
44 | |
45 | // Or the wakeup may have been spurious. |
46 | TimePoint now = TimePoint::Now(); |
47 | FML_DCHECK(now >= start); |
48 | TimeDelta elapsed = now - start; |
49 | // It's possible that we may have timed out anyway. |
50 | if (elapsed >= timeout) { |
51 | return true; |
52 | } |
53 | |
54 | // Otherwise, recalculate the amount that we have left to wait. |
55 | wait_remaining = timeout - elapsed; |
56 | } |
57 | } |
58 | |
59 | // AutoResetWaitableEvent ------------------------------------------------------ |
60 | |
61 | void AutoResetWaitableEvent::Signal() { |
62 | std::scoped_lock locker(mutex_); |
63 | signaled_ = true; |
64 | cv_.notify_one(); |
65 | } |
66 | |
67 | void AutoResetWaitableEvent::Reset() { |
68 | std::scoped_lock locker(mutex_); |
69 | signaled_ = false; |
70 | } |
71 | |
72 | void AutoResetWaitableEvent::Wait() { |
73 | std::unique_lock<std::mutex> locker(mutex_); |
74 | while (!signaled_) { |
75 | cv_.wait(locker); |
76 | } |
77 | signaled_ = false; |
78 | } |
79 | |
80 | bool AutoResetWaitableEvent::WaitWithTimeout(TimeDelta timeout) { |
81 | std::unique_lock<std::mutex> locker(mutex_); |
82 | |
83 | if (signaled_) { |
84 | signaled_ = false; |
85 | return false; |
86 | } |
87 | |
88 | // We may get spurious wakeups. |
89 | TimeDelta wait_remaining = timeout; |
90 | TimePoint start = TimePoint::Now(); |
91 | while (true) { |
92 | if (std::cv_status::timeout == |
93 | cv_.wait_for( |
94 | locker, std::chrono::nanoseconds(wait_remaining.ToNanoseconds()))) { |
95 | return true; // Definitely timed out. |
96 | } |
97 | |
98 | // We may have been awoken. |
99 | if (signaled_) { |
100 | break; |
101 | } |
102 | |
103 | // Or the wakeup may have been spurious. |
104 | TimePoint now = TimePoint::Now(); |
105 | FML_DCHECK(now >= start); |
106 | TimeDelta elapsed = now - start; |
107 | // It's possible that we may have timed out anyway. |
108 | if (elapsed >= timeout) { |
109 | return true; |
110 | } |
111 | |
112 | // Otherwise, recalculate the amount that we have left to wait. |
113 | wait_remaining = timeout - elapsed; |
114 | } |
115 | |
116 | signaled_ = false; |
117 | return false; |
118 | } |
119 | |
120 | bool AutoResetWaitableEvent::IsSignaledForTest() { |
121 | std::scoped_lock locker(mutex_); |
122 | return signaled_; |
123 | } |
124 | |
125 | // ManualResetWaitableEvent ---------------------------------------------------- |
126 | |
127 | void ManualResetWaitableEvent::Signal() { |
128 | std::scoped_lock locker(mutex_); |
129 | signaled_ = true; |
130 | signal_id_++; |
131 | cv_.notify_all(); |
132 | } |
133 | |
134 | void ManualResetWaitableEvent::Reset() { |
135 | std::scoped_lock locker(mutex_); |
136 | signaled_ = false; |
137 | } |
138 | |
139 | void ManualResetWaitableEvent::Wait() { |
140 | std::unique_lock<std::mutex> locker(mutex_); |
141 | |
142 | if (signaled_) { |
143 | return; |
144 | } |
145 | |
146 | auto last_signal_id = signal_id_; |
147 | do { |
148 | cv_.wait(locker); |
149 | } while (signal_id_ == last_signal_id); |
150 | } |
151 | |
152 | bool ManualResetWaitableEvent::WaitWithTimeout(TimeDelta timeout) { |
153 | std::unique_lock<std::mutex> locker(mutex_); |
154 | |
155 | auto last_signal_id = signal_id_; |
156 | // Disable thread-safety analysis for the lambda: We could annotate it with |
157 | // |FML_EXCLUSIVE_LOCKS_REQUIRED(mutex_)|, but then the analyzer currently |
158 | // isn't able to figure out that |WaitWithTimeoutImpl()| calls it while |
159 | // holding |mutex_|. |
160 | bool rv = WaitWithTimeoutImpl( |
161 | &locker, &cv_, |
162 | [this, last_signal_id]() { |
163 | // Also check |signaled_| in case we're already signaled. |
164 | return signaled_ || signal_id_ != last_signal_id; |
165 | }, |
166 | timeout); |
167 | FML_DCHECK(rv || signaled_ || signal_id_ != last_signal_id); |
168 | return rv; |
169 | } |
170 | |
171 | bool ManualResetWaitableEvent::IsSignaledForTest() { |
172 | std::scoped_lock locker(mutex_); |
173 | return signaled_; |
174 | } |
175 | |
176 | } // namespace fml |
177 | |