blob: eb640fea50b719dc148c9d6a7e8e7eada59e1239 [file] [log] [blame]
// 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/atomic_sequence_num.h"
#include "base/stl_util.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/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 base::AtomicSequenceNumber s_event_id_sequence;
int next_event_id = s_event_id_sequence.GetNext() + 1;
CHECK_LT(next_event_id, std::numeric_limits<int>::max());
return next_event_id;
}
} // namespace
// static
constexpr base::TimeDelta ServiceWorkerTimeoutTimer::kIdleDelay;
constexpr base::TimeDelta ServiceWorkerTimeoutTimer::kEventTimeout;
constexpr base::TimeDelta ServiceWorkerTimeoutTimer::kUpdateInterval;
ServiceWorkerTimeoutTimer::StayAwakeToken::StayAwakeToken(
base::WeakPtr<ServiceWorkerTimeoutTimer> timer)
: timer_(std::move(timer)) {
DCHECK(timer_);
DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
timer_->num_of_stay_awake_tokens_++;
}
ServiceWorkerTimeoutTimer::StayAwakeToken::~StayAwakeToken() {
// If |timer_| has already been destroyed, it means the worker thread has
// already been killed.
if (!timer_)
return;
DCHECK_GT(timer_->num_of_stay_awake_tokens_, 0);
timer_->num_of_stay_awake_tokens_--;
if (!timer_->HasInflightEvent())
timer_->OnNoInflightEvent();
}
ServiceWorkerTimeoutTimer::ServiceWorkerTimeoutTimer(
base::RepeatingClosure idle_callback)
: ServiceWorkerTimeoutTimer(std::move(idle_callback),
base::DefaultTickClock::GetInstance()) {}
ServiceWorkerTimeoutTimer::ServiceWorkerTimeoutTimer(
base::RepeatingClosure idle_callback,
const base::TickClock* tick_clock)
: idle_callback_(std::move(idle_callback)),
tick_clock_(tick_clock),
weak_factory_(this) {
// |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& event : inflight_events_)
std::move(event.abort_callback).Run();
};
int ServiceWorkerTimeoutTimer::StartEvent(
base::OnceCallback<void(int /* event_id */)> abort_callback) {
return StartEventWithCustomTimeout(std::move(abort_callback), kEventTimeout);
}
int ServiceWorkerTimeoutTimer::StartEventWithCustomTimeout(
base::OnceCallback<void(int /* event_id */)> abort_callback,
base::TimeDelta timeout) {
if (did_idle_timeout()) {
DCHECK(!running_pending_tasks_);
idle_time_ = base::TimeTicks();
did_idle_timeout_ = false;
running_pending_tasks_ = true;
while (!pending_tasks_.empty()) {
std::move(pending_tasks_.front()).Run();
pending_tasks_.pop();
}
running_pending_tasks_ = false;
}
idle_time_ = base::TimeTicks();
const int event_id = NextEventId();
std::set<EventInfo>::iterator iter;
bool is_inserted;
std::tie(iter, is_inserted) = inflight_events_.emplace(
event_id, tick_clock_->NowTicks() + timeout,
base::BindOnce(std::move(abort_callback), event_id));
DCHECK(is_inserted);
id_event_map_.emplace(event_id, iter);
return event_id;
}
void ServiceWorkerTimeoutTimer::EndEvent(int event_id) {
auto iter = id_event_map_.find(event_id);
DCHECK(iter != id_event_map_.end());
inflight_events_.erase(iter->second);
id_event_map_.erase(iter);
if (!HasInflightEvent())
OnNoInflightEvent();
}
std::unique_ptr<ServiceWorkerTimeoutTimer::StayAwakeToken>
ServiceWorkerTimeoutTimer::CreateStayAwakeToken() {
if (!blink::ServiceWorkerUtils::IsServicificationEnabled())
return nullptr;
return std::make_unique<ServiceWorkerTimeoutTimer::StayAwakeToken>(
weak_factory_.GetWeakPtr());
}
void ServiceWorkerTimeoutTimer::PushPendingTask(
base::OnceClosure pending_task) {
DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
DCHECK(did_idle_timeout());
pending_tasks_.emplace(std::move(pending_task));
}
void ServiceWorkerTimeoutTimer::SetIdleTimerDelayToZero() {
DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
zero_idle_timer_delay_ = true;
if (!HasInflightEvent())
MaybeTriggerIdleTimer();
}
void ServiceWorkerTimeoutTimer::UpdateStatus() {
if (!blink::ServiceWorkerUtils::IsServicificationEnabled())
return;
base::TimeTicks now = tick_clock_->NowTicks();
// Abort all events exceeding |kEventTimeout|.
auto iter = inflight_events_.begin();
while (iter != inflight_events_.end() && iter->expiration_time <= now) {
int event_id = iter->id;
base::OnceClosure callback = std::move(iter->abort_callback);
iter = inflight_events_.erase(iter);
id_event_map_.erase(event_id);
std::move(callback).Run();
// Shut down the worker as soon as possible since the worker may have gone
// into bad state.
zero_idle_timer_delay_ = true;
}
// If the worker is now idle, set the |idle_time_| and possibly trigger the
// idle callback.
if (!HasInflightEvent() && idle_time_.is_null()) {
OnNoInflightEvent();
return;
}
if (!idle_time_.is_null() && idle_time_ < now) {
did_idle_timeout_ = true;
idle_callback_.Run();
}
}
bool ServiceWorkerTimeoutTimer::MaybeTriggerIdleTimer() {
DCHECK(!HasInflightEvent());
if (!zero_idle_timer_delay_)
return false;
did_idle_timeout_ = true;
idle_callback_.Run();
return true;
}
void ServiceWorkerTimeoutTimer::OnNoInflightEvent() {
DCHECK(!HasInflightEvent());
idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
MaybeTriggerIdleTimer();
}
bool ServiceWorkerTimeoutTimer::HasInflightEvent() const {
return !inflight_events_.empty() || running_pending_tasks_ ||
num_of_stay_awake_tokens_ > 0;
}
ServiceWorkerTimeoutTimer::EventInfo::EventInfo(
int id,
base::TimeTicks expiration_time,
base::OnceClosure abort_callback)
: id(id),
expiration_time(expiration_time),
abort_callback(std::move(abort_callback)) {}
ServiceWorkerTimeoutTimer::EventInfo::~EventInfo() = default;
bool ServiceWorkerTimeoutTimer::EventInfo::operator<(
const EventInfo& other) const {
if (expiration_time == other.expiration_time)
return id < other.id;
return expiration_time < other.expiration_time;
}
} // namespace content