blob: 3703abe1fb3125c465313e2ec2b1c1255363673f [file] [log] [blame]
/*
* Copyright 2010 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)
// Unit test the file_system_lock_manager using single-threaded mocks.
#include "pagespeed/kernel/util/file_system_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/mem_file_system.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/named_lock_manager.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/thread/mock_scheduler.h"
#include "pagespeed/kernel/thread/scheduler_based_abstract_lock.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;
} // namespace
class FileSystemLockManagerTest : public testing::Test {
protected:
FileSystemLockManagerTest()
: thread_system_(Platform::CreateThreadSystem()),
timer_(thread_system_->NewMutex(), 0),
scheduler_(thread_system_.get(), &timer_),
file_system_(thread_system_.get(), &timer_),
manager_(&file_system_, GTestTempDir(), &scheduler_, &handler_) { }
virtual ~FileSystemLockManagerTest() { }
SchedulerBasedAbstractLock* MakeLock(const StringPiece& name) {
SchedulerBasedAbstractLock* result = manager_.CreateNamedLock(name);
CHECK(NULL != result) << "Creating lock " << name;
EXPECT_EQ(StrCat(GTestTempDir(), "/", name), result->name());
return result;
}
void AllLocksFail(SchedulerBasedAbstractLock* lock) {
// Note: we do it in this order to make sure that the timed waits don't
// cause the lock to time out.
// Note also that we don't do the blocking lock operations, as they'll block
// indefinitely here!
EXPECT_FALSE(lock->TryLock());
EXPECT_FALSE(lock->TryLockStealOld(kStealMs));
EXPECT_FALSE(lock->LockTimedWaitStealOld(kWaitMs, kStealMs));
EXPECT_FALSE(lock->LockTimedWait(kWaitMs));
}
MockTimer* timer() {
return &timer_;
}
bool TryLock(const scoped_ptr<SchedulerBasedAbstractLock>& lock) {
return lock->TryLock();
}
bool TryLockStealOld(int64 steal_ms,
const scoped_ptr<SchedulerBasedAbstractLock>& lock) {
return lock->TryLockStealOld(steal_ms);
}
scoped_ptr<ThreadSystem> thread_system_;
MockTimer timer_;
MockScheduler scheduler_;
GoogleMessageHandler handler_;
MemFileSystem file_system_;
FileSystemLockManager manager_;
private:
DISALLOW_COPY_AND_ASSIGN(FileSystemLockManagerTest);
};
namespace {
TEST_F(FileSystemLockManagerTest, LockUnlock) {
scoped_ptr<SchedulerBasedAbstractLock> 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.get());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1.get());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(lock1->LockTimedWait(kWaitMs));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1.get());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1.get());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_TRUE(lock1->LockTimedWaitStealOld(kWaitMs, kStealMs));
EXPECT_TRUE(lock1->Held());
AllLocksFail(lock1.get());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
}
TEST_F(FileSystemLockManagerTest, DoubleLockUnlock) {
scoped_ptr<SchedulerBasedAbstractLock> lock1(MakeLock(kLock1));
scoped_ptr<SchedulerBasedAbstractLock> 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.get());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
EXPECT_TRUE(TryLock(lock1));
AllLocksFail(lock11.get());
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
EXPECT_TRUE(lock1->LockTimedWait(kWaitMs));
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
AllLocksFail(lock11.get());
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.get());
lock1->Unlock();
EXPECT_FALSE(lock1->Held());
EXPECT_FALSE(lock11->Held());
EXPECT_TRUE(lock1->LockTimedWaitStealOld(kWaitMs, kStealMs));
EXPECT_TRUE(lock1->Held());
EXPECT_FALSE(lock11->Held());
AllLocksFail(lock11.get());
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(FileSystemLockManagerTest, UnlockOnDestruct) {
scoped_ptr<SchedulerBasedAbstractLock> lock1(MakeLock(kLock1));
{
scoped_ptr<SchedulerBasedAbstractLock> lock11(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock11));
EXPECT_FALSE(TryLock(lock1));
// Should implicitly unlock on lock11 destructor call.
}
EXPECT_TRUE(TryLock(lock1));
}
TEST_F(FileSystemLockManagerTest, LockIndependence) {
// Differently-named locks are different.
scoped_ptr<SchedulerBasedAbstractLock> lock1(MakeLock(kLock1));
scoped_ptr<SchedulerBasedAbstractLock> lock2(MakeLock(kLock2));
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(TryLock(lock2));
EXPECT_FALSE(TryLock(lock1));
EXPECT_FALSE(TryLock(lock2));
lock2->Unlock();
EXPECT_FALSE(TryLock(lock1));
EXPECT_TRUE(TryLock(lock2));
}
TEST_F(FileSystemLockManagerTest, TimeoutFail) {
scoped_ptr<SchedulerBasedAbstractLock> lock1(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
EXPECT_TRUE(lock1->Held());
int64 start_ms = timer()->NowMs();
EXPECT_FALSE(lock1->LockTimedWait(kWaitMs));
EXPECT_TRUE(lock1->Held()); // was never unlocked...
int64 end_ms = timer()->NowMs();
EXPECT_LE(start_ms + kWaitMs, end_ms);
}
TEST_F(FileSystemLockManagerTest, StealOld) {
scoped_ptr<SchedulerBasedAbstractLock> lock1(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
// Now we can't steal the lock until after >kStealMs has elapsed.
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1));
timer()->AdvanceMs(kStealMs);
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1));
// But 1ms longer than kStealMs and we can steal the lock.
timer()->AdvanceMs(1);
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1));
// After steal the timer should reset.
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1));
timer()->AdvanceMs(kStealMs);
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1));
EXPECT_TRUE(lock1->Held()); // was never unlocked...
// But again expire after >kStealMs elapses.
timer()->AdvanceMs(1);
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1));
EXPECT_TRUE(lock1->Held()); // was never unlocked...
}
TEST_F(FileSystemLockManagerTest, BlockingStealOld) {
scoped_ptr<SchedulerBasedAbstractLock> lock1(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
// Now a call to LockTimedWaitStealOld should block until kStealMs has
// elapsed.
int64 start_ms = timer()->NowMs();
lock1->LockTimedWaitStealOld(kStealMs * 100, kStealMs);
int64 end_ms = timer()->NowMs();
EXPECT_LT(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, lock1));
timer()->AdvanceMs(kStealMs);
EXPECT_FALSE(TryLockStealOld(kStealMs, lock1));
timer()->AdvanceMs(1);
EXPECT_TRUE(TryLockStealOld(kStealMs, lock1));
}
TEST_F(FileSystemLockManagerTest, WaitStealOld) {
scoped_ptr<SchedulerBasedAbstractLock> lock1(MakeLock(kLock1));
EXPECT_TRUE(TryLock(lock1));
int64 start_ms = timer()->NowMs();
// If we start now, we'll time out with time to spare.
EXPECT_FALSE(lock1->LockTimedWaitStealOld(kWaitMs, kStealMs));
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(lock1->LockTimedWaitStealOld(kWaitMs, kStealMs));
end_ms = timer()->NowMs();
EXPECT_GT(start_ms + kWaitMs, end_ms);
}
} // namespace
} // namespace net_instaweb