| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/fml/synchronization/waitable_event.h" |
| |
| #include <cerrno> |
| #include <ctime> |
| |
| #include "flutter/fml/logging.h" |
| #include "flutter/fml/time/time_delta.h" |
| #include "flutter/fml/time/time_point.h" |
| |
| namespace fml { |
| |
| // Waits with a timeout on |condition()|. Returns true on timeout, or false if |
| // |condition()| ever returns true. |condition()| should have no side effects |
| // (and will always be called with |*mutex| held). |
| template <typename ConditionFn> |
| bool WaitWithTimeoutImpl(std::unique_lock<std::mutex>* locker, |
| std::condition_variable* cv, |
| ConditionFn condition, |
| TimeDelta timeout) { |
| FML_DCHECK(locker->owns_lock()); |
| |
| if (condition()) { |
| return false; |
| } |
| |
| // We may get spurious wakeups. |
| TimeDelta wait_remaining = timeout; |
| TimePoint start = TimePoint::Now(); |
| while (true) { |
| if (std::cv_status::timeout == |
| cv->wait_for(*locker, std::chrono::nanoseconds( |
| wait_remaining.ToNanoseconds()))) { |
| return true; // Definitely timed out. |
| } |
| |
| // We may have been awoken. |
| if (condition()) { |
| return false; |
| } |
| |
| // Or the wakeup may have been spurious. |
| TimePoint now = TimePoint::Now(); |
| FML_DCHECK(now >= start); |
| TimeDelta elapsed = now - start; |
| // It's possible that we may have timed out anyway. |
| if (elapsed >= timeout) { |
| return true; |
| } |
| |
| // Otherwise, recalculate the amount that we have left to wait. |
| wait_remaining = timeout - elapsed; |
| } |
| } |
| |
| // AutoResetWaitableEvent ------------------------------------------------------ |
| |
| void AutoResetWaitableEvent::Signal() { |
| std::scoped_lock locker(mutex_); |
| signaled_ = true; |
| cv_.notify_one(); |
| } |
| |
| void AutoResetWaitableEvent::Reset() { |
| std::scoped_lock locker(mutex_); |
| signaled_ = false; |
| } |
| |
| void AutoResetWaitableEvent::Wait() { |
| std::unique_lock<std::mutex> locker(mutex_); |
| while (!signaled_) { |
| cv_.wait(locker); |
| } |
| signaled_ = false; |
| } |
| |
| bool AutoResetWaitableEvent::WaitWithTimeout(TimeDelta timeout) { |
| std::unique_lock<std::mutex> locker(mutex_); |
| |
| if (signaled_) { |
| signaled_ = false; |
| return false; |
| } |
| |
| // We may get spurious wakeups. |
| TimeDelta wait_remaining = timeout; |
| TimePoint start = TimePoint::Now(); |
| while (true) { |
| if (std::cv_status::timeout == |
| cv_.wait_for( |
| locker, std::chrono::nanoseconds(wait_remaining.ToNanoseconds()))) { |
| return true; // Definitely timed out. |
| } |
| |
| // We may have been awoken. |
| if (signaled_) { |
| break; |
| } |
| |
| // Or the wakeup may have been spurious. |
| TimePoint now = TimePoint::Now(); |
| FML_DCHECK(now >= start); |
| TimeDelta elapsed = now - start; |
| // It's possible that we may have timed out anyway. |
| if (elapsed >= timeout) { |
| return true; |
| } |
| |
| // Otherwise, recalculate the amount that we have left to wait. |
| wait_remaining = timeout - elapsed; |
| } |
| |
| signaled_ = false; |
| return false; |
| } |
| |
| bool AutoResetWaitableEvent::IsSignaledForTest() { |
| std::scoped_lock locker(mutex_); |
| return signaled_; |
| } |
| |
| // ManualResetWaitableEvent ---------------------------------------------------- |
| |
| void ManualResetWaitableEvent::Signal() { |
| std::scoped_lock locker(mutex_); |
| signaled_ = true; |
| signal_id_++; |
| cv_.notify_all(); |
| } |
| |
| void ManualResetWaitableEvent::Reset() { |
| std::scoped_lock locker(mutex_); |
| signaled_ = false; |
| } |
| |
| void ManualResetWaitableEvent::Wait() { |
| std::unique_lock<std::mutex> locker(mutex_); |
| |
| if (signaled_) { |
| return; |
| } |
| |
| auto last_signal_id = signal_id_; |
| do { |
| cv_.wait(locker); |
| } while (signal_id_ == last_signal_id); |
| } |
| |
| bool ManualResetWaitableEvent::WaitWithTimeout(TimeDelta timeout) { |
| std::unique_lock<std::mutex> locker(mutex_); |
| |
| auto last_signal_id = signal_id_; |
| // Disable thread-safety analysis for the lambda: We could annotate it with |
| // |FML_EXCLUSIVE_LOCKS_REQUIRED(mutex_)|, but then the analyzer currently |
| // isn't able to figure out that |WaitWithTimeoutImpl()| calls it while |
| // holding |mutex_|. |
| bool rv = WaitWithTimeoutImpl( |
| &locker, &cv_, |
| [this, last_signal_id]() { |
| // Also check |signaled_| in case we're already signaled. |
| return signaled_ || signal_id_ != last_signal_id; |
| }, |
| timeout); |
| FML_DCHECK(rv || signaled_ || signal_id_ != last_signal_id); |
| return rv; |
| } |
| |
| bool ManualResetWaitableEvent::IsSignaledForTest() { |
| std::scoped_lock locker(mutex_); |
| return signaled_; |
| } |
| |
| } // namespace fml |