blob: 5dc27e0dc4dd4b93951134bfa7e59f3384d3ceb0 [file] [log] [blame]
/*
* Copyright 2015 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: jmarantz@google.com (Joshua Marantz)
#include "pagespeed/kernel/util/lock_manager_spammer.h"
#include <memory>
#include <vector>
#include "pagespeed/kernel/base/atomic_int32.h"
#include "pagespeed/kernel/base/condvar.h"
#include "pagespeed/kernel/base/dynamic_annotations.h"
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/named_lock_manager.h"
#include "pagespeed/kernel/base/shared_string.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/util/threadsafe_lock_manager.h"
namespace {
int WaitMs() { return RunningOnValgrind() ? 2000 : 200; }
int StealMs() { return RunningOnValgrind() ? 1000 : 100; }
// In our test, we set the lock wait timeout at 100ms and the steal
// time to 200ms. But we insert timing delays of 150ms so that we
// successfully steal a lock that is not released in the second round
// of lock-requests. But for the third round of lock-requests, which
// occurs at 300ms, the timeout will expire.
//
// All the times are multipled by 10 for valgrind, since these are real-time.
// Leaving them as is results in a modest percentage of flakes in valgrind
// tests.
int DelayMs() { return (WaitMs() + StealMs()) / 2; } // 1500ms or 150ms.
} // namespace
namespace net_instaweb {
LockManagerSpammer::LockManagerSpammer(Scheduler* scheduler,
ThreadSystem::ThreadFlags flags,
const StringVector& lock_names,
ThreadSafeLockManager* lock_manager,
bool expecting_denials,
bool delay_unlocks,
int index,
int num_iters,
int num_names,
CountDown* pending_threads)
: Thread(scheduler->thread_system(), "lock_manager_spammer", flags),
scheduler_(scheduler),
lock_names_(lock_names),
lock_manager_(lock_manager),
expecting_denials_(expecting_denials),
delay_unlocks_(delay_unlocks),
index_(index),
num_iters_(num_iters),
num_names_(num_names),
mutex_(scheduler->thread_system()->NewMutex()),
condvar_(mutex_->NewCondvar()),
grants_(0),
denials_(0),
pending_threads_(pending_threads) {
}
LockManagerSpammer::~LockManagerSpammer() {
}
LockManagerSpammer::CountDown::CountDown(Scheduler* scheduler,
int initial_value)
: scheduler_(scheduler),
value_(initial_value) {
}
LockManagerSpammer::CountDown::~CountDown() {
}
void LockManagerSpammer::CountDown::RunAlarmsTillThreadsComplete() {
ScopedMutex lock(scheduler_->mutex());
bool running = true;
while ((value_ != 0) || running) {
running = scheduler_->ProcessAlarmsOrWaitUs(100);
}
}
void LockManagerSpammer::CountDown::Decrement() {
ScopedMutex lock(scheduler_->mutex());
--value_;
if (value_ == 0) {
scheduler_->Signal();
}
}
void LockManagerSpammer::RunTests(int num_threads,
int num_iters,
int num_names,
bool expecting_denials,
bool delay_unlocks,
ThreadSafeLockManager* lock_manager,
Scheduler* scheduler) {
std::vector<LockManagerSpammer*> spammers(num_threads);
CountDown pending_threads(scheduler, num_threads);
StringVector lock_names;
const char name_pattern[] = "name%d";
for (int i = 0; i < num_names; ++i) {
lock_names.push_back(StringPrintf(name_pattern, i));
}
// First, create all the threads.
for (int i = 0; i < num_threads; ++i) {
spammers[i] = new LockManagerSpammer(
scheduler, ThreadSystem::kJoinable, lock_names,
lock_manager,
expecting_denials, delay_unlocks, i, num_iters,
num_names,
&pending_threads);
}
// Then, start them.
for (int i = 0; i < num_threads; ++i) {
spammers[i]->Start();
}
pending_threads.RunAlarmsTillThreadsComplete();
// Finally, wait for them to complete by joining them.
for (int i = 0; i < num_threads; ++i) {
spammers[i]->Join();
delete spammers[i];
}
}
void LockManagerSpammer::Run() {
int num_locks = num_iters_ * num_names_;
std::vector<NamedLock*> locks;
locks.reserve(num_locks);
for (int i = 0; i < num_iters_; ++i) {
for (int j = 0; j < num_names_; ++j) {
NamedLock* lock = lock_manager_->CreateNamedLock(lock_names_[j]);
Function* callback = MakeFunction(
this, &LockManagerSpammer::Granted, &LockManagerSpammer::Denied,
lock);
lock->LockTimedWaitStealOld(WaitMs(), StealMs(), callback);
locks.push_back(lock);
}
}
{
ScopedMutex lock(mutex_.get());
while (denials_ + grants_ < num_locks) {
condvar_->Wait();
while (!queued_unlocks_.empty()) {
LockVector locks;
locks.swap(queued_unlocks_);
mutex_->Unlock();
for (int i = 0, n = locks.size(); i < n; ++i) {
locks[i]->Unlock();
}
mutex_->Lock();
}
}
if (!expecting_denials_) {
EXPECT_EQ(0, denials_);
}
}
for (int i = 0; i < num_locks; ++i) {
delete locks[i];
}
pending_threads_->Decrement();
}
void LockManagerSpammer::Granted(NamedLock* lock) {
if (delay_unlocks_) {
// Schedule the unlocking for some future point in time that's
// beyond the steal-time but smaller than the wait time.
int64 wakeup_time_us = scheduler_->timer()->NowUs() +
DelayMs() * Timer::kMsUs;
Function* callback = MakeFunction(
this, &LockManagerSpammer::UnlockAfterGrant, lock);
scheduler_->AddAlarmAtUs(wakeup_time_us, callback);
} else {
lock->Unlock();
{
ScopedMutex mutex_lock(mutex_.get());
++grants_;
condvar_->Signal();
}
}
}
void LockManagerSpammer::UnlockAfterGrant(NamedLock* lock) {
ScopedMutex mutex_lock(mutex_.get());
queued_unlocks_.push_back(lock);
++grants_;
condvar_->Signal();
}
void LockManagerSpammer::Denied(NamedLock* lock) {
ScopedMutex scoped_mutex(mutex_.get());
++denials_;
condvar_->Signal();
}
} // namespace net_instaweb