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