1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/modules/service_worker/service_worker_event_queue.h"
6 
7 #include "base/atomic_sequence_num.h"
8 #include "base/bind.h"
9 #include "base/stl_util.h"
10 #include "base/time/default_tick_clock.h"
11 #include "base/time/time.h"
12 #include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom-blink.h"
13 #include "third_party/blink/renderer/platform/wtf/functional.h"
14 
15 namespace blink {
16 
17 namespace {
18 
NextEventId()19 int NextEventId() {
20   // Event id should not start from zero since HashMap in Blink requires
21   // non-zero keys.
22   static base::AtomicSequenceNumber s_event_id_sequence;
23   int next_event_id = s_event_id_sequence.GetNext() + 1;
24   CHECK_LT(next_event_id, std::numeric_limits<int>::max());
25   return next_event_id;
26 }
27 
28 }  // namespace
29 
30 // static
31 constexpr base::TimeDelta ServiceWorkerEventQueue::kEventTimeout;
32 constexpr base::TimeDelta ServiceWorkerEventQueue::kUpdateInterval;
33 
StayAwakeToken(base::WeakPtr<ServiceWorkerEventQueue> event_queue)34 ServiceWorkerEventQueue::StayAwakeToken::StayAwakeToken(
35     base::WeakPtr<ServiceWorkerEventQueue> event_queue)
36     : event_queue_(std::move(event_queue)) {
37   DCHECK(event_queue_);
38   event_queue_->ResetIdleTimeout();
39   event_queue_->num_of_stay_awake_tokens_++;
40 }
41 
~StayAwakeToken()42 ServiceWorkerEventQueue::StayAwakeToken::~StayAwakeToken() {
43   // If |event_queue_| has already been destroyed, it means the worker thread
44   // has already been killed.
45   if (!event_queue_)
46     return;
47   DCHECK_GT(event_queue_->num_of_stay_awake_tokens_, 0);
48   event_queue_->num_of_stay_awake_tokens_--;
49 
50   if (!event_queue_->HasInflightEvent())
51     event_queue_->OnNoInflightEvent();
52 }
53 
ServiceWorkerEventQueue(BeforeStartEventCallback before_start_event_callback,base::RepeatingClosure idle_callback,scoped_refptr<base::SequencedTaskRunner> task_runner)54 ServiceWorkerEventQueue::ServiceWorkerEventQueue(
55     BeforeStartEventCallback before_start_event_callback,
56     base::RepeatingClosure idle_callback,
57     scoped_refptr<base::SequencedTaskRunner> task_runner)
58     : ServiceWorkerEventQueue(std::move(before_start_event_callback),
59                               std::move(idle_callback),
60                               std::move(task_runner),
61                               base::DefaultTickClock::GetInstance()) {}
62 
ServiceWorkerEventQueue(BeforeStartEventCallback before_start_event_callback,base::RepeatingClosure idle_callback,scoped_refptr<base::SequencedTaskRunner> task_runner,const base::TickClock * tick_clock)63 ServiceWorkerEventQueue::ServiceWorkerEventQueue(
64     BeforeStartEventCallback before_start_event_callback,
65     base::RepeatingClosure idle_callback,
66     scoped_refptr<base::SequencedTaskRunner> task_runner,
67     const base::TickClock* tick_clock)
68     : task_runner_(std::move(task_runner)),
69       before_start_event_callback_(std::move(before_start_event_callback)),
70       idle_callback_(std::move(idle_callback)),
71       tick_clock_(tick_clock) {}
72 
~ServiceWorkerEventQueue()73 ServiceWorkerEventQueue::~ServiceWorkerEventQueue() {
74   in_dtor_ = true;
75   // Abort all callbacks.
76   for (auto& event : id_event_map_) {
77     std::move(event.value->abort_callback)
78         .Run(blink::mojom::ServiceWorkerEventStatus::ABORTED);
79   }
80 }
81 
Start()82 void ServiceWorkerEventQueue::Start() {
83   DCHECK(!timer_.IsRunning());
84   if (!HasInflightEvent() && !HasScheduledIdleCallback()) {
85     // If no event happens until Start(), the idle callback should be scheduled.
86     OnNoInflightEvent();
87   }
88   timer_.Start(FROM_HERE, kUpdateInterval,
89                WTF::BindRepeating(&ServiceWorkerEventQueue::UpdateStatus,
90                                   WTF::Unretained(this)));
91 }
92 
EnqueueNormal(StartCallback start_callback,AbortCallback abort_callback,base::Optional<base::TimeDelta> custom_timeout)93 void ServiceWorkerEventQueue::EnqueueNormal(
94     StartCallback start_callback,
95     AbortCallback abort_callback,
96     base::Optional<base::TimeDelta> custom_timeout) {
97   EnqueueEvent(std::make_unique<Event>(
98       Event::Type::Normal, std::move(start_callback), std::move(abort_callback),
99       std::move(custom_timeout)));
100 }
101 
EnqueuePending(StartCallback start_callback,AbortCallback abort_callback,base::Optional<base::TimeDelta> custom_timeout)102 void ServiceWorkerEventQueue::EnqueuePending(
103     StartCallback start_callback,
104     AbortCallback abort_callback,
105     base::Optional<base::TimeDelta> custom_timeout) {
106   EnqueueEvent(std::make_unique<Event>(
107       Event::Type::Pending, std::move(start_callback),
108       std::move(abort_callback), std::move(custom_timeout)));
109 }
110 
EnqueueOffline(StartCallback start_callback,AbortCallback abort_callback,base::Optional<base::TimeDelta> custom_timeout)111 void ServiceWorkerEventQueue::EnqueueOffline(
112     StartCallback start_callback,
113     AbortCallback abort_callback,
114     base::Optional<base::TimeDelta> custom_timeout) {
115   EnqueueEvent(std::make_unique<ServiceWorkerEventQueue::Event>(
116       ServiceWorkerEventQueue::Event::Type::Offline, std::move(start_callback),
117       std::move(abort_callback), std::move(custom_timeout)));
118 }
119 
CanStartEvent(const Event & event) const120 bool ServiceWorkerEventQueue::CanStartEvent(const Event& event) const {
121   if (!HasInflightEvent())
122     return true;
123   if (event.type == Event::Type::Offline)
124     return running_offline_events_;
125   return !running_offline_events_;
126 }
127 
EnqueueEvent(std::unique_ptr<Event> event)128 void ServiceWorkerEventQueue::EnqueueEvent(std::unique_ptr<Event> event) {
129   DCHECK(event->type != Event::Type::Pending || did_idle_timeout());
130   bool can_start_processing_events =
131       !processing_events_ && event->type != Event::Type::Pending;
132   queue_.emplace_back(std::move(event));
133 
134   if (!can_start_processing_events)
135     return;
136 
137   ResetIdleTimeout();
138   ProcessEvents();
139 }
140 
ProcessEvents()141 void ServiceWorkerEventQueue::ProcessEvents() {
142   DCHECK(!processing_events_);
143   processing_events_ = true;
144   while (!queue_.IsEmpty() && CanStartEvent(*queue_.front())) {
145     StartEvent(queue_.TakeFirst());
146   }
147   processing_events_ = false;
148 
149   // We have to check HasInflightEvent() and may trigger
150   // OnNoInflightEvent() here because StartEvent() can call EndEvent()
151   // synchronously, and EndEvent() never triggers OnNoInflightEvent()
152   // while ProcessEvents() is running.
153   if (!HasInflightEvent())
154     OnNoInflightEvent();
155 }
156 
StartEvent(std::unique_ptr<Event> event)157 void ServiceWorkerEventQueue::StartEvent(std::unique_ptr<Event> event) {
158   DCHECK(CanStartEvent(*event));
159   running_offline_events_ = event->type == Event::Type::Offline;
160   const int event_id = NextEventId();
161   DCHECK(!HasEvent(event_id));
162   id_event_map_.insert(
163       event_id, std::make_unique<EventInfo>(
164                     tick_clock_->NowTicks() +
165                         event->custom_timeout.value_or(kEventTimeout),
166                     WTF::Bind(std::move(event->abort_callback), event_id)));
167   if (before_start_event_callback_)
168     before_start_event_callback_.Run(event->type == Event::Type::Offline);
169   std::move(event->start_callback).Run(event_id);
170 }
171 
EndEvent(int event_id)172 void ServiceWorkerEventQueue::EndEvent(int event_id) {
173   DCHECK(HasEvent(event_id));
174   id_event_map_.erase(event_id);
175   // Check |processing_events_| here because EndEvent() can be called
176   // synchronously in StartEvent(). We don't want to trigger
177   // OnNoInflightEvent() while ProcessEvents() is running.
178   if (!processing_events_ && !HasInflightEvent())
179     OnNoInflightEvent();
180 }
181 
HasEvent(int event_id) const182 bool ServiceWorkerEventQueue::HasEvent(int event_id) const {
183   return id_event_map_.find(event_id) != id_event_map_.end();
184 }
185 
186 std::unique_ptr<ServiceWorkerEventQueue::StayAwakeToken>
CreateStayAwakeToken()187 ServiceWorkerEventQueue::CreateStayAwakeToken() {
188   return std::make_unique<ServiceWorkerEventQueue::StayAwakeToken>(
189       weak_factory_.GetWeakPtr());
190 }
191 
SetIdleDelay(base::TimeDelta idle_delay)192 void ServiceWorkerEventQueue::SetIdleDelay(base::TimeDelta idle_delay) {
193   idle_delay_ = idle_delay;
194 
195   if (HasInflightEvent())
196     return;
197 
198   if (did_idle_timeout()) {
199     // The idle callback has already been called. It should not be called again
200     // until this worker becomes active.
201     return;
202   }
203 
204   // There should be a scheduled idle callback because this is now in the idle
205   // delay. The idle callback will be rescheduled based on the new idle delay.
206   DCHECK(HasScheduledIdleCallback());
207   idle_callback_handle_.Cancel();
208 
209   // Calculate the updated time of when the |idle_callback_| should be invoked.
210   DCHECK(!last_no_inflight_event_.is_null());
211   auto new_idle_callback_time = last_no_inflight_event_ + idle_delay;
212   base::TimeDelta delta_until_idle =
213       new_idle_callback_time - tick_clock_->NowTicks();
214 
215   if (delta_until_idle <= base::TimeDelta::FromSeconds(0)) {
216     // The new idle delay is shorter than the previous idle delay, and the idle
217     // time has been already passed. Let's run the idle callback immediately.
218     TriggerIdleCallback();
219     return;
220   }
221 
222   // Let's schedule the idle callback in |delta_until_idle|.
223   ScheduleIdleCallback(delta_until_idle);
224 }
225 
UpdateStatus()226 void ServiceWorkerEventQueue::UpdateStatus() {
227   base::TimeTicks now = tick_clock_->NowTicks();
228 
229   HashMap<int /* event_id */, std::unique_ptr<EventInfo>> new_id_event_map;
230 
231   bool should_idle_delay_to_be_zero = false;
232   // Abort all events exceeding |kEventTimeout|.
233   for (auto& it : id_event_map_) {
234     auto& event_info = it.value;
235     if (event_info->expiration_time > now) {
236       new_id_event_map.insert(it.key, std::move(event_info));
237       continue;
238     }
239     std::move(event_info->abort_callback)
240         .Run(blink::mojom::ServiceWorkerEventStatus::TIMEOUT);
241     should_idle_delay_to_be_zero = true;
242   }
243   id_event_map_.swap(new_id_event_map);
244   if (should_idle_delay_to_be_zero) {
245     // Inflight events might be timed out and there might be no inflight event
246     // at this point.
247     if (!HasInflightEvent()) {
248       OnNoInflightEvent();
249     }
250     // Shut down the worker as soon as possible since the worker may have gone
251     // into bad state.
252     SetIdleDelay(base::TimeDelta::FromSeconds(0));
253   }
254 }
255 
ScheduleIdleCallback(base::TimeDelta delay)256 void ServiceWorkerEventQueue::ScheduleIdleCallback(base::TimeDelta delay) {
257   DCHECK(!HasInflightEvent());
258   DCHECK(!HasScheduledIdleCallback());
259 
260   // WTF::Unretained() is safe because the task runner will be destroyed
261   // before |this| is destroyed at ServiceWorkerGlobalScope::Dispose().
262   idle_callback_handle_ = PostDelayedCancellableTask(
263       *task_runner_, FROM_HERE,
264       WTF::Bind(&ServiceWorkerEventQueue::TriggerIdleCallback,
265                 WTF::Unretained(this)),
266       delay);
267 }
268 
TriggerIdleCallback()269 void ServiceWorkerEventQueue::TriggerIdleCallback() {
270   DCHECK(!HasInflightEvent());
271   DCHECK(!HasScheduledIdleCallback());
272   DCHECK(!did_idle_timeout_);
273 
274   did_idle_timeout_ = true;
275   idle_callback_.Run();
276 }
277 
OnNoInflightEvent()278 void ServiceWorkerEventQueue::OnNoInflightEvent() {
279   DCHECK(!HasInflightEvent());
280   running_offline_events_ = false;
281   // There might be events in the queue because offline (or non-offline) events
282   // can be enqueued during running non-offline (or offline) events.
283   if (!queue_.IsEmpty()) {
284     ProcessEvents();
285     return;
286   }
287   last_no_inflight_event_ = tick_clock_->NowTicks();
288   ScheduleIdleCallback(idle_delay_);
289 }
290 
HasInflightEvent() const291 bool ServiceWorkerEventQueue::HasInflightEvent() const {
292   return !id_event_map_.IsEmpty() || num_of_stay_awake_tokens_ > 0;
293 }
294 
ResetIdleTimeout()295 void ServiceWorkerEventQueue::ResetIdleTimeout() {
296   last_no_inflight_event_ = base::TimeTicks();
297   idle_callback_handle_.Cancel();
298   did_idle_timeout_ = false;
299 }
300 
HasScheduledIdleCallback() const301 bool ServiceWorkerEventQueue::HasScheduledIdleCallback() const {
302   return idle_callback_handle_.IsActive();
303 }
304 
Event(ServiceWorkerEventQueue::Event::Type type,StartCallback start_callback,AbortCallback abort_callback,base::Optional<base::TimeDelta> custom_timeout)305 ServiceWorkerEventQueue::Event::Event(
306     ServiceWorkerEventQueue::Event::Type type,
307     StartCallback start_callback,
308     AbortCallback abort_callback,
309     base::Optional<base::TimeDelta> custom_timeout)
310     : type(type),
311       start_callback(std::move(start_callback)),
312       abort_callback(std::move(abort_callback)),
313       custom_timeout(custom_timeout) {}
314 
315 ServiceWorkerEventQueue::Event::~Event() = default;
316 
EventInfo(base::TimeTicks expiration_time,base::OnceCallback<void (blink::mojom::ServiceWorkerEventStatus)> abort_callback)317 ServiceWorkerEventQueue::EventInfo::EventInfo(
318     base::TimeTicks expiration_time,
319     base::OnceCallback<void(blink::mojom::ServiceWorkerEventStatus)>
320         abort_callback)
321     : expiration_time(expiration_time),
322       abort_callback(std::move(abort_callback)) {}
323 
324 ServiceWorkerEventQueue::EventInfo::~EventInfo() = default;
325 
326 }  // namespace blink
327