| /* |
| * Copyright (c) 2016 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 "system_wrappers/source/event_timer_posix.h" |
| |
| #include "rtc_base/criticalsection.h" |
| #include "rtc_base/event.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| |
| enum class ThreadState { |
| kNotStarted, |
| kWaiting, |
| kRequestProcessCall, |
| kCallingProcess, |
| kProcessDone, |
| kContinue, |
| kExiting, |
| kDead |
| }; |
| |
| class EventTimerPosixTest : public testing::Test, public EventTimerPosix { |
| public: |
| EventTimerPosixTest() |
| : thread_state_(ThreadState::kNotStarted), |
| process_event_(false, true), |
| main_event_(false, true), |
| process_thread_id_(0), |
| process_thread_(nullptr) {} |
| ~EventTimerPosixTest() override {} |
| |
| rtc::PlatformThread* CreateThread() override { |
| EXPECT_TRUE(process_thread_ == nullptr); |
| process_thread_ = |
| new rtc::PlatformThread(Run, this, "EventTimerPosixTestThread"); |
| return process_thread_; |
| } |
| |
| static bool Run(void* obj) { |
| return static_cast<EventTimerPosixTest*>(obj)->Process(); |
| } |
| |
| bool Process() { |
| bool res = ProcessInternal(); |
| if (!res) { |
| rtc::CritScope cs(&lock_); |
| thread_state_ = ThreadState::kDead; |
| main_event_.Set(); |
| } |
| return res; |
| } |
| |
| bool ProcessInternal() { |
| { |
| rtc::CritScope cs(&lock_); |
| if (thread_state_ == ThreadState::kNotStarted) { |
| if (!ChangeThreadState(ThreadState::kNotStarted, |
| ThreadState::kContinue)) { |
| ADD_FAILURE() << "Unable to start process thread"; |
| return false; |
| } |
| process_thread_id_ = rtc::CurrentThreadId(); |
| } |
| } |
| |
| if (!ChangeThreadState(ThreadState::kContinue, ThreadState::kWaiting)) |
| return false; |
| |
| if (!AwaitThreadState(ThreadState::kRequestProcessCall, |
| rtc::Event::kForever)) |
| return false; |
| |
| if (!ChangeThreadState(ThreadState::kRequestProcessCall, |
| ThreadState::kCallingProcess)) |
| return false; |
| |
| EventTimerPosix::Process(); |
| |
| if (!ChangeThreadState(ThreadState::kCallingProcess, |
| ThreadState::kProcessDone)) |
| return false; |
| |
| if (!AwaitThreadState(ThreadState::kContinue, rtc::Event::kForever)) |
| return false; |
| |
| return true; |
| } |
| |
| bool IsProcessThread() { |
| rtc::CritScope cs(&lock_); |
| return process_thread_id_ == rtc::CurrentThreadId(); |
| } |
| |
| bool ChangeThreadState(ThreadState prev_state, ThreadState new_state) { |
| rtc::CritScope cs(&lock_); |
| if (thread_state_ != prev_state) |
| return false; |
| thread_state_ = new_state; |
| if (IsProcessThread()) { |
| main_event_.Set(); |
| } else { |
| process_event_.Set(); |
| } |
| return true; |
| } |
| |
| bool AwaitThreadState(ThreadState state, int timeout) { |
| rtc::Event* event = IsProcessThread() ? &process_event_ : &main_event_; |
| do { |
| rtc::CritScope cs(&lock_); |
| if (state != ThreadState::kDead && thread_state_ == ThreadState::kExiting) |
| return false; |
| if (thread_state_ == state) |
| return true; |
| } while (event->Wait(timeout)); |
| return false; |
| } |
| |
| bool CallProcess(int timeout_ms) { |
| return AwaitThreadState(ThreadState::kWaiting, timeout_ms) && |
| ChangeThreadState(ThreadState::kWaiting, |
| ThreadState::kRequestProcessCall); |
| } |
| |
| bool AwaitProcessDone(int timeout_ms) { |
| return AwaitThreadState(ThreadState::kProcessDone, timeout_ms) && |
| ChangeThreadState(ThreadState::kProcessDone, ThreadState::kContinue); |
| } |
| |
| void TearDown() override { |
| if (process_thread_) { |
| { |
| rtc::CritScope cs(&lock_); |
| if (thread_state_ != ThreadState::kDead) { |
| thread_state_ = ThreadState::kExiting; |
| process_event_.Set(); |
| } |
| } |
| ASSERT_TRUE(AwaitThreadState(ThreadState::kDead, 5000)); |
| } |
| } |
| |
| ThreadState thread_state_; |
| rtc::CriticalSection lock_; |
| rtc::Event process_event_; |
| rtc::Event main_event_; |
| rtc::PlatformThreadId process_thread_id_; |
| rtc::PlatformThread* process_thread_; |
| }; |
| |
| TEST_F(EventTimerPosixTest, WaiterBlocksUntilTimeout) { |
| const int kTimerIntervalMs = 100; |
| const int kTimeoutMs = 5000; |
| ASSERT_TRUE(StartTimer(false, kTimerIntervalMs)); |
| ASSERT_TRUE(CallProcess(kTimeoutMs)); |
| EventTypeWrapper res = Wait(kTimeoutMs); |
| EXPECT_EQ(kEventSignaled, res); |
| ASSERT_TRUE(AwaitProcessDone(kTimeoutMs)); |
| } |
| |
| TEST_F(EventTimerPosixTest, WaiterWakesImmediatelyAfterTimeout) { |
| const int kTimerIntervalMs = 100; |
| const int kTimeoutMs = 5000; |
| ASSERT_TRUE(StartTimer(false, kTimerIntervalMs)); |
| ASSERT_TRUE(CallProcess(kTimeoutMs)); |
| ASSERT_TRUE(AwaitProcessDone(kTimeoutMs)); |
| EventTypeWrapper res = Wait(0); |
| EXPECT_EQ(kEventSignaled, res); |
| } |
| |
| TEST_F(EventTimerPosixTest, WaiterBlocksUntilTimeoutProcessInactiveOnStart) { |
| const int kTimerIntervalMs = 100; |
| const int kTimeoutMs = 5000; |
| // First call to StartTimer initializes thread. |
| ASSERT_TRUE(StartTimer(false, kTimerIntervalMs)); |
| |
| // Process thread currently _not_ blocking on Process() call. |
| ASSERT_TRUE(AwaitThreadState(ThreadState::kWaiting, kTimeoutMs)); |
| |
| // Start new one-off timer, then call Process(). |
| ASSERT_TRUE(StartTimer(false, kTimerIntervalMs)); |
| ASSERT_TRUE(CallProcess(kTimeoutMs)); |
| |
| EventTypeWrapper res = Wait(kTimeoutMs); |
| EXPECT_EQ(kEventSignaled, res); |
| |
| ASSERT_TRUE(AwaitProcessDone(kTimeoutMs)); |
| } |
| |
| } // namespace webrtc |