blob: 4a8f3996576e1914b16d9b980064443d7d7a9ef5 [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 "third_party/blink/renderer/modules/service_worker/service_worker_event_queue.h"
#include "base/atomic_sequence_num.h"
#include "base/bind.h"
#include "base/stl_util.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom-blink.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
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 ServiceWorkerEventQueue::kIdleDelay;
constexpr base::TimeDelta ServiceWorkerEventQueue::kEventTimeout;
constexpr base::TimeDelta ServiceWorkerEventQueue::kUpdateInterval;
ServiceWorkerEventQueue::StayAwakeToken::StayAwakeToken(
base::WeakPtr<ServiceWorkerEventQueue> event_queue)
: event_queue_(std::move(event_queue)) {
DCHECK(event_queue_);
event_queue_->num_of_stay_awake_tokens_++;
}
ServiceWorkerEventQueue::StayAwakeToken::~StayAwakeToken() {
// If |event_queue_| has already been destroyed, it means the worker thread
// has already been killed.
if (!event_queue_)
return;
DCHECK_GT(event_queue_->num_of_stay_awake_tokens_, 0);
event_queue_->num_of_stay_awake_tokens_--;
if (!event_queue_->HasInflightEvent())
event_queue_->OnNoInflightEvent();
}
ServiceWorkerEventQueue::ServiceWorkerEventQueue(
BeforeStartEventCallback before_start_event_callback,
base::RepeatingClosure idle_callback)
: ServiceWorkerEventQueue(std::move(before_start_event_callback),
std::move(idle_callback),
base::DefaultTickClock::GetInstance()) {}
ServiceWorkerEventQueue::ServiceWorkerEventQueue(
BeforeStartEventCallback before_start_event_callback,
base::RepeatingClosure idle_callback,
const base::TickClock* tick_clock)
: before_start_event_callback_(std::move(before_start_event_callback)),
idle_callback_(std::move(idle_callback)),
tick_clock_(tick_clock) {}
ServiceWorkerEventQueue::~ServiceWorkerEventQueue() {
in_dtor_ = true;
// Abort all callbacks.
for (auto& event : id_event_map_) {
std::move(event.value->abort_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::ABORTED);
}
}
void ServiceWorkerEventQueue::Start() {
DCHECK(!timer_.IsRunning());
// |idle_callback_| will be invoked if no event happens in |kIdleDelay|.
if (!HasInflightEvent() && idle_time_.is_null())
idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
timer_.Start(FROM_HERE, kUpdateInterval,
WTF::BindRepeating(&ServiceWorkerEventQueue::UpdateStatus,
WTF::Unretained(this)));
}
void ServiceWorkerEventQueue::EnqueueNormal(
StartCallback start_callback,
AbortCallback abort_callback,
base::Optional<base::TimeDelta> custom_timeout) {
EnqueueEvent(std::make_unique<Event>(
Event::Type::Normal, std::move(start_callback), std::move(abort_callback),
std::move(custom_timeout)));
}
void ServiceWorkerEventQueue::EnqueuePending(
StartCallback start_callback,
AbortCallback abort_callback,
base::Optional<base::TimeDelta> custom_timeout) {
EnqueueEvent(std::make_unique<Event>(
Event::Type::Pending, std::move(start_callback),
std::move(abort_callback), std::move(custom_timeout)));
}
void ServiceWorkerEventQueue::EnqueueOffline(
StartCallback start_callback,
AbortCallback abort_callback,
base::Optional<base::TimeDelta> custom_timeout) {
EnqueueEvent(std::make_unique<ServiceWorkerEventQueue::Event>(
ServiceWorkerEventQueue::Event::Type::Offline, std::move(start_callback),
std::move(abort_callback), std::move(custom_timeout)));
}
bool ServiceWorkerEventQueue::CanStartEvent(const Event& event) const {
if (!HasInflightEvent())
return true;
if (event.type == Event::Type::Offline)
return running_offline_events_;
return !running_offline_events_;
}
void ServiceWorkerEventQueue::EnqueueEvent(std::unique_ptr<Event> event) {
DCHECK(event->type != Event::Type::Pending || did_idle_timeout());
bool can_start_processing_events =
!processing_events_ && event->type != Event::Type::Pending;
queue_.emplace_back(std::move(event));
if (!can_start_processing_events)
return;
if (did_idle_timeout()) {
idle_time_ = base::TimeTicks();
did_idle_timeout_ = false;
}
ProcessEvents();
}
void ServiceWorkerEventQueue::ProcessEvents() {
DCHECK(!processing_events_);
processing_events_ = true;
while (!queue_.IsEmpty() && CanStartEvent(*queue_.front())) {
StartEvent(queue_.TakeFirst());
}
processing_events_ = false;
// We have to check HasInflightEvent() and may trigger
// OnNoInflightEvent() here because StartEvent() can call EndEvent()
// synchronously, and EndEvent() never triggers OnNoInflightEvent()
// while ProcessEvents() is running.
if (!HasInflightEvent())
OnNoInflightEvent();
}
void ServiceWorkerEventQueue::StartEvent(std::unique_ptr<Event> event) {
DCHECK(CanStartEvent(*event));
running_offline_events_ = event->type == Event::Type::Offline;
idle_time_ = base::TimeTicks();
const int event_id = NextEventId();
DCHECK(!HasEvent(event_id));
id_event_map_.insert(
event_id, std::make_unique<EventInfo>(
tick_clock_->NowTicks() +
event->custom_timeout.value_or(kEventTimeout),
WTF::Bind(std::move(event->abort_callback), event_id)));
if (before_start_event_callback_)
before_start_event_callback_.Run(event->type == Event::Type::Offline);
std::move(event->start_callback).Run(event_id);
}
void ServiceWorkerEventQueue::EndEvent(int event_id) {
DCHECK(HasEvent(event_id));
id_event_map_.erase(event_id);
// Check |processing_events_| here because EndEvent() can be called
// synchronously in StartEvent(). We don't want to trigger
// OnNoInflightEvent() while ProcessEvents() is running.
if (!processing_events_ && !HasInflightEvent())
OnNoInflightEvent();
}
bool ServiceWorkerEventQueue::HasEvent(int event_id) const {
return id_event_map_.find(event_id) != id_event_map_.end();
}
std::unique_ptr<ServiceWorkerEventQueue::StayAwakeToken>
ServiceWorkerEventQueue::CreateStayAwakeToken() {
return std::make_unique<ServiceWorkerEventQueue::StayAwakeToken>(
weak_factory_.GetWeakPtr());
}
void ServiceWorkerEventQueue::SetIdleTimerDelayToZero() {
zero_idle_timer_delay_ = true;
if (!HasInflightEvent())
MaybeTriggerIdleTimer();
}
void ServiceWorkerEventQueue::UpdateStatus() {
base::TimeTicks now = tick_clock_->NowTicks();
HashMap<int /* event_id */, std::unique_ptr<EventInfo>> new_id_event_map;
// Abort all events exceeding |kEventTimeout|.
for (auto& it : id_event_map_) {
auto& event_info = it.value;
if (event_info->expiration_time > now) {
new_id_event_map.insert(it.key, std::move(event_info));
continue;
}
std::move(event_info->abort_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::TIMEOUT);
// Shut down the worker as soon as possible since the worker may have gone
// into bad state.
zero_idle_timer_delay_ = true;
}
id_event_map_.swap(new_id_event_map);
// 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 ServiceWorkerEventQueue::MaybeTriggerIdleTimer() {
DCHECK(!HasInflightEvent());
if (!zero_idle_timer_delay_)
return false;
did_idle_timeout_ = true;
idle_callback_.Run();
return true;
}
void ServiceWorkerEventQueue::OnNoInflightEvent() {
DCHECK(!HasInflightEvent());
running_offline_events_ = false;
// There might be events in the queue because offline (or non-offline) events
// can be enqueued during running non-offline (or offline) events.
if (!queue_.IsEmpty()) {
ProcessEvents();
return;
}
idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
MaybeTriggerIdleTimer();
}
bool ServiceWorkerEventQueue::HasInflightEvent() const {
return !id_event_map_.IsEmpty() || num_of_stay_awake_tokens_ > 0;
}
ServiceWorkerEventQueue::Event::Event(
ServiceWorkerEventQueue::Event::Type type,
StartCallback start_callback,
AbortCallback abort_callback,
base::Optional<base::TimeDelta> custom_timeout)
: type(type),
start_callback(std::move(start_callback)),
abort_callback(std::move(abort_callback)),
custom_timeout(custom_timeout) {}
ServiceWorkerEventQueue::Event::~Event() = default;
ServiceWorkerEventQueue::EventInfo::EventInfo(
base::TimeTicks expiration_time,
base::OnceCallback<void(blink::mojom::ServiceWorkerEventStatus)>
abort_callback)
: expiration_time(expiration_time),
abort_callback(std::move(abort_callback)) {}
ServiceWorkerEventQueue::EventInfo::~EventInfo() = default;
} // namespace blink