diff options
Diffstat (limited to 'src/system_wrappers/source/condition_variable_unittest.cc')
-rw-r--r-- | src/system_wrappers/source/condition_variable_unittest.cc | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/src/system_wrappers/source/condition_variable_unittest.cc b/src/system_wrappers/source/condition_variable_unittest.cc new file mode 100644 index 0000000000..a9fdd0d24e --- /dev/null +++ b/src/system_wrappers/source/condition_variable_unittest.cc @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2012 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/interface/condition_variable_wrapper.h" + +#include "gtest/gtest.h" +#include "system_wrappers/interface/critical_section_wrapper.h" +#include "system_wrappers/interface/thread_wrapper.h" +#include "system_wrappers/interface/trace.h" +#include "system_wrappers/source/unittest_utilities.h" + +namespace webrtc { + +namespace { + +const int kLogTrace = false; // Set to true to enable debug logging to stdout. +const int kLongWaitMs = 100*1000; // A long time in testing terms +const int kShortWaitMs = 2*1000; // Long enough for process switches to happen + +#define LOG(...) WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, __VA_ARGS__); + +// A Baton is one possible control structure one can build using +// conditional variables. +// A Baton is always held by one and only one active thread - unlike +// a lock, it can never be free. +// One can pass it or grab it - both calls have timeouts. +// Note - a production tool would guard against passing it without +// grabbing it first. This one is for testing, so it doesn't. +class Baton { + public: + Baton() + : giver_sect_(CriticalSectionWrapper::CreateCriticalSection()), + crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), + cond_var_(ConditionVariableWrapper::CreateConditionVariable()), + being_passed_(false), + pass_count_(0) { + } + + ~Baton() { + delete giver_sect_; + delete crit_sect_; + delete cond_var_; + } + + // Pass the baton. Returns false if baton is not picked up in |max_msecs|. + // Only one process can pass at the same time; this property is + // ensured by the |giver_sect_| lock. + bool Pass(WebRtc_UWord32 max_msecs) { + LOG("Locking giver_sect"); + CriticalSectionScoped cs_giver(giver_sect_); + LOG("Locked giver_sect, locking crit_sect"); + CriticalSectionScoped cs(crit_sect_); + SignalBatonAvailable(); + const bool result = TakeBatonIfStillFree(max_msecs); + if (result) { + ++pass_count_; + LOG("Pass count is %d", pass_count_); + } + return result; + } + + // Grab the baton. Returns false if baton is not passed. + bool Grab(WebRtc_UWord32 max_msecs) { + CriticalSectionScoped cs(crit_sect_); + return WaitUntilBatonOffered(max_msecs); + } + + int PassCount() { + // We don't allow polling PassCount() during a Pass()-call since there is + // no guarantee that |pass_count_| is incremented until the Pass()-call + // finishes. I.e. the Grab()-call may finish before |pass_count_| has been + // incremented. + // Thus, this function waits on giver_sect_. + CriticalSectionScoped cs(giver_sect_); + return pass_count_; + } + + private: + // Wait/Signal forms a classical semaphore on |being_passed_|. + // These functions must be called with crit_sect_ held. + bool WaitUntilBatonOffered(int timeout_ms) { + while (!being_passed_) { + LOG("Wait waiting"); + if (!cond_var_->SleepCS(*crit_sect_, timeout_ms)) { + LOG("Wait timeout"); + return false; + } + } + being_passed_ = false; + cond_var_->Wake(); + return true; + } + + void SignalBatonAvailable() { + assert(!being_passed_); + being_passed_ = true; + LOG("Signal waking"); + cond_var_->Wake(); + } + + // Timeout extension: Wait for a limited time for someone else to + // take it, and take it if it's not taken. + // Returns true if resource is taken by someone else, false + // if it is taken back by the caller. + // This function must be called with both |giver_sect_| and + // |crit_sect_| held. + bool TakeBatonIfStillFree(int timeout_ms) { + bool not_timeout = true; + while (being_passed_ && not_timeout) { + LOG("Takeback waiting"); + not_timeout = cond_var_->SleepCS(*crit_sect_, timeout_ms); + // If we're woken up while variable is still held, we may have + // gotten a wakeup destined for a grabber thread. + // This situation is not treated specially here. + } + if (!being_passed_) { + return true; + } else { + LOG("Takeback grab"); + assert(!not_timeout); + being_passed_ = false; + return false; + } + } + + // Lock that ensures that there is only one thread in the active + // part of Pass() at a time. + // |giver_sect_| must always be acquired before |cond_var_|. + CriticalSectionWrapper* giver_sect_; + // Lock that protects |being_passed_|. + CriticalSectionWrapper* crit_sect_; + ConditionVariableWrapper* cond_var_; + bool being_passed_; + // Statistics information: Number of successfull passes. + int pass_count_; +}; + +// Function that waits on a Baton, and passes it right back. +// We expect these calls never to time out. +bool WaitingRunFunction(void* obj) { + Baton* the_baton = static_cast<Baton*> (obj); + LOG("Thread waiting"); + EXPECT_TRUE(the_baton->Grab(kLongWaitMs)); + LOG("Thread waking parent"); + EXPECT_TRUE(the_baton->Pass(kLongWaitMs)); + return true; +} + +class CondVarTest : public ::testing::Test { + public: + CondVarTest() + : trace_(kLogTrace) { + } + + virtual void SetUp() { + thread_ = ThreadWrapper::CreateThread(&WaitingRunFunction, + &baton_); + unsigned int id = 42; + ASSERT_TRUE(thread_->Start(id)); + } + + virtual void TearDown() { + // We have to wake the thread in order to make it obey the stop order. + // But we don't know if the thread has completed the run function, so + // we don't know if it will exit before or after the Pass. + // Thus, we need to pin it down inside its Run function (between Grab + // and Pass). + ASSERT_TRUE(baton_.Pass(kShortWaitMs)); + thread_->SetNotAlive(); + ASSERT_TRUE(baton_.Grab(kShortWaitMs)); + ASSERT_TRUE(thread_->Stop()); + delete thread_; + } + + protected: + Baton baton_; + + private: + ScopedTracing trace_; + ThreadWrapper* thread_; +}; + +// The SetUp and TearDown functions use condition variables. +// This test verifies those pieces in isolation. +TEST_F(CondVarTest, InitFunctionsWork) { + // All relevant asserts are in the SetUp and TearDown functions. +} + +// This test verifies that one can use the baton multiple times. +TEST_F(CondVarTest, PassBatonMultipleTimes) { + const int kNumberOfRounds = 2; + for (int i = 0; i < kNumberOfRounds; ++i) { + ASSERT_TRUE(baton_.Pass(kShortWaitMs)); + ASSERT_TRUE(baton_.Grab(kShortWaitMs)); + } + EXPECT_EQ(2*kNumberOfRounds, baton_.PassCount()); +} + +} // anonymous namespace + +} // namespace webrtc |