Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "PrioritizedEventQueue.h"
8
#include "mozilla/EventQueue.h"
9
#include "mozilla/ScopeExit.h"
10
#include "mozilla/StaticPrefs_threads.h"
11
#include "mozilla/ipc/IdleSchedulerChild.h"
12
#include "nsThreadManager.h"
13
#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
14
#include "InputEventStatistics.h"
15
16
using namespace mozilla;
17
18
PrioritizedEventQueue::PrioritizedEventQueue(
19
already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
20
: mHighQueue(MakeUnique<EventQueue>(EventQueuePriority::High)),
21
mInputQueue(MakeUnique<EventQueue>(EventQueuePriority::Input)),
22
mMediumHighQueue(MakeUnique<EventQueue>(EventQueuePriority::MediumHigh)),
23
mNormalQueue(MakeUnique<EventQueue>(EventQueuePriority::Normal)),
24
mDeferredTimersQueue(
25
MakeUnique<EventQueue>(EventQueuePriority::DeferredTimers)),
26
mIdleQueue(MakeUnique<EventQueue>(EventQueuePriority::Idle)),
27
mIdlePeriodState(std::move(aIdlePeriod)) {}
28
29
PrioritizedEventQueue::~PrioritizedEventQueue() = default;
30
31
void PrioritizedEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
32
EventQueuePriority aPriority,
33
const MutexAutoLock& aProofOfLock,
34
mozilla::TimeDuration* aDelay) {
35
// Double check the priority with a QI.
36
RefPtr<nsIRunnable> event(aEvent);
37
EventQueuePriority priority = aPriority;
38
39
if (priority == EventQueuePriority::Input &&
40
mInputQueueState == STATE_DISABLED) {
41
priority = EventQueuePriority::Normal;
42
} else if (priority == EventQueuePriority::MediumHigh &&
43
!StaticPrefs::threads_medium_high_event_queue_enabled()) {
44
priority = EventQueuePriority::Normal;
45
}
46
47
switch (priority) {
48
case EventQueuePriority::High:
49
mHighQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
50
break;
51
case EventQueuePriority::Input:
52
mInputQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
53
break;
54
case EventQueuePriority::MediumHigh:
55
mMediumHighQueue->PutEvent(event.forget(), priority, aProofOfLock,
56
aDelay);
57
break;
58
case EventQueuePriority::Normal:
59
mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
60
break;
61
case EventQueuePriority::DeferredTimers:
62
mDeferredTimersQueue->PutEvent(event.forget(), priority, aProofOfLock,
63
aDelay);
64
break;
65
case EventQueuePriority::Idle:
66
mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock, aDelay);
67
break;
68
case EventQueuePriority::Count:
69
MOZ_CRASH("EventQueuePriority::Count isn't a valid priority");
70
break;
71
}
72
}
73
74
EventQueuePriority PrioritizedEventQueue::SelectQueue(
75
bool aUpdateState, const MutexAutoLock& aProofOfLock) {
76
size_t inputCount = mInputQueue->Count(aProofOfLock);
77
78
if (aUpdateState && mInputQueueState == STATE_ENABLED &&
79
mInputHandlingStartTime.IsNull() && inputCount > 0) {
80
mInputHandlingStartTime =
81
InputEventStatistics::Get().GetInputHandlingStartTime(inputCount);
82
}
83
84
// We check the different queues in the following order. The conditions we use
85
// are meant to avoid starvation and to ensure that we don't process an event
86
// at the wrong time.
87
//
88
// HIGH: if mProcessHighPriorityQueue
89
// INPUT: if inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime
90
// MEDIUMHIGH: if medium high pending
91
// NORMAL: if normal pending
92
//
93
// If we still don't have an event, then we take events from the queues
94
// in the following order:
95
//
96
// HIGH
97
// INPUT
98
// DEFERREDTIMERS: if GetLocalIdleDeadline()
99
// IDLE: if GetLocalIdleDeadline()
100
//
101
// If we don't get an event in this pass, then we return null since no events
102
// are ready.
103
104
// This variable determines which queue we will take an event from.
105
EventQueuePriority queue;
106
bool highPending = !mHighQueue->IsEmpty(aProofOfLock);
107
108
if (mProcessHighPriorityQueue) {
109
queue = EventQueuePriority::High;
110
} else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING ||
111
(mInputQueueState == STATE_ENABLED &&
112
!mInputHandlingStartTime.IsNull() &&
113
TimeStamp::Now() > mInputHandlingStartTime))) {
114
queue = EventQueuePriority::Input;
115
} else if (!mMediumHighQueue->IsEmpty(aProofOfLock)) {
116
MOZ_ASSERT(
117
mInputQueueState != STATE_FLUSHING,
118
"Shouldn't consume medium high event when flushing input events");
119
queue = EventQueuePriority::MediumHigh;
120
} else if (!mNormalQueue->IsEmpty(aProofOfLock)) {
121
MOZ_ASSERT(mInputQueueState != STATE_FLUSHING,
122
"Shouldn't consume normal event when flushing input events");
123
queue = EventQueuePriority::Normal;
124
} else if (highPending) {
125
queue = EventQueuePriority::High;
126
} else if (inputCount > 0 && mInputQueueState != STATE_SUSPEND) {
127
MOZ_ASSERT(
128
mInputQueueState != STATE_DISABLED,
129
"Shouldn't consume input events when the input queue is disabled");
130
queue = EventQueuePriority::Input;
131
} else if (!mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
132
// We may not actually return an idle event in this case.
133
queue = EventQueuePriority::DeferredTimers;
134
} else {
135
// We may not actually return an idle event in this case.
136
queue = EventQueuePriority::Idle;
137
}
138
139
MOZ_ASSERT_IF(
140
queue == EventQueuePriority::Input,
141
mInputQueueState != STATE_DISABLED && mInputQueueState != STATE_SUSPEND);
142
143
if (aUpdateState) {
144
mProcessHighPriorityQueue = highPending;
145
}
146
147
return queue;
148
}
149
150
// The delay returned is the queuing delay a hypothetical Input event would
151
// see due to the current running event if it had arrived while the current
152
// event was queued. This means that any event running at priority below
153
// Input doesn't cause queuing delay for Input events, and we return
154
// TimeDuration() for those cases.
155
already_AddRefed<nsIRunnable> PrioritizedEventQueue::GetEvent(
156
EventQueuePriority* aPriority, const MutexAutoLock& aProofOfLock,
157
mozilla::TimeDuration* aHypotheticalInputEventDelay) {
158
#ifndef RELEASE_OR_BETA
159
// Clear mNextIdleDeadline so that it is possible to determine that
160
// we're running an idle runnable in ProcessNextEvent.
161
*mNextIdleDeadline = TimeStamp();
162
#endif
163
164
EventQueuePriority queue = SelectQueue(true, aProofOfLock);
165
auto guard = MakeScopeExit([&] {
166
mIdlePeriodState.ForgetPendingTaskGuarantee();
167
if (queue != EventQueuePriority::Idle &&
168
queue != EventQueuePriority::DeferredTimers) {
169
mIdlePeriodState.FlagNotIdle(*mMutex);
170
}
171
});
172
173
if (aPriority) {
174
*aPriority = queue;
175
}
176
177
// Since Input events will only be delayed behind Input or High events,
178
// the amount of time a lower-priority event spent in the queue is
179
// irrelevant in knowing how long an input event would be delayed.
180
// Alternatively, we could export the delay and let the higher-level code
181
// key off the returned priority level (though then it'd need to know
182
// if the thread's queue was a PrioritizedEventQueue or normal/other
183
// EventQueue).
184
nsCOMPtr<nsIRunnable> event;
185
switch (queue) {
186
default:
187
MOZ_CRASH();
188
break;
189
190
case EventQueuePriority::High:
191
event = mHighQueue->GetEvent(aPriority, aProofOfLock,
192
aHypotheticalInputEventDelay);
193
MOZ_ASSERT(event);
194
mInputHandlingStartTime = TimeStamp();
195
mProcessHighPriorityQueue = false;
196
break;
197
198
case EventQueuePriority::Input:
199
event = mInputQueue->GetEvent(aPriority, aProofOfLock,
200
aHypotheticalInputEventDelay);
201
MOZ_ASSERT(event);
202
break;
203
204
// All queue priorities below Input don't add their queuing time to the
205
// time an input event will be delayed, so report 0 for time-in-queue
206
// if we're below Input; input events will only be delayed by the time
207
// an event actually runs (if the event is below Input event's priority)
208
case EventQueuePriority::MediumHigh:
209
event = mMediumHighQueue->GetEvent(aPriority, aProofOfLock);
210
*aHypotheticalInputEventDelay = TimeDuration();
211
break;
212
213
case EventQueuePriority::Normal:
214
event = mNormalQueue->GetEvent(aPriority, aProofOfLock);
215
*aHypotheticalInputEventDelay = TimeDuration();
216
break;
217
218
case EventQueuePriority::Idle:
219
case EventQueuePriority::DeferredTimers:
220
*aHypotheticalInputEventDelay = TimeDuration();
221
// If we get here, then all queues except deferredtimers and idle are
222
// empty.
223
224
if (mIdleQueue->IsEmpty(aProofOfLock) &&
225
mDeferredTimersQueue->IsEmpty(aProofOfLock)) {
226
mIdlePeriodState.RanOutOfTasks(*mMutex);
227
return nullptr;
228
}
229
230
TimeStamp idleDeadline = mIdlePeriodState.GetDeadlineForIdleTask(*mMutex);
231
if (!idleDeadline) {
232
return nullptr;
233
}
234
235
event = mDeferredTimersQueue->GetEvent(aPriority, aProofOfLock);
236
if (!event) {
237
event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
238
}
239
if (event) {
240
nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event);
241
if (idleEvent) {
242
idleEvent->SetDeadline(idleDeadline);
243
}
244
245
#ifndef RELEASE_OR_BETA
246
// Store the next idle deadline to be able to determine budget use
247
// in ProcessNextEvent.
248
*mNextIdleDeadline = idleDeadline;
249
#endif
250
}
251
break;
252
} // switch (queue)
253
254
if (!event) {
255
*aHypotheticalInputEventDelay = TimeDuration();
256
}
257
258
return event.forget();
259
}
260
261
void PrioritizedEventQueue::DidRunEvent(const MutexAutoLock& aProofOfLock) {
262
if (IsEmpty(aProofOfLock)) {
263
// Certainly no more idle tasks.
264
mIdlePeriodState.RanOutOfTasks(*mMutex);
265
}
266
}
267
268
bool PrioritizedEventQueue::IsEmpty(const MutexAutoLock& aProofOfLock) {
269
// Just check IsEmpty() on the sub-queues. Don't bother checking the idle
270
// deadline since that only determines whether an idle event is ready or not.
271
return mHighQueue->IsEmpty(aProofOfLock) &&
272
mInputQueue->IsEmpty(aProofOfLock) &&
273
mMediumHighQueue->IsEmpty(aProofOfLock) &&
274
mNormalQueue->IsEmpty(aProofOfLock) &&
275
mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
276
mIdleQueue->IsEmpty(aProofOfLock);
277
}
278
279
bool PrioritizedEventQueue::HasReadyEvent(const MutexAutoLock& aProofOfLock) {
280
mIdlePeriodState.ForgetPendingTaskGuarantee();
281
282
EventQueuePriority queue = SelectQueue(false, aProofOfLock);
283
284
if (queue == EventQueuePriority::High) {
285
return mHighQueue->HasReadyEvent(aProofOfLock);
286
} else if (queue == EventQueuePriority::Input) {
287
return mInputQueue->HasReadyEvent(aProofOfLock);
288
} else if (queue == EventQueuePriority::MediumHigh) {
289
return mMediumHighQueue->HasReadyEvent(aProofOfLock);
290
} else if (queue == EventQueuePriority::Normal) {
291
return mNormalQueue->HasReadyEvent(aProofOfLock);
292
}
293
294
MOZ_ASSERT(queue == EventQueuePriority::Idle ||
295
queue == EventQueuePriority::DeferredTimers);
296
297
// If we get here, then both the high and normal queues are empty.
298
299
if (mDeferredTimersQueue->IsEmpty(aProofOfLock) &&
300
mIdleQueue->IsEmpty(aProofOfLock)) {
301
return false;
302
}
303
304
TimeStamp idleDeadline = mIdlePeriodState.PeekIdleDeadline(*mMutex);
305
if (idleDeadline && (mDeferredTimersQueue->HasReadyEvent(aProofOfLock) ||
306
mIdleQueue->HasReadyEvent(aProofOfLock))) {
307
mIdlePeriodState.EnforcePendingTaskGuarantee();
308
return true;
309
}
310
311
return false;
312
}
313
314
bool PrioritizedEventQueue::HasPendingHighPriorityEvents(
315
const MutexAutoLock& aProofOfLock) {
316
return !mHighQueue->IsEmpty(aProofOfLock);
317
}
318
319
size_t PrioritizedEventQueue::Count(const MutexAutoLock& aProofOfLock) const {
320
MOZ_CRASH("unimplemented");
321
}
322
323
void PrioritizedEventQueue::EnableInputEventPrioritization(
324
const MutexAutoLock& aProofOfLock) {
325
MOZ_ASSERT(mInputQueueState == STATE_DISABLED);
326
mInputQueueState = STATE_ENABLED;
327
mInputHandlingStartTime = TimeStamp();
328
}
329
330
void PrioritizedEventQueue::FlushInputEventPrioritization(
331
const MutexAutoLock& aProofOfLock) {
332
MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
333
mInputQueueState == STATE_SUSPEND);
334
mInputQueueState =
335
mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND;
336
}
337
338
void PrioritizedEventQueue::SuspendInputEventPrioritization(
339
const MutexAutoLock& aProofOfLock) {
340
MOZ_ASSERT(mInputQueueState == STATE_ENABLED ||
341
mInputQueueState == STATE_FLUSHING);
342
mInputQueueState = STATE_SUSPEND;
343
}
344
345
void PrioritizedEventQueue::ResumeInputEventPrioritization(
346
const MutexAutoLock& aProofOfLock) {
347
MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
348
mInputQueueState = STATE_ENABLED;
349
}