blob: 5c81d6120dc2b4d0ec6f15b2fc8a9c96fb9e8e54 [file] [log] [blame]
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: jmaessen@google.com (Jan Maessen)
#ifndef PAGESPEED_KERNEL_THREAD_CONDVAR_TEST_BASE_H_
#define PAGESPEED_KERNEL_THREAD_CONDVAR_TEST_BASE_H_
#include "pagespeed/kernel/base/condvar.h"
#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/base/thread_system.h"
namespace net_instaweb {
class CondvarTestBase : public testing::Test {
protected:
typedef void (ThreadSystem::Condvar::*SignalMethod)();
CondvarTestBase()
: mutex_(NULL),
startup_condvar_(NULL),
condvar_(NULL),
ready_to_start_(false),
iters_(0),
current_iter_(0),
signal_method_(&ThreadSystem::Condvar::Signal),
wait_after_signal_(false),
helper_increments_(0),
init_called_(false) {
}
// Init is intended to be called from the constructor of the derived class.
// Ownership of the objects remains with the caller.
void Init(ThreadSystem::CondvarCapableMutex* mutex,
ThreadSystem::Condvar* startup_condvar,
ThreadSystem::Condvar* condvar) {
CHECK(!init_called_);
mutex_ = mutex;
startup_condvar_ = startup_condvar;
condvar_ = condvar;
init_called_ = true;
}
static void* HelperThread(void* data) {
CondvarTestBase* test = static_cast<CondvarTestBase*>(data);
test->HelperThreadMethod();
return NULL;
}
// CreateHelper creates a thread that ultimately calls
// this->HelperThreadMethod(), most simply by invoking HelperThread(this). It
// runs this method to completion, and then terminates gracefully.
virtual void CreateHelper() = 0;
// FinishHelper is called in the main thread to wait for graceful termination
// of the thread created by CreateHelper.
virtual void FinishHelper() = 0;
void StartHelper() {
CHECK(init_called_);
CreateHelper();
{
ScopedMutex lock(mutex_);
ready_to_start_ = true;
startup_condvar_->Signal();
}
}
virtual void HelperThreadMethod() {
{
ScopedMutex lock(mutex_);
while (!ready_to_start_) {
startup_condvar_->Wait();
}
}
while (true) {
ScopedMutex lock(mutex_);
// We must hold the mutex to access the iteration count and check the loop
// condition.
int iter = current_iter_ + 1;
if (iter > iters_) {
return;
}
++helper_increments_;
current_iter_ = iter;
(condvar_->*signal_method_)();
while (wait_after_signal_ && current_iter_ == iter) {
condvar_->Wait();
}
}
}
// Below are the common tests that should be run by every client.
// Make sure we can start and stop the helper gracefully.
void StartupTest() {
StartHelper();
EXPECT_TRUE(ready_to_start_);
FinishHelper();
EXPECT_EQ(helper_increments_, 0);
}
// Run the helper without interacting with it.
// Also run with signal_method_ = &ThreadSystem::Condvar::Broadcast
void BlindSignalsTest() {
iters_ = 10;
StartHelper();
EXPECT_TRUE(ready_to_start_);
FinishHelper();
EXPECT_EQ(helper_increments_, 10);
}
// Use condvars to pass control back and forth between worker and main thread.
// Also run with signal_method_ = &ThreadSystem::Condvar::Broadcast
void PingPongTest() {
iters_ = 10;
wait_after_signal_ = true;
StartHelper();
int local_increments = 0;
while (true) {
ScopedMutex lock(mutex_);
while ((current_iter_ % 2) == 0 && current_iter_ < iters_) {
condvar_->Wait();
}
// We must hold the mutex to access the iteration count and check the loop
// condition.
if (current_iter_ == iters_) {
break;
}
++current_iter_;
++local_increments;
condvar_->Signal();
}
EXPECT_EQ(local_increments, 5);
FinishHelper();
EXPECT_EQ(helper_increments_, 5);
}
// Make sure that TimedWait eventually progresses in the absence of a signal.
void TimeoutTest() {
iters_ = 0;
StartHelper();
{
ScopedMutex lock(mutex_);
condvar_->TimedWait(10); // This will deadlock if we don't time out.
}
FinishHelper();
}
// Make sure that a long timeout doesn't exit too early.
void LongTimeoutTest(int wait_ms) {
iters_ = 0;
StartHelper();
int64 start_ms = timer()->NowMs();
{
ScopedMutex lock(mutex_);
condvar_->TimedWait(wait_ms);
}
int64 end_ms = timer()->NowMs();
// This test should not be flaky even if it runs slowly, as we are
// not placing an *upper* bound on the lock duration.
EXPECT_LE(wait_ms, end_ms - start_ms);
FinishHelper();
}
// Use condvars to pass control back and forth between worker and main thread.
// Final interaction will be one-sided and will time out.
// Also run with signal_method_ = &ThreadSystem::Condvar::Broadcast
void TimeoutPingPongTest() {
iters_ = 10;
wait_after_signal_ = true;
StartHelper();
int local_increments = 0;
while (true) {
ScopedMutex lock(mutex_);
if ((current_iter_ % 2) == 0) {
condvar_->TimedWait(1);
}
// We must hold the mutex to access the iteration count and check the
// loop condition. Note that in case of timeout we might get here with
// current_iter_ % 2 == 0, so we might perform more local increments
// than we expect.
if (current_iter_ > iters_) {
break;
}
++current_iter_;
++local_increments;
condvar_->Signal();
}
FinishHelper();
EXPECT_LE(6, local_increments);
EXPECT_GE(5, helper_increments_);
EXPECT_EQ(11, local_increments + helper_increments_);
}
virtual Timer* timer() = 0;
ThreadSystem::CondvarCapableMutex* mutex_;
ThreadSystem::Condvar* startup_condvar_;
ThreadSystem::Condvar* condvar_;
bool ready_to_start_;
int iters_;
int current_iter_;
SignalMethod signal_method_;
bool wait_after_signal_;
int helper_increments_;
bool init_called_;
private:
DISALLOW_COPY_AND_ASSIGN(CondvarTestBase);
};
} // namespace net_instaweb
#endif // PAGESPEED_KERNEL_THREAD_CONDVAR_TEST_BASE_H_