Commit b642922e authored by bartfab@chromium.org's avatar bartfab@chromium.org

Convert SessionLengthLimiter to OneShotTimer

This CL converts the SessionLengthLimiter from a RepeatingTimer that
fires once per second to a OneShotTimer that fires when the session
time is up and the user should be logged out.

BUG=175797
TEST=Manual, unittests


Review URL: https://chromiumcodereview.appspot.com/12218123

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@182214 0039d316-1c4b-4281-b951-d872f2087c98
parent 20ddac71
......@@ -27,10 +27,6 @@ const int kSessionLengthLimitMinMs = 30 * 1000; // 30 seconds.
// The maximum session time limit that can be set.
const int kSessionLengthLimitMaxMs = 24 * 60 * 60 * 1000; // 24 hours.
// The interval at which to fire periodic callbacks and check whether the
// session time limit has been reached.
const int kSessionLengthLimitTimerIntervalMs = 1000;
// A default delegate implementation that returns the current time and does end
// the current user's session when requested. This can be replaced with a mock
// in tests.
......@@ -98,6 +94,8 @@ SessionLengthLimiter::SessionLengthLimiter(Delegate* delegate,
prefs::kSessionLengthLimit,
base::Bind(&SessionLengthLimiter::OnSessionLengthLimitChanged,
base::Unretained(this)));
// Handle the current session length limit, if any.
OnSessionLengthLimitChanged();
}
......@@ -106,61 +104,43 @@ SessionLengthLimiter::~SessionLengthLimiter() {
void SessionLengthLimiter::OnSessionLengthLimitChanged() {
DCHECK(thread_checker_.CalledOnValidThread());
// Stop any currently running timer.
if (timer_)
timer_->Stop();
int limit;
const PrefServiceBase::Preference* session_length_limit_pref =
pref_change_registrar_.prefs()->
FindPreference(prefs::kSessionLengthLimit);
// If no session length limit is set, stop the timer.
if (session_length_limit_pref->IsDefaultValue() ||
!session_length_limit_pref->GetValue()->GetAsInteger(&limit)) {
session_length_limit_ = base::TimeDelta();
StopTimer();
// If no session length limit is set, destroy the timer.
timer_.reset();
return;
}
// If a session length limit is set, clamp it to the valid range and start
// the timer.
session_length_limit_ = base::TimeDelta::FromMilliseconds(
std::min(std::max(limit, kSessionLengthLimitMinMs),
kSessionLengthLimitMaxMs));
StartTimer();
}
void SessionLengthLimiter::StartTimer() {
if (repeating_timer_ && repeating_timer_->IsRunning())
return;
if (!repeating_timer_)
repeating_timer_.reset(new base::RepeatingTimer<SessionLengthLimiter>);
repeating_timer_->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kSessionLengthLimitTimerIntervalMs),
this,
&SessionLengthLimiter::UpdateRemainingTime);
}
void SessionLengthLimiter::StopTimer() {
if (!repeating_timer_)
return;
repeating_timer_.reset();
UpdateRemainingTime();
}
void SessionLengthLimiter::UpdateRemainingTime() {
const base::TimeDelta kZeroTimeDelta = base::TimeDelta();
// If no session length limit is set, return.
if (session_length_limit_ == kZeroTimeDelta)
return;
// Clamp the session length limit to the valid range.
const base::TimeDelta session_length_limit =
base::TimeDelta::FromMilliseconds(std::min(std::max(
limit, kSessionLengthLimitMinMs), kSessionLengthLimitMaxMs));
// Calculate the remaining session time, clamping so that it never falls below
// zero.
base::TimeDelta remaining = session_length_limit_ -
// Calculate the remaining session time.
const base::TimeDelta remaining = session_length_limit -
(delegate_->GetCurrentTime() - session_start_time_);
if (remaining < kZeroTimeDelta)
remaining = kZeroTimeDelta;
// End the session if the remaining time reaches zero.
if (remaining == base::TimeDelta())
// Log out the user immediately if the session length limit has been reached
// or exceeded.
if (remaining <= base::TimeDelta()) {
delegate_->StopSession();
return;
}
// Set a timer to log out the user when the session length limit is reached.
if (!timer_)
timer_.reset(new base::OneShotTimer<SessionLengthLimiter::Delegate>);
timer_->Start(FROM_HERE, remaining, delegate_.get(),
&SessionLengthLimiter::Delegate::StopSession);
}
} // namespace chromeos
......@@ -38,25 +38,13 @@ class SessionLengthLimiter {
private:
void OnSessionLengthLimitChanged();
// Starts a timer that periodically checks whether the remaining time has
// reached zero.
void StartTimer();
// Stops the timer.
void StopTimer();
// Updates the remaining time, and terminates the session when the time
// reaches zero.
void UpdateRemainingTime();
base::ThreadChecker thread_checker_;
scoped_ptr<Delegate> delegate_;
PrefChangeRegistrar pref_change_registrar_;
scoped_ptr<base::RepeatingTimer<SessionLengthLimiter> > repeating_timer_;
scoped_ptr<base::OneShotTimer<SessionLengthLimiter::Delegate> > timer_;
base::Time session_start_time_;
base::TimeDelta session_length_limit_;
DISALLOW_COPY_AND_ASSIGN(SessionLengthLimiter);
};
......
......@@ -4,8 +4,9 @@
#include "chrome/browser/chromeos/power/session_length_limiter.h"
#include <deque>
#include <queue>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/compiler_specific.h"
......@@ -16,16 +17,13 @@
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "base/time.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NiceMock;
......@@ -34,159 +32,197 @@ namespace chromeos {
namespace {
// The interval at which the SessionLengthLimiter checks whether the remaining
// session time has reachzed zero.
const base::TimeDelta kSessionLengthLimitTimerInterval(
base::TimeDelta::FromSeconds(1));
const base::TimeDelta kZeroTimeDelta;
const base::TimeDelta kTenSeconds(base::TimeDelta::FromSeconds(10));
class MockSessionLengthLimiterDelegate : public SessionLengthLimiter::Delegate {
public:
MOCK_CONST_METHOD0(GetCurrentTime, const base::Time(void));
MOCK_METHOD0(StopSession, void(void));
};
// A SingleThreadTaskRunner that allows the task queue to be inspected and
// delayed tasks to be run without waiting for the actual delays to expire.
class ImmediateSingleThreadTaskRunner : public base::SingleThreadTaskRunner {
// A SingleThreadTaskRunner that mocks the current time and allows it to be
// fast-forwarded.
class MockTimeSingleThreadTaskRunner : public base::SingleThreadTaskRunner {
public:
virtual bool RunsTasksOnCurrentThread() const OVERRIDE {
return true;
}
MockTimeSingleThreadTaskRunner();
// base::SingleThreadTaskRunner:
virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) OVERRIDE {
tasks_.push_back(std::pair<base::TimeDelta, base::Closure>(delay, task));
return true;
}
base::TimeDelta delay) OVERRIDE;
virtual bool PostNonNestableDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) OVERRIDE {
NOTREACHED();
return false;
}
base::TimeDelta delay) OVERRIDE;
void RunTasks() {
std::deque<std::pair<base::TimeDelta, base::Closure> > tasks;
tasks.swap(tasks_);
for (std::deque<std::pair<base::TimeDelta, base::Closure> >::iterator
it = tasks.begin(); it != tasks.end(); ++it) {
it->second.Run();
}
}
const base::Time& GetCurrentTime() const;
const std::deque<std::pair<base::TimeDelta, base::Closure> >& tasks() const {
return tasks_;
}
void FastForwardBy(int64 milliseconds);
void FastForwardUntilNoTasksRemain();
private:
std::deque<std::pair<base::TimeDelta, base::Closure> > tasks_;
// Strict weak temporal ordering of tasks.
class TemporalOrder {
public:
bool operator()(
const std::pair<base::Time, base::Closure>& first_task,
const std::pair<base::Time, base::Closure>& second_task) const;
};
virtual ~ImmediateSingleThreadTaskRunner() {}
virtual ~MockTimeSingleThreadTaskRunner();
base::Time now_;
std::priority_queue<std::pair<base::Time, base::Closure>,
std::vector<std::pair<base::Time, base::Closure> >,
TemporalOrder> tasks_;
};
} // namespace
class SessionLengthLimiterTest : public testing::Test {
protected:
SessionLengthLimiterTest() : delegate_(NULL) {
SessionLengthLimiterTest();
// testing::Test:
virtual void SetUp() OVERRIDE;
virtual void TearDown() OVERRIDE;
void SetSessionStartTimePref(int64 session_start_time);
void VerifySessionStartTimePref();
void SetSessionLengthLimitPref(int64 session_length_limit);
void ExpectStopSession();
void CheckStopSessionTime();
void CreateSessionLengthLimiter(bool browser_restarted);
TestingPrefServiceSimple local_state_;
scoped_refptr<MockTimeSingleThreadTaskRunner> runner_;
base::Time session_start_time_;
base::Time session_end_time_;
MockSessionLengthLimiterDelegate* delegate_; // Owned by
// session_length_limiter_.
scoped_ptr<SessionLengthLimiter> session_length_limiter_;
};
MockTimeSingleThreadTaskRunner::MockTimeSingleThreadTaskRunner()
// Initialize the mock clock to a fixed value, ensuring that timezone
// differences or DST changes do not affect the test.
: now_(base::Time::UnixEpoch() + base::TimeDelta::FromDays(40 * 365)) {
}
bool MockTimeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const {
return true;
}
bool MockTimeSingleThreadTaskRunner::PostDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) {
tasks_.push(std::pair<base::Time, base::Closure>(now_ + delay, task));
return true;
}
bool MockTimeSingleThreadTaskRunner::PostNonNestableDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) {
NOTREACHED();
return false;
}
const base::Time& MockTimeSingleThreadTaskRunner::GetCurrentTime() const {
return now_;
}
void MockTimeSingleThreadTaskRunner::FastForwardBy(int64 delta) {
const base::Time latest = now_ + base::TimeDelta::FromMilliseconds(delta);
while (!tasks_.empty() && tasks_.top().first <= latest) {
now_ = tasks_.top().first;
tasks_.top().second.Run();
tasks_.pop();
}
now_ = latest;
}
void MockTimeSingleThreadTaskRunner::FastForwardUntilNoTasksRemain() {
while (!tasks_.empty()) {
now_ = tasks_.top().first;
tasks_.top().second.Run();
tasks_.pop();
}
}
virtual void SetUp() {
bool MockTimeSingleThreadTaskRunner::TemporalOrder::operator()(
const std::pair<base::Time, base::Closure>& first_task,
const std::pair<base::Time, base::Closure>& second_task) const {
return first_task.first < second_task.first;
}
MockTimeSingleThreadTaskRunner::~MockTimeSingleThreadTaskRunner() {
}
SessionLengthLimiterTest::SessionLengthLimiterTest() : delegate_(NULL) {
}
void SessionLengthLimiterTest::SetUp() {
TestingBrowserProcess::GetGlobal()->SetLocalState(&local_state_);
SessionLengthLimiter::RegisterPrefs(local_state_.registry());
runner_ = new MockTimeSingleThreadTaskRunner;
session_start_time_ = runner_->GetCurrentTime();
delegate_ = new NiceMock<MockSessionLengthLimiterDelegate>;
ON_CALL(*delegate_, GetCurrentTime())
.WillByDefault(Invoke(this, &SessionLengthLimiterTest::GetCurrentTime));
.WillByDefault(Invoke(runner_.get(),
&MockTimeSingleThreadTaskRunner::GetCurrentTime));
EXPECT_CALL(*delegate_, StopSession()).Times(0);
runner_ = new ImmediateSingleThreadTaskRunner;
// Initialize the mock clock to a fixed value, ensuring that timezone
// differences or DST changes do not affect the test.
now_ = base::Time::UnixEpoch() + base::TimeDelta::FromDays(40 * 365);
session_start_time_ = now_;
}
}
virtual void TearDown() {
void SessionLengthLimiterTest::TearDown() {
TestingBrowserProcess::GetGlobal()->SetLocalState(NULL);
}
}
void SetSessionStartTimePref(int64 session_start_time) {
void SessionLengthLimiterTest::SetSessionStartTimePref(
int64 session_start_time) {
local_state_.SetUserPref(prefs::kSessionStartTime,
base::Value::CreateStringValue(
base::Int64ToString(session_start_time)));
}
}
void VerifySessionStartTimePref() {
void SessionLengthLimiterTest::VerifySessionStartTimePref() {
base::Time session_start_time(base::Time::FromInternalValue(
local_state_.GetInt64(prefs::kSessionStartTime)));
EXPECT_EQ(session_start_time_, session_start_time);
}
}
void SetSessionLengthLimitPref(int64 session_length_limit) {
void SessionLengthLimiterTest::SetSessionLengthLimitPref(
int64 session_length_limit) {
session_end_time_ = session_start_time_ +
base::TimeDelta::FromMilliseconds(session_length_limit);
// If the new session end time has passed already, the session should end now.
if (session_end_time_ < runner_->GetCurrentTime())
session_end_time_ = runner_->GetCurrentTime();
local_state_.SetUserPref(prefs::kSessionLengthLimit,
base::Value::CreateIntegerValue(
session_length_limit));
base::TimeDelta remaining(
base::TimeDelta::FromMilliseconds(session_length_limit) -
(now_ - session_start_time_));
if (remaining < kZeroTimeDelta)
remaining = kZeroTimeDelta;
remaining_.reset(new base::TimeDelta(remaining));
}
}
void ExpectStopSession() {
void SessionLengthLimiterTest::ExpectStopSession() {
Mock::VerifyAndClearExpectations(delegate_);
EXPECT_CALL(*delegate_, StopSession()).Times(1);
}
EXPECT_CALL(*delegate_, StopSession())
.Times(1)
.WillOnce(Invoke(this, &SessionLengthLimiterTest::CheckStopSessionTime));
}
void CreateSessionLengthLimiter(bool browser_restarted) {
void SessionLengthLimiterTest::CheckStopSessionTime() {
EXPECT_EQ(session_end_time_, runner_->GetCurrentTime());
}
void SessionLengthLimiterTest::CreateSessionLengthLimiter(
bool browser_restarted) {
session_length_limiter_.reset(
new SessionLengthLimiter(delegate_, browser_restarted));
}
void VerifyNoTimerTickIsEnqueued() {
EXPECT_TRUE(runner_->tasks().empty());
}
void VerifyTimerTickIsEnqueued() {
ASSERT_EQ(1U, runner_->tasks().size());
EXPECT_EQ(kSessionLengthLimitTimerInterval,
runner_->tasks().front().first);
}
void VerifyTimerTick() {
VerifyTimerTickIsEnqueued();
runner_->RunTasks();
now_ += kSessionLengthLimitTimerInterval;
*remaining_ -= kSessionLengthLimitTimerInterval;
if (*remaining_ < kZeroTimeDelta)
remaining_.reset(new base::TimeDelta(kZeroTimeDelta));
}
base::Time GetCurrentTime() const {
return now_;
}
TestingPrefServiceSimple local_state_;
MockSessionLengthLimiterDelegate* delegate_; // Owned by
// session_length_limiter_.
scoped_refptr<ImmediateSingleThreadTaskRunner> runner_;
base::Time session_start_time_;
base::Time now_;
scoped_ptr<base::TimeDelta> remaining_;
scoped_ptr<SessionLengthLimiter> session_length_limiter_;
};
}
// Verifies that the session start time in local state is updated during login
// if no session start time has been stored before.
TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeUnset) {
......@@ -206,7 +242,7 @@ TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeInvalid) {
// if a session start time lying in the future has been stored before.
TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeFuture) {
SetSessionStartTimePref(
(now_ + base::TimeDelta::FromHours(2)).ToInternalValue());
(session_start_time_ + base::TimeDelta::FromHours(2)).ToInternalValue());
CreateSessionLengthLimiter(false);
VerifySessionStartTimePref();
}
......@@ -214,8 +250,8 @@ TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeFuture) {
// Verifies that the session start time in local state is updated during login
// if a valid session start time has been stored before.
TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeValid) {
const base::Time previous_start_time = now_ - base::TimeDelta::FromHours(2);
SetSessionStartTimePref(previous_start_time.ToInternalValue());
SetSessionStartTimePref(
(session_start_time_ - base::TimeDelta::FromHours(2)).ToInternalValue());
CreateSessionLengthLimiter(false);
VerifySessionStartTimePref();
}
......@@ -240,7 +276,7 @@ TEST_F(SessionLengthLimiterTest, RestartWithSessionStartTimeInvalid) {
// before.
TEST_F(SessionLengthLimiterTest, RestartWithSessionStartTimeFuture) {
SetSessionStartTimePref(
(now_ + base::TimeDelta::FromHours(2)).ToInternalValue());
(session_start_time_ + base::TimeDelta::FromHours(2)).ToInternalValue());
CreateSessionLengthLimiter(true);
VerifySessionStartTimePref();
}
......@@ -262,8 +298,8 @@ TEST_F(SessionLengthLimiterTest, RunWithoutSessionLengthLimit) {
// Create a SessionLengthLimiter.
CreateSessionLengthLimiter(false);
// Verify that no timer tick has been enqueued.
VerifyNoTimerTickIsEnqueued();
// Verify that no timer fires to terminate the session.
runner_->FastForwardUntilNoTasksRemain();
}
// Creates a SessionLengthLimiter after setting a limit. Verifies that the
......@@ -278,13 +314,10 @@ TEST_F(SessionLengthLimiterTest, RunWithSessionLengthLimit) {
// Create a SessionLengthLimiter.
CreateSessionLengthLimiter(false);
// Check timer ticks until the remaining session time reaches zero.
while (*remaining_ > kZeroTimeDelta)
VerifyTimerTick();
// Check that the next timer tick leads to the session being terminated.
// Verify that the timer fires and the session is terminated when the session
// length limit is reached.
ExpectStopSession();
VerifyTimerTick();
runner_->FastForwardUntilNoTasksRemain();
}
// Creates a SessionLengthLimiter after setting a 60 second limit, allows 50
......@@ -300,20 +333,17 @@ TEST_F(SessionLengthLimiterTest, RunAndIncreaseSessionLengthLimit) {
// Create a SessionLengthLimiter.
CreateSessionLengthLimiter(false);
// Check timer ticks for 50 seconds of session time.
while (*remaining_ > kTenSeconds)
VerifyTimerTick();
// Fast forward the time by 50 seconds, verifying that no timer fires to
// terminate the session.
runner_->FastForwardBy(50 * 1000); // 50 seconds.
// Increase the session length limit to 90 seconds.
SetSessionLengthLimitPref(90 * 1000); // 90 seconds.
// Check timer ticks until the remaining session time reaches zero.
while (*remaining_ > kZeroTimeDelta)
VerifyTimerTick();
// Check that the next timer tick leads to the session being terminated.
// Verify that the the timer fires and the session is terminated when the
// session length limit is reached.
ExpectStopSession();
VerifyTimerTick();
runner_->FastForwardUntilNoTasksRemain();
}
// Creates a SessionLengthLimiter after setting a 60 second limit, allows 50
......@@ -330,17 +360,14 @@ TEST_F(SessionLengthLimiterTest, RunAndDecreaseSessionLengthLimit) {
// Create a SessionLengthLimiter.
CreateSessionLengthLimiter(false);
// Check timer ticks for 50 seconds of session time.
while (*remaining_ > kTenSeconds)
VerifyTimerTick();
// Fast forward the time by 50 seconds, verifying that no timer fires to
// terminate the session.
runner_->FastForwardBy(50 * 1000); // 50 seconds.
// Reduce the session length limit below the 50 seconds that have already
// elapsed.
SetSessionLengthLimitPref(40 * 1000); // 40 seconds.
// Check that the next timer tick causes the session to be terminated.
// Verify that reducing the session length limit below the 50 seconds that
// have already elapsed causes the session to be terminated immediately.
ExpectStopSession();
VerifyTimerTick();
SetSessionLengthLimitPref(40 * 1000); // 40 seconds.
}
// Creates a SessionLengthLimiter after setting a 60 second limit, allows 50
......@@ -356,28 +383,15 @@ TEST_F(SessionLengthLimiterTest, RunAndRemoveSessionLengthLimit) {
// Create a SessionLengthLimiter.
CreateSessionLengthLimiter(false);
// Check timer ticks for 50 seconds of session time.
while (*remaining_ > kTenSeconds)
VerifyTimerTick();
// Fast forward the time by 50 seconds, verifying that no timer fires to
// terminate the session.
runner_->FastForwardBy(50 * 1000); // 50 seconds.
// Remove the session length limit.
local_state_.RemoveUserPref(prefs::kSessionLengthLimit);
// Continue advancing the session time until it reaches the original 60 second
// limit.
while (*remaining_ > kZeroTimeDelta) {
runner_->RunTasks();
now_ += kSessionLengthLimitTimerInterval;
*remaining_ -= kSessionLengthLimitTimerInterval;
if (*remaining_ < kZeroTimeDelta)
remaining_.reset(new base::TimeDelta(kZeroTimeDelta));
}
// Check that the next timer tick does not lead to the session being
// terminated.
now_ += kSessionLengthLimitTimerInterval;
runner_->RunTasks();
// Verify that no timer fires to terminate the session.
runner_->FastForwardUntilNoTasksRemain();
}
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment