blob: 65800b511373e225c8794e49e2037aec7e8ada9c [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: morlovich@google.com (Maksim Orlovich)
// Unit-test for QueuedAlarm
#include "pagespeed/kernel/thread/queued_alarm.h"
#include <unistd.h>
#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/thread/mock_scheduler.h"
#include "pagespeed/kernel/thread/queued_worker_pool.h"
#include "pagespeed/kernel/thread/scheduler_thread.h"
#include "pagespeed/kernel/thread/worker_test_base.h"
#include "pagespeed/kernel/util/platform.h"
namespace net_instaweb {
namespace {
class QueuedAlarmTest : public WorkerTestBase {
public:
QueuedAlarmTest()
: thread_system_(Platform::CreateThreadSystem()),
sequence_(NULL),
done_(false),
cancel_(false) {
}
virtual ~QueuedAlarmTest() {
ClearSequence();
}
MockScheduler* SetupWithMockScheduler() {
MockTimer* timer = new MockTimer(thread_system_->NewMutex(), 0);
MockScheduler* scheduler = new MockScheduler(thread_system_.get(), timer);
timer_.reset(timer);
scheduler_.reset(scheduler);
SetupWorker();
return scheduler;
}
void SetupWithRealScheduler() {
timer_.reset(Platform::CreateTimer());
scheduler_.reset(new Scheduler(thread_system_.get(), timer_.get()));
SetupWorker();
}
void MakeSequence() {
if (sequence_ == NULL) {
sequence_ = worker_->NewSequence();
// Take advantage of mock scheduler's quiescence detection.
scheduler_->RegisterWorker(sequence_);
}
}
void ClearSequence() {
if (sequence_ != NULL) {
scheduler_->UnregisterWorker(sequence_);
worker_->FreeSequence(sequence_);
sequence_ = NULL;
}
}
void MarkDone() { done_ = true; }
void MarkCancel() { cancel_ = true; }
Scheduler* scheduler() { return scheduler_.get(); }
QueuedWorkerPool::Sequence* sequence() { return sequence_; }
Timer* timer() { return timer_.get(); }
protected:
void SetupWorker() {
worker_.reset(
new QueuedWorkerPool(2, "queued_alarm_test", thread_system_.get()));
MakeSequence();
}
scoped_ptr<ThreadSystem> thread_system_;
scoped_ptr<Timer> timer_;
scoped_ptr<Scheduler> scheduler_;
scoped_ptr<QueuedWorkerPool> worker_;
QueuedWorkerPool::Sequence* sequence_;
bool done_, cancel_;
private:
DISALLOW_COPY_AND_ASSIGN(QueuedAlarmTest);
};
// A class that keeps track of an alarm object and runs ops on it in a sequence.
class TestAlarmHandler {
public:
TestAlarmHandler(QueuedAlarmTest* fixture, WorkerTestBase::SyncPoint* sync)
: fixture_(fixture),
sync_(sync),
alarm_(NULL),
fired_(false) {}
void StartAlarm() {
fixture_->sequence()->Add(
MakeFunction(this, &TestAlarmHandler::StartAlarmImpl));
}
void CancelAlarm() {
fixture_->sequence()->Add(
MakeFunction(this, &TestAlarmHandler::CancelAlarmImpl));
}
void FireAlarm() {
// Should not have run CancelAlarm if we got here!
EXPECT_TRUE(alarm_ != NULL);
// Nothing more to cancel.
alarm_ = NULL;
fired_ = true;
}
bool fired() const { return fired_; }
private:
void StartAlarmImpl() {
alarm_ =
new QueuedAlarm(fixture_->scheduler(),
fixture_->sequence(),
fixture_->timer()->NowUs(),
MakeFunction(this, &TestAlarmHandler::FireAlarm));
}
void CancelAlarmImpl() {
if (alarm_ != NULL) {
alarm_->CancelAlarm();
alarm_ = NULL;
}
// Note that we notify here, as this method will always run. In particular:
// 1) If we get here first, FireAlarm will not fire, so we're fine.
// 2) If we get here second, FireAlarm already ran, so we're fine to
// cleanup, too.
// (It makes sense to talk about us running before or after FireAlarm
// because it runs in the same sequence).
sync_->Notify();
}
QueuedAlarmTest* fixture_;
WorkerTestBase::SyncPoint* sync_;
QueuedAlarm* alarm_;
bool fired_;
DISALLOW_COPY_AND_ASSIGN(TestAlarmHandler);
};
TEST_F(QueuedAlarmTest, BasicOperation) {
const int kDelayUs = Timer::kMsUs;
MockScheduler* scheduler = SetupWithMockScheduler();
// Tests to make sure the alarm actually runs.
new QueuedAlarm(scheduler, sequence_,
timer_->NowUs() + kDelayUs,
MakeFunction(static_cast<QueuedAlarmTest*>(this),
&QueuedAlarmTest::MarkDone,
&QueuedAlarmTest::MarkCancel));
{
ScopedMutex lock(scheduler->mutex());
scheduler->ProcessAlarmsOrWaitUs(kDelayUs);
}
// Make sure to let the work threads complete.
scheduler->AwaitQuiescence();
EXPECT_TRUE(done_);
EXPECT_FALSE(cancel_);
}
TEST_F(QueuedAlarmTest, BasicCancel) {
const int kDelayUs = Timer::kMsUs;
MockScheduler* scheduler = SetupWithMockScheduler();
// Tests to make sure that we can cancel it.
QueuedAlarm* alarm =
new QueuedAlarm(scheduler, sequence_,
timer_->NowUs() + kDelayUs,
MakeFunction(static_cast<QueuedAlarmTest*>(this),
&QueuedAlarmTest::MarkDone,
&QueuedAlarmTest::MarkCancel));
alarm->CancelAlarm();
{
ScopedMutex lock(scheduler->mutex());
scheduler->ProcessAlarmsOrWaitUs(kDelayUs);
}
// Make sure to let the work threads complete.
scheduler->AwaitQuiescence();
EXPECT_FALSE(done_);
EXPECT_TRUE(cancel_);
}
TEST_F(QueuedAlarmTest, RacingCancel) {
const int kRuns = 1000;
int fired = 0;
// Test to make sure cases where cancel and alarm execution may be racing
// are handled safely, without crashes or check failures.
SetupWithRealScheduler();
SchedulerThread* scheduler_thread =
new SchedulerThread(thread_system_.get(), scheduler_.get());
scheduler_thread->Start();
for (int run = 0; run < kRuns; ++run) {
SyncPoint sync(thread_system_.get());
MakeSequence();
TestAlarmHandler t(this, &sync);
t.StartAlarm();
// Unfortunately w/o a sleep here, the race is consistently won by
// the cancellation.
usleep(1);
t.CancelAlarm();
sync.Wait();
ClearSequence();
if (t.fired()) {
++fired;
}
}
scheduler_thread->MakeDeleter()->CallRun();
LOG(ERROR) << "Alarm fired in: " << fired << "/" << kRuns;
}
} // namespace
} // namespace net_instaweb