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
14namespace 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).
19template <typename ConditionFn>
20bool 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
61void AutoResetWaitableEvent::Signal() {
62 std::scoped_lock locker(mutex_);
63 signaled_ = true;
64 cv_.notify_one();
65}
66
67void AutoResetWaitableEvent::Reset() {
68 std::scoped_lock locker(mutex_);
69 signaled_ = false;
70}
71
72void AutoResetWaitableEvent::Wait() {
73 std::unique_lock<std::mutex> locker(mutex_);
74 while (!signaled_) {
75 cv_.wait(locker);
76 }
77 signaled_ = false;
78}
79
80bool 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
120bool AutoResetWaitableEvent::IsSignaledForTest() {
121 std::scoped_lock locker(mutex_);
122 return signaled_;
123}
124
125// ManualResetWaitableEvent ----------------------------------------------------
126
127void ManualResetWaitableEvent::Signal() {
128 std::scoped_lock locker(mutex_);
129 signaled_ = true;
130 signal_id_++;
131 cv_.notify_all();
132}
133
134void ManualResetWaitableEvent::Reset() {
135 std::scoped_lock locker(mutex_);
136 signaled_ = false;
137}
138
139void 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
152bool 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
171bool ManualResetWaitableEvent::IsSignaledForTest() {
172 std::scoped_lock locker(mutex_);
173 return signaled_;
174}
175
176} // namespace fml
177