| /* | |
| * Copyright 2004 The WebRTC Project Authors. All rights reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "rtc_base/event.h" | |
| #include <time.h> | |
| #include <cstdlib> | |
| #include <optional> | |
| #include "api/units/time_delta.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/synchronization/yield_policy.h" | |
| #include "rtc_base/system/warn_current_thread_is_deadlocked.h" | |
| #if defined(WEBRTC_WIN) | |
| #include <windows.h> | |
| #elif defined(WEBRTC_POSIX) | |
| #include <pthread.h> | |
| #include <cerrno> | |
| #ifdef WEBRTC_MAC | |
| #include <sys/time.h> | |
| #endif | |
| #include <ctime> | |
| #else | |
| #error "Must define either WEBRTC_WIN or WEBRTC_POSIX." | |
| #endif | |
| namespace webrtc { | |
| Event::Event() : Event(false, false) {} | |
| #if defined(WEBRTC_WIN) | |
| Event::Event(bool manual_reset, bool initially_signaled) { | |
| event_handle_ = ::CreateEvent(nullptr, // Security attributes. | |
| manual_reset, initially_signaled, | |
| nullptr); // Name. | |
| RTC_CHECK(event_handle_); | |
| } | |
| Event::~Event() { | |
| CloseHandle(event_handle_); | |
| } | |
| void Event::Set() { | |
| SetEvent(event_handle_); | |
| } | |
| void Event::Reset() { | |
| ResetEvent(event_handle_); | |
| } | |
| bool Event::Wait(TimeDelta give_up_after, TimeDelta /*warn_after*/) { | |
| ScopedYieldPolicy::YieldExecution(); | |
| const DWORD ms = give_up_after.IsPlusInfinity() | |
| ? INFINITE | |
| : give_up_after.RoundUpTo(TimeDelta::Millis(1)).ms(); | |
| return (WaitForSingleObject(event_handle_, ms) == WAIT_OBJECT_0); | |
| } | |
| #elif defined(WEBRTC_POSIX) | |
| // On MacOS, clock_gettime is available from version 10.12, and on | |
| // iOS, from version 10.0. So we can't use it yet. | |
| #if defined(WEBRTC_MAC) || defined(WEBRTC_IOS) | |
| #define USE_CLOCK_GETTIME 0 | |
| #define USE_PTHREAD_COND_TIMEDWAIT_MONOTONIC_NP 0 | |
| // On Android, pthread_condattr_setclock is available from version 21. By | |
| // default, we target a new enough version for 64-bit platforms but not for | |
| // 32-bit platforms. For older versions, use | |
| // pthread_cond_timedwait_monotonic_np. | |
| #elif defined(WEBRTC_ANDROID) && (__ANDROID_API__ < 21) | |
| #define USE_CLOCK_GETTIME 1 | |
| #define USE_PTHREAD_COND_TIMEDWAIT_MONOTONIC_NP 1 | |
| #else | |
| #define USE_CLOCK_GETTIME 1 | |
| #define USE_PTHREAD_COND_TIMEDWAIT_MONOTONIC_NP 0 | |
| #endif | |
| Event::Event(bool manual_reset, bool initially_signaled) | |
| : is_manual_reset_(manual_reset), event_status_(initially_signaled) { | |
| RTC_CHECK(pthread_mutex_init(&event_mutex_, nullptr) == 0); | |
| pthread_condattr_t cond_attr; | |
| RTC_CHECK(pthread_condattr_init(&cond_attr) == 0); | |
| #if USE_CLOCK_GETTIME && !USE_PTHREAD_COND_TIMEDWAIT_MONOTONIC_NP | |
| RTC_CHECK(pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC) == 0); | |
| #endif | |
| RTC_CHECK(pthread_cond_init(&event_cond_, &cond_attr) == 0); | |
| pthread_condattr_destroy(&cond_attr); | |
| } | |
| Event::~Event() { | |
| pthread_mutex_destroy(&event_mutex_); | |
| pthread_cond_destroy(&event_cond_); | |
| } | |
| void Event::Set() { | |
| pthread_mutex_lock(&event_mutex_); | |
| event_status_ = true; | |
| pthread_cond_broadcast(&event_cond_); | |
| pthread_mutex_unlock(&event_mutex_); | |
| } | |
| void Event::Reset() { | |
| pthread_mutex_lock(&event_mutex_); | |
| event_status_ = false; | |
| pthread_mutex_unlock(&event_mutex_); | |
| } | |
| namespace { | |
| timespec GetTimespec(TimeDelta from_now) { | |
| timespec ts; | |
| // Get the current time. | |
| #if USE_CLOCK_GETTIME | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| #else | |
| timeval tv; | |
| gettimeofday(&tv, nullptr); | |
| ts.tv_sec = tv.tv_sec; | |
| ts.tv_nsec = TimeDelta::Micros(tv.tv_usec).ns(); | |
| #endif | |
| // Add the specified number of nanoseconds to it. | |
| // Use integer division to transfer full seconds to `tv_sec`. | |
| // `ns` has type either std::ldiv_t or std::lldiv_t. | |
| auto ns = std::div(ts.tv_nsec + from_now.ns(), TimeDelta::Seconds(1).ns()); | |
| ts.tv_sec += ns.quot; | |
| ts.tv_nsec = ns.rem; | |
| return ts; | |
| } | |
| } // namespace | |
| bool Event::Wait(TimeDelta give_up_after, TimeDelta warn_after) { | |
| // Instant when we'll log a warning message (because we've been waiting so | |
| // long it might be a bug), but not yet give up waiting. nullopt if we | |
| // shouldn't log a warning. | |
| const std::optional<timespec> warn_ts = | |
| warn_after >= give_up_after ? std::nullopt | |
| : std::make_optional(GetTimespec(warn_after)); | |
| // Instant when we'll stop waiting and return an error. nullopt if we should | |
| // never give up. | |
| const std::optional<timespec> give_up_ts = | |
| give_up_after.IsPlusInfinity() | |
| ? std::nullopt | |
| : std::make_optional(GetTimespec(give_up_after)); | |
| ScopedYieldPolicy::YieldExecution(); | |
| pthread_mutex_lock(&event_mutex_); | |
| // Wait for `event_cond_` to trigger and `event_status_` to be set, with the | |
| // given timeout (or without a timeout if none is given). | |
| const auto wait = [&](const std::optional<timespec> timeout_ts) { | |
| int error = 0; | |
| while (!event_status_ && error == 0) { | |
| if (timeout_ts == std::nullopt) { | |
| error = pthread_cond_wait(&event_cond_, &event_mutex_); | |
| } else { | |
| #if USE_PTHREAD_COND_TIMEDWAIT_MONOTONIC_NP | |
| error = pthread_cond_timedwait_monotonic_np(&event_cond_, &event_mutex_, | |
| &*timeout_ts); | |
| #else | |
| error = | |
| pthread_cond_timedwait(&event_cond_, &event_mutex_, &*timeout_ts); | |
| #endif | |
| } | |
| } | |
| return error; | |
| }; | |
| int error; | |
| if (warn_ts == std::nullopt) { | |
| error = wait(give_up_ts); | |
| } else { | |
| error = wait(warn_ts); | |
| if (error == ETIMEDOUT) { | |
| WarnThatTheCurrentThreadIsProbablyDeadlocked(); | |
| error = wait(give_up_ts); | |
| } | |
| } | |
| // NOTE(liulk): Exactly one thread will auto-reset this event. All | |
| // the other threads will think it's unsignaled. This seems to be | |
| // consistent with auto-reset events in WEBRTC_WIN | |
| if (error == 0 && !is_manual_reset_) | |
| event_status_ = false; | |
| pthread_mutex_unlock(&event_mutex_); | |
| return (error == 0); | |
| } | |
| #endif | |
| } // namespace webrtc |