Commit 172fdb46 authored by Makoto Shimazu's avatar Makoto Shimazu Committed by Commit Bot

S13nServiceWorker: Implement a timer for long standing events

This CL implements long standing event timer on the renderer. Events dispatched
to the service worker will be aborted in ServiceWorkerEventTimer::kEventTimeout.
ServiceWorkerEventTimer needs to track each event individually, so the event
timer issues an unique id to each event. For that reason, all of IDMaps used for
keeping the event callbacks are replaced to std::map.

Design doc: bit.ly/sw-timers-on-renderer

Bug: 774374
Change-Id: I77f8c9cd01e1e02ff856306c42de298eebf318ca
Reviewed-on: https://chromium-review.googlesource.com/760083Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Makoto Shimazu <shimazu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#519593}
parent 794bf2c3
......@@ -447,8 +447,6 @@ target(link_target_type, "renderer") {
"service_worker/service_worker_context_message_filter.h",
"service_worker/service_worker_dispatcher.cc",
"service_worker/service_worker_dispatcher.h",
"service_worker/service_worker_event_timer.cc",
"service_worker/service_worker_event_timer.h",
"service_worker/service_worker_fetch_context_impl.cc",
"service_worker/service_worker_fetch_context_impl.h",
"service_worker/service_worker_handle_reference.cc",
......@@ -461,6 +459,8 @@ target(link_target_type, "renderer") {
"service_worker/service_worker_provider_context.h",
"service_worker/service_worker_subresource_loader.cc",
"service_worker/service_worker_subresource_loader.h",
"service_worker/service_worker_timeout_timer.cc",
"service_worker/service_worker_timeout_timer.h",
"service_worker/service_worker_type_converters.cc",
"service_worker/service_worker_type_converters.h",
"service_worker/service_worker_type_util.cc",
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/service_worker/service_worker_event_timer.h"
#include "base/time/time.h"
namespace content {
// static
constexpr base::TimeDelta ServiceWorkerEventTimer::kIdleDelay;
ServiceWorkerEventTimer::ServiceWorkerEventTimer(
base::RepeatingClosure idle_callback)
: idle_callback_(std::move(idle_callback)) {
// |idle_callback_| will be invoked if no event happens in |kIdleDelay|.
idle_timer_.Start(FROM_HERE, kIdleDelay, idle_callback_);
}
ServiceWorkerEventTimer::~ServiceWorkerEventTimer() = default;
void ServiceWorkerEventTimer::StartEvent() {
idle_timer_.Stop();
++num_inflight_events_;
}
void ServiceWorkerEventTimer::EndEvent() {
DCHECK(!idle_timer_.IsRunning());
DCHECK_GT(num_inflight_events_, 0u);
--num_inflight_events_;
if (num_inflight_events_ == 0) {
idle_timer_.Start(FROM_HERE, kIdleDelay, idle_callback_);
}
}
} // namespace content
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_EVENT_TIMER_H_
#define CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_EVENT_TIMER_H_
#include "base/callback.h"
#include "base/timer/timer.h"
#include "content/common/content_export.h"
namespace content {
// Manages idle timeout of events.
// TODO(crbug.com/774374) Implement long running timer too.
class CONTENT_EXPORT ServiceWorkerEventTimer {
public:
// |idle_callback| will be called when a certain period has passed since the
// last event ended.
explicit ServiceWorkerEventTimer(base::RepeatingClosure idle_callback);
~ServiceWorkerEventTimer();
void StartEvent();
void EndEvent();
// Idle timeout duration since the last event has finished.
static constexpr base::TimeDelta kIdleDelay =
base::TimeDelta::FromSeconds(30);
private:
uint64_t num_inflight_events_ = 0;
// This is repeating timer since |idle_timer_| will fire repeatedly once it's
// considered as idle.
base::RepeatingTimer idle_timer_;
base::RepeatingClosure idle_callback_;
};
} // namespace content
#endif // CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_EVENT_TIMER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/service_worker/service_worker_event_timer.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/test/test_mock_time_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
class ServiceWorkerEventTimerTest : public testing::Test {
protected:
void SetUp() override {
task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::Time::Now(), base::TimeTicks::Now());
message_loop_.SetTaskRunner(task_runner_);
}
base::TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); }
private:
base::MessageLoop message_loop_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
};
TEST_F(ServiceWorkerEventTimerTest, IdleTimer) {
bool is_idle = false;
ServiceWorkerEventTimer timer(base::BindRepeating(
[](bool* out_is_idle) { *out_is_idle = true; }, &is_idle));
task_runner()->FastForwardBy(ServiceWorkerEventTimer::kIdleDelay +
base::TimeDelta::FromSeconds(1));
// |idle_callback| should be fired since there is no event.
EXPECT_TRUE(is_idle);
is_idle = false;
timer.StartEvent();
task_runner()->FastForwardBy(ServiceWorkerEventTimer::kIdleDelay +
base::TimeDelta::FromSeconds(1));
// Nothing happens since there is an inflight event.
EXPECT_FALSE(is_idle);
timer.StartEvent();
task_runner()->FastForwardBy(ServiceWorkerEventTimer::kIdleDelay +
base::TimeDelta::FromSeconds(1));
// Nothing happens since there are two inflight events.
EXPECT_FALSE(is_idle);
timer.EndEvent();
task_runner()->FastForwardBy(ServiceWorkerEventTimer::kIdleDelay +
base::TimeDelta::FromSeconds(1));
// Nothing happens since there is an inflight event.
EXPECT_FALSE(is_idle);
timer.EndEvent();
task_runner()->FastForwardBy(ServiceWorkerEventTimer::kIdleDelay +
base::TimeDelta::FromSeconds(1));
// |idle_callback| should be fired.
EXPECT_TRUE(is_idle);
}
} // namespace content
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/service_worker/service_worker_timeout_timer.h"
#include "base/stl_util.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "content/common/service_worker/service_worker_utils.h"
namespace content {
namespace {
int NextEventId() {
// Event id should not start from zero since HashMap in Blink requires
// non-zero keys.
static int s_next_event_id = 1;
CHECK_LT(s_next_event_id, std::numeric_limits<int>::max());
return s_next_event_id++;
}
} // namespace
// static
constexpr base::TimeDelta ServiceWorkerTimeoutTimer::kIdleDelay;
constexpr base::TimeDelta ServiceWorkerTimeoutTimer::kEventTimeout;
constexpr base::TimeDelta ServiceWorkerTimeoutTimer::kUpdateInterval;
ServiceWorkerTimeoutTimer::ServiceWorkerTimeoutTimer(
base::RepeatingClosure idle_callback)
: ServiceWorkerTimeoutTimer(std::move(idle_callback),
std::make_unique<base::DefaultTickClock>()) {}
ServiceWorkerTimeoutTimer::ServiceWorkerTimeoutTimer(
base::RepeatingClosure idle_callback,
std::unique_ptr<base::TickClock> tick_clock)
: idle_callback_(std::move(idle_callback)),
tick_clock_(std::move(tick_clock)) {
// |idle_callback_| will be invoked if no event happens in |kIdleDelay|.
idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
timer_.Start(FROM_HERE, kUpdateInterval,
base::BindRepeating(&ServiceWorkerTimeoutTimer::UpdateStatus,
base::Unretained(this)));
}
ServiceWorkerTimeoutTimer::~ServiceWorkerTimeoutTimer() {
// Abort all callbacks.
for (auto& it : abort_callbacks_)
std::move(it.second).Run();
};
int ServiceWorkerTimeoutTimer::StartEvent(
base::OnceCallback<void(int /* event_id */)> abort_callback) {
idle_time_ = base::TimeTicks();
const int event_id = NextEventId();
DCHECK(!base::ContainsKey(abort_callbacks_, event_id));
abort_callbacks_[event_id] =
base::BindOnce(std::move(abort_callback), event_id);
event_timeout_times_.emplace(tick_clock_->NowTicks() + kEventTimeout,
event_id);
return event_id;
}
void ServiceWorkerTimeoutTimer::EndEvent(int event_id) {
DCHECK(base::ContainsKey(abort_callbacks_, event_id));
abort_callbacks_.erase(event_id);
if (abort_callbacks_.empty())
idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
}
void ServiceWorkerTimeoutTimer::UpdateStatus() {
if (!ServiceWorkerUtils::IsServicificationEnabled())
return;
base::TimeTicks now = tick_clock_->NowTicks();
// Abort all events exceeding |kEventTimeout|.
while (!event_timeout_times_.empty()) {
const auto& p = event_timeout_times_.front();
if (p.first > now)
break;
int event_id = p.second;
event_timeout_times_.pop();
// If |event_id| is not found in |abort_callbacks_|, it means the event has
// successfully finished.
auto iter = abort_callbacks_.find(event_id);
if (iter == abort_callbacks_.end())
continue;
base::OnceClosure callback = std::move(iter->second);
abort_callbacks_.erase(iter);
std::move(callback).Run();
}
// If |abort_callbacks_| is empty, the worker is now idle.
if (abort_callbacks_.empty() && idle_time_.is_null())
idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
if (!idle_time_.is_null() && idle_time_ < now)
idle_callback_.Run();
}
} // namespace content
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_TIMEOUT_TIMER_H_
#define CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_TIMEOUT_TIMER_H_
#include <map>
#include "base/callback.h"
#include "base/containers/queue.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "content/common/content_export.h"
namespace base {
class TickClock;
} // namespace base
namespace content {
// ServiceWorkerTimeoutTimer manages two types of timeouts: the long standing
// event timeout and the idle timeout.
//
// S13nServiceWorker:
// 1) Event timeout: when an event starts, StartEvent() records the expiration
// time of the event (kEventTimeout). If EndEvent() has not been called within
// the timeout time, |abort_callback| passed to StartEvent() is called.
// 2) Idle timeout: when a certain time has passed (kIdleDelay) since all of
// events have ended, ServiceWorkerTimeoutTimer calls the |idle_callback|.
// |idle_callback| will be continuously called at a certain interval
// (kUpdateInterval) until the next event starts.
//
// The lifetime of ServiceWorkerTimeoutTimer is the same with the worker
// thread. If ServiceWorkerTimeoutTimer is destructed while there are inflight
// events, all |abort_callback|s will be immediately called.
//
// Non-S13nServiceWorker:
// Does nothing except calls the abort callbacks upon destruction.
class CONTENT_EXPORT ServiceWorkerTimeoutTimer {
public:
explicit ServiceWorkerTimeoutTimer(base::RepeatingClosure idle_callback);
// For testing.
ServiceWorkerTimeoutTimer(base::RepeatingClosure idle_callback,
std::unique_ptr<base::TickClock> tick_clock);
~ServiceWorkerTimeoutTimer();
// StartEvent() should be called at the beginning of an event. It returns an
// event id. The event id should be passed to EndEvent() when the event has
// finished.
// See the class comment to know when |abort_callback| runs.
int StartEvent(base::OnceCallback<void(int /* event_id */)> abort_callback);
void EndEvent(int event_id);
// Idle timeout duration since the last event has finished.
static constexpr base::TimeDelta kIdleDelay =
base::TimeDelta::FromSeconds(30);
// Duration of the long standing event timeout since StartEvent() has been
// called.
static constexpr base::TimeDelta kEventTimeout =
base::TimeDelta::FromMinutes(5);
// ServiceWorkerTimeoutTimer periodically updates the timeout state by
// kUpdateInterval.
static constexpr base::TimeDelta kUpdateInterval =
base::TimeDelta::FromSeconds(30);
private:
// Updates the internal states and fires timeout callbacks if any.
void UpdateStatus();
// For event timeouts. Contains only inflight events.
std::map<int /* event_id */, base::OnceClosure> abort_callbacks_;
// For long standing event timeouts. Contains both inflight and settled
// events. If an |event_id| does not exist in |abort_callbacks_|, the event
// already finished successfully.
base::queue<std::pair<base::TimeTicks, int /* event_id */>>
event_timeout_times_;
// For idle timeouts. The time the service worker started being considered
// idle. This time is null if there are any inflight events.
base::TimeTicks idle_time_;
// For idle timeouts. Invoked when UpdateStatus() is called after
// |idle_time_|.
base::RepeatingClosure idle_callback_;
// |timer_| invokes UpdateEventStatus() periodically.
base::RepeatingTimer timer_;
std::unique_ptr<base::TickClock> tick_clock_;
};
} // namespace content
#endif // CONTENT_RENDERER_SERVICE_WORKER_SERVICE_WORKER_TIMEOUT_TIMER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/service_worker/service_worker_timeout_timer.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/tick_clock.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/content_features.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class MockEvent {
public:
base::OnceCallback<void(int)> CreateAbortCallback() {
EXPECT_FALSE(has_aborted_);
return base::BindOnce(&MockEvent::Abort, base::Unretained(this));
}
int event_id() const { return event_id_; }
void set_event_id(int event_id) { event_id_ = event_id; }
bool has_aborted() const { return has_aborted_; }
private:
void Abort(int event_id) {
EXPECT_EQ(event_id_, event_id);
has_aborted_ = true;
}
bool has_aborted_ = false;
int event_id_ = 0;
};
} // namespace
class ServiceWorkerTimeoutTimerTest : public testing::Test {
protected:
void SetUp() override {
task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::Time::Now(), base::TimeTicks::Now());
message_loop_.SetTaskRunner(task_runner_);
}
void EnableServicification() {
feature_list_.InitWithFeatures(
{features::kBrowserSideNavigation, features::kNetworkService}, {});
ASSERT_TRUE(ServiceWorkerUtils::IsServicificationEnabled());
}
base::TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); }
private:
base::MessageLoop message_loop_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::test::ScopedFeatureList feature_list_;
};
TEST_F(ServiceWorkerTimeoutTimerTest, IdleTimer) {
EnableServicification();
const base::TimeDelta kIdleInterval =
ServiceWorkerTimeoutTimer::kIdleDelay +
ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1);
base::RepeatingCallback<void(int)> do_nothing_callback =
base::BindRepeating([](int) {});
bool is_idle = false;
ServiceWorkerTimeoutTimer timer(
base::BindRepeating([](bool* out_is_idle) { *out_is_idle = true; },
&is_idle),
task_runner()->GetMockTickClock());
task_runner()->FastForwardBy(kIdleInterval);
// |idle_callback| should be fired since there is no event.
EXPECT_TRUE(is_idle);
is_idle = false;
int event_id_1 = timer.StartEvent(do_nothing_callback);
task_runner()->FastForwardBy(kIdleInterval);
// Nothing happens since there is an inflight event.
EXPECT_FALSE(is_idle);
int event_id_2 = timer.StartEvent(do_nothing_callback);
task_runner()->FastForwardBy(kIdleInterval);
// Nothing happens since there are two inflight events.
EXPECT_FALSE(is_idle);
timer.EndEvent(event_id_2);
task_runner()->FastForwardBy(kIdleInterval);
// Nothing happens since there is an inflight event.
EXPECT_FALSE(is_idle);
timer.EndEvent(event_id_1);
task_runner()->FastForwardBy(kIdleInterval);
// |idle_callback| should be fired.
EXPECT_TRUE(is_idle);
}
TEST_F(ServiceWorkerTimeoutTimerTest, EventTimer) {
EnableServicification();
ServiceWorkerTimeoutTimer timer(base::BindRepeating(&base::DoNothing),
task_runner()->GetMockTickClock());
MockEvent event1, event2;
int event_id1 = timer.StartEvent(event1.CreateAbortCallback());
int event_id2 = timer.StartEvent(event2.CreateAbortCallback());
event1.set_event_id(event_id1);
event2.set_event_id(event_id2);
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
EXPECT_FALSE(event1.has_aborted());
EXPECT_FALSE(event2.has_aborted());
timer.EndEvent(event1.event_id());
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kEventTimeout +
base::TimeDelta::FromSeconds(1));
EXPECT_FALSE(event1.has_aborted());
EXPECT_TRUE(event2.has_aborted());
}
TEST_F(ServiceWorkerTimeoutTimerTest, BecomeIdleAfterAbort) {
EnableServicification();
bool is_idle = false;
ServiceWorkerTimeoutTimer timer(
base::BindRepeating([](bool* out_is_idle) { *out_is_idle = true; },
&is_idle),
task_runner()->GetMockTickClock());
MockEvent event;
int event_id = timer.StartEvent(event.CreateAbortCallback());
event.set_event_id(event_id);
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kEventTimeout +
ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
EXPECT_TRUE(event.has_aborted());
EXPECT_FALSE(is_idle);
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kIdleDelay +
base::TimeDelta::FromSeconds(1));
EXPECT_TRUE(is_idle);
}
TEST_F(ServiceWorkerTimeoutTimerTest, AbortAllOnDestruction) {
EnableServicification();
MockEvent event1, event2;
{
ServiceWorkerTimeoutTimer timer(base::BindRepeating(&base::DoNothing),
task_runner()->GetMockTickClock());
int event_id1 = timer.StartEvent(event1.CreateAbortCallback());
int event_id2 = timer.StartEvent(event2.CreateAbortCallback());
event1.set_event_id(event_id1);
event2.set_event_id(event_id2);
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
EXPECT_FALSE(event1.has_aborted());
EXPECT_FALSE(event2.has_aborted());
}
EXPECT_TRUE(event1.has_aborted());
EXPECT_TRUE(event2.has_aborted());
}
TEST_F(ServiceWorkerTimeoutTimerTest, NonS13nServiceWorker) {
ASSERT_FALSE(ServiceWorkerUtils::IsServicificationEnabled());
MockEvent event;
{
bool is_idle = false;
ServiceWorkerTimeoutTimer timer(
base::BindRepeating([](bool* out_is_idle) { *out_is_idle = true; },
&is_idle),
task_runner()->GetMockTickClock());
int event_id = timer.StartEvent(event.CreateAbortCallback());
event.set_event_id(event_id);
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kEventTimeout +
ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
// Timed out events should *NOT* be aborted in non-S13nServiceWorker.
EXPECT_FALSE(event.has_aborted());
EXPECT_FALSE(is_idle);
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kIdleDelay +
ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
// |idle_callback| should *NOT* be fired in non-S13nServiceWorker.
EXPECT_FALSE(is_idle);
}
// Events should be aborted when the timer is destructed.
EXPECT_TRUE(event.has_aborted());
}
} // namespace content
......@@ -1567,9 +1567,9 @@ test("content_unittests") {
"../renderer/render_widget_unittest.cc",
"../renderer/scheduler/resource_dispatch_throttler_unittest.cc",
"../renderer/service_worker/service_worker_dispatcher_unittest.cc",
"../renderer/service_worker/service_worker_event_timer_unittest.cc",
"../renderer/service_worker/service_worker_provider_context_unittest.cc",
"../renderer/service_worker/service_worker_subresource_loader_unittest.cc",
"../renderer/service_worker/service_worker_timeout_timer_unittest.cc",
"../renderer/service_worker/thread_safe_script_container_unittest.cc",
"../renderer/service_worker/web_service_worker_installed_scripts_manager_impl_unittest.cc",
"../renderer/skia_benchmarking_extension_unittest.cc",
......
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