// 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 #include #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 bool WaitWithTimeoutImpl(std::unique_lock* 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 locker(mutex_); while (!signaled_) { cv_.wait(locker); } signaled_ = false; } bool AutoResetWaitableEvent::WaitWithTimeout(TimeDelta timeout) { std::unique_lock 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 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 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