blob: f39aa41e2f8e19df7895bcc0f0d59aa22ca5d9a1 [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)
// Unit test the MemLockManager. Note that the lock-server is designed to be
// run in a separate process, but we are testing it all in-process. It
// is intended to be run with some sort of scheduling infrastructure (not
// necessarily net_instaweb::Scheduler) to wake up on timer events. The
//
// MemLockManager needs to wake up occasionally to report failure to locks that
// specify a timeout, or to enable steeling locks. It exposes NextWakeupTimeMs
// to allow this scheduling to occur, and this is called directly by the test
// framework using MockTimer.
#include "pagespeed/kernel/util/mem_lock_manager.h"
#include "base/logging.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/google_message_handler.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/named_lock_manager.h"
#include "pagespeed/kernel/base/named_lock_tester.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/util/platform.h"
namespace net_instaweb {
namespace {
const char kLock1[] = "lock1";
const char kLock2[] = "lock2";
const int64 kStealMs = 50000;
const int64 kWaitMs = 10000;
class MemLockManagerTest : public testing::Test {
protected:
MemLockManagerTest()
: thread_system_(Platform::CreateThreadSystem()),
timer_(thread_system_->NewMutex(), 0),
lock_manager_(new MemLockManager(&timer_)),
tester_(thread_system_.get()) {
tester_.set_quiesce(MakeFunction(this, &MemLockManagerTest::Quiesce));
}
virtual ~MemLockManagerTest() { }
NamedLock* MakeLock(const StringPiece& name) {
return lock_manager_->CreateNamedLock(name);
}
void AllLocksFail(const scoped_ptr<NamedLock>& lock) {
scoped_ptr<NamedLock> lock2(MakeLock(lock->name()));
EXPECT_FALSE(TryLock(lock2));
EXPECT_FALSE(TryLockStealOld(kStealMs, lock2));
EXPECT_FALSE(LockTimedWaitStealOld(kWaitMs, kStealMs, lock2));
EXPECT_FALSE(LockTimedWait(kWaitMs, lock2));
}
MockTimer* timer() {
return &timer_;
}
bool TryLock(const scoped_ptr<NamedLock>& lock) {
return tester_.TryLock(lock.get());
}
bool TryLockStealOld(int64 steal_ms, const scoped_ptr<NamedLock>& lock) {
return tester_.LockTimedWaitStealOld(0 /* wait_ms */, steal_ms, lock.get());
}
bool LockTimedWaitStealOld(int64 wait_ms, int64 steal_ms,
const scoped_ptr<NamedLock>& lock) {
return tester_.LockTimedWaitStealOld(wait_ms, steal_ms, lock.get());
}
bool LockTimedWait(int64 wait_ms, const scoped_ptr<NamedLock>& lock) {
return tester_.LockTimedWait(wait_ms, lock.get());
}
void Quiesce() {
int64 wakeup_ms;
while ((wakeup_ms = lock_manager_->NextWakeupTimeMs()) !=
MemLockManager::kNoWakeupsPending) {
timer_.SetTimeMs(wakeup_ms);
lock_manager_->Wakeup();
}
}
void LogGrant(const char* name) {
StrAppend(&log_, name, "(grant) ");
}
void LogDeny(const char* name) {
StrAppend(&log_, name, "(deny) ");
}
Function* LogFunction(const char* name) {
return MakeFunction(this,
&MemLockManagerTest::LogGrant,
&MemLockManagerTest::LogDeny,
name);
}
protected:
scoped_ptr<ThreadSystem> thread_system_;
MockTimer timer_;
GoogleMessageHandler handler_;
scoped_ptr<MemLockManager> lock_manager_;
NamedLockTester tester_;
GoogleString log_;
private:
DISALLOW_COPY_AND_ASSIGN(MemLockManagerTest);
};
TEST_F(MemLockManagerTest, LockUnlock) {
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
// Just do pairs of matched lock / unlock, making sure
// we can't lock while the lock is held.
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(LockTimedWait(kWaitMs, lock1));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(LockTimedWaitStealOld(kWaitMs, kStealMs, lock1));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
}
TEST_F(MemLockManagerTest, DoubleLockUnlock) {
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
scoped_ptr<NamedLock> lock11(MakeLock(kLock1));
// Just do pairs of matched lock / unlock, but make sure
// we hold a separate lock object with the same lock name.
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
AllLocksFail(lock11);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
EXPECT_TRUE(TryLock(lock1));
AllLocksFail(lock11);
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
EXPECT_TRUE(LockTimedWait(kWaitMs, lock1));
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
AllLocksFail(lock11);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1));
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
AllLocksFail(lock11);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
EXPECT_TRUE(LockTimedWaitStealOld(kWaitMs, kStealMs, lock1));
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
AllLocksFail(lock11);
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
}
// From this point, we assume all the locking routines hold
// the lock in equivalent ways. Now we just need to check that
// their timeout behaviors are correct.
TEST_F(MemLockManagerTest, UnlockOnDestruct) {
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
{
scoped_ptr<NamedLock> lock11(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock11));
EXPECT_FALSE(TryLock(lock1));
// Should implicitly unlock on lock11 destructor call.
}
EXPECT_TRUE(TryLock(lock1));
}
TEST_F(MemLockManagerTest, LockIndependence) {
// Differently-named locks are different.
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
scoped_ptr<NamedLock> lock1a(MakeLock(kLock1));
scoped_ptr<NamedLock> lock2(MakeLock(kLock2));
scoped_ptr<NamedLock> lock2a(MakeLock(kLock2));
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(TryLock(lock2));
EXPECT_FALSE(TryLock(lock1a));
EXPECT_FALSE(TryLock(lock2a));
lock2->Unlock();
EXPECT_FALSE(TryLock(lock1a));
EXPECT_TRUE(TryLock(lock2));
}
TEST_F(MemLockManagerTest, TimeoutFail) {
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(lock1->Held());
int64 start_ms = timer()->NowMs();
scoped_ptr<NamedLock> lock1a(MakeLock(kLock1)); // Same name, new object.
EXPECT_FALSE(LockTimedWait(kWaitMs, lock1a));
EXPECT_TRUE(lock1->Held()); // was never unlocked...
int64 end_ms = timer()->NowMs();
EXPECT_LE(start_ms + kWaitMs, end_ms);
}
TEST_F(MemLockManagerTest, StealOld) {
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
scoped_ptr<NamedLock> lock1a(MakeLock(kLock1));
scoped_ptr<NamedLock> lock1b(MakeLock(kLock1));
scoped_ptr<NamedLock> lock1c(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
// Now we can't steal the lock until after >kStealMs has elapsed.
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1a));
timer()->AdvanceMs(kStealMs);
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1a));
// But 1ms longer than kStealMs and we can steal the lock.
EXPECT_TRUE(lock1->Held());
timer()->AdvanceMs(1);
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1a));
EXPECT_FALSE(lock1->Held()); // Stealing turns releases the lock.
// After steal the timer should reset.
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1b));
timer()->AdvanceMs(kStealMs);
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1b));
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(lock1a->Held()); // 1a still has the lock
EXPECT_FALSE(lock1b->Held()); // 1b doesn't have it yet.
// But again expire after >kStealMs elapses.
timer()->AdvanceMs(1);
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1c));
EXPECT_FALSE(lock1b->Held());
EXPECT_TRUE(lock1c->Held());
}
TEST_F(MemLockManagerTest, BlockingStealOld) {
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
scoped_ptr<NamedLock> lock1a(MakeLock(kLock1));
scoped_ptr<NamedLock> lock1b(MakeLock(kLock1));
scoped_ptr<NamedLock> lock1c(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
// Now a call to LockTimedWaitStealOld should block until kStealMs has
// elapsed.
int64 start_ms = timer()->NowMs();
EXPECT_TRUE(LockTimedWaitStealOld(kStealMs * 100, kStealMs, lock1a));
int64 end_ms = timer()->NowMs();
EXPECT_EQ(start_ms + kStealMs, end_ms);
EXPECT_GT(start_ms + kStealMs * 100, end_ms);
// Again the timer should reset after the lock is obtained.
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1b));
timer()->AdvanceMs(kStealMs);
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1b));
timer()->AdvanceMs(1);
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1c));
}
TEST_F(MemLockManagerTest, WaitStealOld) {
scoped_ptr<NamedLock> lock1(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
int64 start_ms = timer()->NowMs();
// If we start now, we'll time out with time to spare.
scoped_ptr<NamedLock> lock1a(MakeLock(kLock1)); // Same name, new object.
EXPECT_FALSE(LockTimedWaitStealOld(kWaitMs, kStealMs, lock1a));
int64 end_ms = timer()->NowMs();
EXPECT_LE(start_ms + kWaitMs, end_ms);
EXPECT_GT(start_ms + kStealMs, end_ms);
// Advance time so that the lock timeout is within the wait time.
int64 time_ms = start_ms + kStealMs - kWaitMs / 2;
timer()->SetTimeUs(1000 * time_ms);
start_ms = timer()->NowMs();
EXPECT_TRUE(LockTimedWaitStealOld(kWaitMs, kStealMs, lock1a));
end_ms = timer()->NowMs();
EXPECT_GT(start_ms + kWaitMs, end_ms);
}
TEST_F(MemLockManagerTest, MultipleLocksSameTimeouts) {
scoped_ptr<NamedLock> a(MakeLock(kLock1));
scoped_ptr<NamedLock> b(MakeLock(kLock1));
scoped_ptr<NamedLock> c(MakeLock(kLock1));
scoped_ptr<NamedLock> d(MakeLock(kLock2));
scoped_ptr<NamedLock> e(MakeLock(kLock2));
a->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("a"));
b->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("b"));
c->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("c"));
d->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("d"));
e->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("e"));
Quiesce();
// Because kStealMs (50k) is larger than kWaitMs (10k), both b and
// c will be cancelled before they steal the lock from a, ane e
// will be canceled before i steals the lock from d.
EXPECT_STREQ("a(grant) d(grant) b(deny) c(deny) e(deny) ", log_);
EXPECT_EQ(kWaitMs, timer()->NowMs());
log_.clear();
// However, if we add some more delay (to 45k ms) and try to lock b and c
// again, then the Steal should occur for b, but c will timeout.
timer()->SetTimeMs(kStealMs - (kWaitMs / 2) /* 45000 */);
b->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("b"));
c->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("c"));
Quiesce();
EXPECT_STREQ("b(grant) c(deny) ", log_);
}
TEST_F(MemLockManagerTest, OverrideSteal) {
scoped_ptr<NamedLock> a(MakeLock(kLock1));
scoped_ptr<NamedLock> b(MakeLock(kLock1));
scoped_ptr<NamedLock> c(MakeLock(kLock1));
a->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("a"));
timer()->SetTimeMs(kStealMs - (kWaitMs / 2) /* 45000 */);
b->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("b"));
// Before 'b' gets a chance to steal a's lock, c comes in with
// a smaller steal delay (1 ms less), so it will get to steal the
// lock and not a.
c->LockTimedWaitStealOld(kWaitMs, kStealMs - 1, LogFunction("c"));
Quiesce();
EXPECT_STREQ("a(grant) c(grant) b(deny) ", log_);
}
TEST_F(MemLockManagerTest, UnlockRevealingNewStealer) {
scoped_ptr<NamedLock> a(MakeLock(kLock1));
scoped_ptr<NamedLock> b(MakeLock(kLock1));
scoped_ptr<NamedLock> c(MakeLock(kLock1));
a->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("a"));
b->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("b"));
c->LockTimedWaitStealOld(2*kStealMs, kStealMs, LogFunction("c"));
timer()->SetTimeMs(1);
lock_manager_->Wakeup();
EXPECT_TRUE(a->Held());
timer()->SetTimeMs(kStealMs - (kWaitMs / 2) /* 45000 */);
a->Unlock(); // grants B, C is stealer
Quiesce(); // C steals cause it has long wait
EXPECT_STREQ("a(grant) b(grant) c(grant) ", log_);
}
TEST_F(MemLockManagerTest, CloseManagerWithActiveLocks) {
scoped_ptr<NamedLock> a(MakeLock(kLock1));
scoped_ptr<NamedLock> b(MakeLock(kLock1));
scoped_ptr<NamedLock> c(MakeLock(kLock1));
scoped_ptr<NamedLock> d(MakeLock(kLock1));
a->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("a"));
b->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("b"));
c->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("c"));
lock_manager_.reset(NULL); // Locks still active.
EXPECT_STREQ("a(grant) b(deny) c(deny) ", log_);
}
TEST_F(MemLockManagerTest, DeletePendingLock) {
scoped_ptr<NamedLock> a(MakeLock(kLock1));
scoped_ptr<NamedLock> b(MakeLock(kLock1));
scoped_ptr<NamedLock> c(MakeLock(kLock1));
a->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("a"));
b->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("b"));
c->LockTimedWaitStealOld(kWaitMs, kStealMs, LogFunction("c"));
timer()->SetTimeMs(1);
lock_manager_->Wakeup();
EXPECT_TRUE(a->Held());
b.reset(NULL); // Denies B.
a->Unlock(); // Grants C.
Quiesce();
EXPECT_STREQ("a(grant) b(deny) c(grant) ", log_);
}
} // namespace
} // namespace net_instaweb