blob: 85e31311c63a4ee648a92a73dc5014d3c65731a0 [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::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_->ResetIdleTimeout();
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,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: ServiceWorkerEventQueue(std::move(before_start_event_callback),
std::move(idle_callback),
std::move(task_runner),
base::DefaultTickClock::GetInstance()) {}
ServiceWorkerEventQueue::ServiceWorkerEventQueue(
BeforeStartEventCallback before_start_event_callback,
base::RepeatingClosure idle_callback,
scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::TickClock* tick_clock)
: task_runner_(std::move(task_runner)),
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());
if (!HasInflightEvent() && !HasScheduledIdleCallback()) {
// If no event happens until Start(), the idle callback should be scheduled.
OnNoInflightEvent();
}
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;
ResetIdleTimeout();
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;
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::SetIdleDelay(base::TimeDelta idle_delay) {
idle_delay_ = idle_delay;
if (HasInflightEvent())
return;
if (did_idle_timeout()) {
// The idle callback has already been called. It should not be called again
// until this worker becomes active.
return;
}
// There should be a scheduled idle callback because this is now in the idle
// delay. The idle callback will be rescheduled based on the new idle delay.
DCHECK(HasScheduledIdleCallback());
idle_callback_handle_.Cancel();
// Calculate the updated time of when the |idle_callback_| should be invoked.
DCHECK(!last_no_inflight_event_.is_null());
auto new_idle_callback_time = last_no_inflight_event_ + idle_delay;
base::TimeDelta delta_until_idle =
new_idle_callback_time - tick_clock_->NowTicks();
if (delta_until_idle <= base::TimeDelta::FromSeconds(0)) {
// The new idle delay is shorter than the previous idle delay, and the idle
// time has been already passed. Let's run the idle callback immediately.
TriggerIdleCallback();
return;
}
// Let's schedule the idle callback in |delta_until_idle|.
ScheduleIdleCallback(delta_until_idle);
}
void ServiceWorkerEventQueue::UpdateStatus() {
base::TimeTicks now = tick_clock_->NowTicks();
HashMap<int /* event_id */, std::unique_ptr<EventInfo>> new_id_event_map;
bool should_idle_delay_to_be_zero = false;
// 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);
should_idle_delay_to_be_zero = true;
}
id_event_map_.swap(new_id_event_map);
if (should_idle_delay_to_be_zero) {
// Inflight events might be timed out and there might be no inflight event
// at this point.
if (!HasInflightEvent()) {
OnNoInflightEvent();
}
// Shut down the worker as soon as possible since the worker may have gone
// into bad state.
SetIdleDelay(base::TimeDelta::FromSeconds(0));
}
}
void ServiceWorkerEventQueue::ScheduleIdleCallback(base::TimeDelta delay) {
DCHECK(!HasInflightEvent());
DCHECK(!HasScheduledIdleCallback());
// WTF::Unretained() is safe because the task runner will be destroyed
// before |this| is destroyed at ServiceWorkerGlobalScope::Dispose().
idle_callback_handle_ = PostDelayedCancellableTask(
*task_runner_, FROM_HERE,
WTF::Bind(&ServiceWorkerEventQueue::TriggerIdleCallback,
WTF::Unretained(this)),
delay);
}
void ServiceWorkerEventQueue::TriggerIdleCallback() {
DCHECK(!HasInflightEvent());
DCHECK(!HasScheduledIdleCallback());
DCHECK(!did_idle_timeout_);
did_idle_timeout_ = true;
idle_callback_.Run();
}
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;
}
last_no_inflight_event_ = tick_clock_->NowTicks();
ScheduleIdleCallback(idle_delay_);
}
bool ServiceWorkerEventQueue::HasInflightEvent() const {
return !id_event_map_.IsEmpty() || num_of_stay_awake_tokens_ > 0;
}
void ServiceWorkerEventQueue::ResetIdleTimeout() {
last_no_inflight_event_ = base::TimeTicks();
idle_callback_handle_.Cancel();
did_idle_timeout_ = false;
}
bool ServiceWorkerEventQueue::HasScheduledIdleCallback() const {
return idle_callback_handle_.IsActive();
}
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