| /* |
| * 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 |