Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/threads/PrioritizedEventQueue.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsThreadManager.h"
11
#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown
12
#include "InputEventStatistics.h"
13
14
using namespace mozilla;
15
16
template<class InnerQueueT>
17
PrioritizedEventQueue<InnerQueueT>::PrioritizedEventQueue(UniquePtr<InnerQueueT> aHighQueue,
18
                                                          UniquePtr<InnerQueueT> aInputQueue,
19
                                                          UniquePtr<InnerQueueT> aNormalQueue,
20
                                                          UniquePtr<InnerQueueT> aIdleQueue,
21
                                                          already_AddRefed<nsIIdlePeriod> aIdlePeriod)
22
  : mHighQueue(std::move(aHighQueue))
23
  , mInputQueue(std::move(aInputQueue))
24
  , mNormalQueue(std::move(aNormalQueue))
25
  , mIdleQueue(std::move(aIdleQueue))
26
  , mIdlePeriod(aIdlePeriod)
27
3
{
28
3
  static_assert(IsBaseOf<AbstractEventQueue, InnerQueueT>::value,
29
3
                "InnerQueueT must be an AbstractEventQueue subclass");
30
3
}
mozilla::PrioritizedEventQueue<mozilla::EventQueue>::PrioritizedEventQueue(mozilla::UniquePtr<mozilla::EventQueue, mozilla::DefaultDelete<mozilla::EventQueue> >, mozilla::UniquePtr<mozilla::EventQueue, mozilla::DefaultDelete<mozilla::EventQueue> >, mozilla::UniquePtr<mozilla::EventQueue, mozilla::DefaultDelete<mozilla::EventQueue> >, mozilla::UniquePtr<mozilla::EventQueue, mozilla::DefaultDelete<mozilla::EventQueue> >, already_AddRefed<nsIIdlePeriod>)
Line
Count
Source
27
3
{
28
3
  static_assert(IsBaseOf<AbstractEventQueue, InnerQueueT>::value,
29
3
                "InnerQueueT must be an AbstractEventQueue subclass");
30
3
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::PrioritizedEventQueue(mozilla::UniquePtr<mozilla::LabeledEventQueue, mozilla::DefaultDelete<mozilla::LabeledEventQueue> >, mozilla::UniquePtr<mozilla::LabeledEventQueue, mozilla::DefaultDelete<mozilla::LabeledEventQueue> >, mozilla::UniquePtr<mozilla::LabeledEventQueue, mozilla::DefaultDelete<mozilla::LabeledEventQueue> >, mozilla::UniquePtr<mozilla::LabeledEventQueue, mozilla::DefaultDelete<mozilla::LabeledEventQueue> >, already_AddRefed<nsIIdlePeriod>)
31
32
template<class InnerQueueT>
33
void
34
PrioritizedEventQueue<InnerQueueT>::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
35
                                             EventPriority aPriority,
36
                                             const MutexAutoLock& aProofOfLock)
37
163
{
38
163
  // Double check the priority with a QI.
39
163
  RefPtr<nsIRunnable> event(aEvent);
40
163
  EventPriority priority = aPriority;
41
163
42
163
  if (priority == EventPriority::Input && mInputQueueState == STATE_DISABLED) {
43
0
    priority = EventPriority::Normal;
44
0
  }
45
163
46
163
  switch (priority) {
47
163
  case EventPriority::High:
48
0
    mHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
49
0
    break;
50
163
  case EventPriority::Input:
51
0
    mInputQueue->PutEvent(event.forget(), priority, aProofOfLock);
52
0
    break;
53
163
  case EventPriority::Normal:
54
120
    mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock);
55
120
    break;
56
163
  case EventPriority::Idle:
57
43
    mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
58
43
    break;
59
163
  case EventPriority::Count:
60
0
    MOZ_CRASH("EventPriority::Count isn't a valid priority");
61
0
    break;
62
163
  }
63
163
}
mozilla::PrioritizedEventQueue<mozilla::EventQueue>::PutEvent(already_AddRefed<nsIRunnable>&&, mozilla::EventPriority, mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Line
Count
Source
37
163
{
38
163
  // Double check the priority with a QI.
39
163
  RefPtr<nsIRunnable> event(aEvent);
40
163
  EventPriority priority = aPriority;
41
163
42
163
  if (priority == EventPriority::Input && mInputQueueState == STATE_DISABLED) {
43
0
    priority = EventPriority::Normal;
44
0
  }
45
163
46
163
  switch (priority) {
47
163
  case EventPriority::High:
48
0
    mHighQueue->PutEvent(event.forget(), priority, aProofOfLock);
49
0
    break;
50
163
  case EventPriority::Input:
51
0
    mInputQueue->PutEvent(event.forget(), priority, aProofOfLock);
52
0
    break;
53
163
  case EventPriority::Normal:
54
120
    mNormalQueue->PutEvent(event.forget(), priority, aProofOfLock);
55
120
    break;
56
163
  case EventPriority::Idle:
57
43
    mIdleQueue->PutEvent(event.forget(), priority, aProofOfLock);
58
43
    break;
59
163
  case EventPriority::Count:
60
0
    MOZ_CRASH("EventPriority::Count isn't a valid priority");
61
0
    break;
62
163
  }
63
163
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::PutEvent(already_AddRefed<nsIRunnable>&&, mozilla::EventPriority, mozilla::BaseAutoLock<mozilla::Mutex&> const&)
64
65
template<class InnerQueueT>
66
TimeStamp
67
PrioritizedEventQueue<InnerQueueT>::GetIdleDeadline()
68
0
{
69
0
  // If we are shutting down, we won't honor the idle period, and we will
70
0
  // always process idle runnables.  This will ensure that the idle queue
71
0
  // gets exhausted at shutdown time to prevent intermittently leaking
72
0
  // some runnables inside that queue and even worse potentially leaving
73
0
  // some important cleanup work unfinished.
74
0
  if (gXPCOMThreadsShutDown || nsThreadManager::get().GetCurrentThread()->ShuttingDown()) {
75
0
    return TimeStamp::Now();
76
0
  }
77
0
78
0
  TimeStamp idleDeadline;
79
0
  {
80
0
    // Releasing the lock temporarily since getting the idle period
81
0
    // might need to lock the timer thread. Unlocking here might make
82
0
    // us receive an event on the main queue, but we've committed to
83
0
    // run an idle event anyhow.
84
0
    MutexAutoUnlock unlock(*mMutex);
85
0
    mIdlePeriod->GetIdlePeriodHint(&idleDeadline);
86
0
  }
87
0
88
0
  // If HasPendingEvents() has been called and it has returned true because of
89
0
  // pending idle events, there is a risk that we may decide here that we aren't
90
0
  // idle and return null, in which case HasPendingEvents() has effectively
91
0
  // lied.  Since we can't go back and fix the past, we have to adjust what we
92
0
  // do here and forcefully pick the idle queue task here.  Note that this means
93
0
  // that we are choosing to run a task from the idle queue when we would
94
0
  // normally decide that we aren't in an idle period, but this can only happen
95
0
  // if we fall out of the idle period in between the call to HasPendingEvents()
96
0
  // and here, which should hopefully be quite rare.  We are effectively
97
0
  // choosing to prioritize the sanity of our API semantics over the optimal
98
0
  // scheduling.
99
0
  if (!mHasPendingEventsPromisedIdleEvent &&
100
0
      (!idleDeadline || idleDeadline < TimeStamp::Now())) {
101
0
    return TimeStamp();
102
0
  }
103
0
  if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) {
104
0
    // If HasPendingEvents() has been called and it has returned true, but we're no
105
0
    // longer in the idle period, we must return a valid timestamp to pretend that
106
0
    // we are still in the idle period.
107
0
    return TimeStamp::Now();
108
0
  }
109
0
  return idleDeadline;
110
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::GetIdleDeadline()
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::GetIdleDeadline()
111
112
template<class InnerQueueT>
113
EventPriority
114
PrioritizedEventQueue<InnerQueueT>::SelectQueue(bool aUpdateState,
115
                                                const MutexAutoLock& aProofOfLock)
116
0
{
117
0
  bool highPending = !mHighQueue->IsEmpty(aProofOfLock);
118
0
  bool normalPending = !mNormalQueue->IsEmpty(aProofOfLock);
119
0
  size_t inputCount = mInputQueue->Count(aProofOfLock);
120
0
121
0
  if (aUpdateState &&
122
0
      mInputQueueState == STATE_ENABLED &&
123
0
      mInputHandlingStartTime.IsNull() &&
124
0
      inputCount > 0) {
125
0
    mInputHandlingStartTime =
126
0
      InputEventStatistics::Get()
127
0
      .GetInputHandlingStartTime(inputCount);
128
0
  }
129
0
130
0
  // We check the different queues in the following order. The conditions we use
131
0
  // are meant to avoid starvation and to ensure that we don't process an event
132
0
  // at the wrong time.
133
0
  //
134
0
  // HIGH: if mProcessHighPriorityQueue
135
0
  // INPUT: if inputCount > 0 && TimeStamp::Now() > mInputHandlingStartTime
136
0
  // NORMAL: if normalPending
137
0
  //
138
0
  // If we still don't have an event, then we take events from the queues
139
0
  // in the following order:
140
0
  //
141
0
  // HIGH
142
0
  // INPUT
143
0
  // IDLE: if GetIdleDeadline()
144
0
  //
145
0
  // If we don't get an event in this pass, then we return null since no events
146
0
  // are ready.
147
0
148
0
  // This variable determines which queue we will take an event from.
149
0
  EventPriority queue;
150
0
151
0
  if (mProcessHighPriorityQueue) {
152
0
    queue = EventPriority::High;
153
0
  } else if (inputCount > 0 && (mInputQueueState == STATE_FLUSHING ||
154
0
                                (mInputQueueState == STATE_ENABLED &&
155
0
                                 !mInputHandlingStartTime.IsNull() &&
156
0
                                 TimeStamp::Now() > mInputHandlingStartTime))) {
157
0
    queue = EventPriority::Input;
158
0
  } else if (normalPending) {
159
0
    MOZ_ASSERT(mInputQueueState != STATE_FLUSHING,
160
0
               "Shouldn't consume normal event when flusing input events");
161
0
    queue = EventPriority::Normal;
162
0
  } else if (highPending) {
163
0
    queue = EventPriority::High;
164
0
  } else if (inputCount > 0 && mInputQueueState != STATE_SUSPEND) {
165
0
    MOZ_ASSERT(mInputQueueState != STATE_DISABLED,
166
0
               "Shouldn't consume input events when the input queue is disabled");
167
0
    queue = EventPriority::Input;
168
0
  } else {
169
0
    // We may not actually return an idle event in this case.
170
0
    queue = EventPriority::Idle;
171
0
  }
172
0
173
0
  MOZ_ASSERT_IF(queue == EventPriority::Input,
174
0
                mInputQueueState != STATE_DISABLED && mInputQueueState != STATE_SUSPEND);
175
0
176
0
  if (aUpdateState) {
177
0
    mProcessHighPriorityQueue = highPending;
178
0
  }
179
0
180
0
  return queue;
181
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::SelectQueue(bool, mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::SelectQueue(bool, mozilla::BaseAutoLock<mozilla::Mutex&> const&)
182
183
template<class InnerQueueT>
184
already_AddRefed<nsIRunnable>
185
PrioritizedEventQueue<InnerQueueT>::GetEvent(EventPriority* aPriority,
186
                                             const MutexAutoLock& aProofOfLock)
187
0
{
188
0
  auto guard = MakeScopeExit([&] {
189
0
    mHasPendingEventsPromisedIdleEvent = false;
190
0
  });
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::GetEvent(mozilla::EventPriority*, mozilla::BaseAutoLock<mozilla::Mutex&> const&)::{lambda()#1}::operator()() const
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::GetEvent(mozilla::EventPriority*, mozilla::BaseAutoLock<mozilla::Mutex&> const&)::{lambda()#1}::operator()() const
191
0
192
0
#ifndef RELEASE_OR_BETA
193
0
  // Clear mNextIdleDeadline so that it is possible to determine that
194
0
  // we're running an idle runnable in ProcessNextEvent.
195
0
  *mNextIdleDeadline = TimeStamp();
196
0
#endif
197
0
198
0
  EventPriority queue = SelectQueue(true, aProofOfLock);
199
0
200
0
  if (aPriority) {
201
0
    *aPriority = queue;
202
0
  }
203
0
204
0
  if (queue == EventPriority::High) {
205
0
    nsCOMPtr<nsIRunnable> event = mHighQueue->GetEvent(aPriority, aProofOfLock);
206
0
    MOZ_ASSERT(event);
207
0
    mInputHandlingStartTime = TimeStamp();
208
0
    mProcessHighPriorityQueue = false;
209
0
    return event.forget();
210
0
  }
211
0
212
0
  if (queue == EventPriority::Input) {
213
0
    nsCOMPtr<nsIRunnable> event = mInputQueue->GetEvent(aPriority, aProofOfLock);
214
0
    MOZ_ASSERT(event);
215
0
    return event.forget();
216
0
  }
217
0
218
0
  if (queue == EventPriority::Normal) {
219
0
    nsCOMPtr<nsIRunnable> event = mNormalQueue->GetEvent(aPriority, aProofOfLock);
220
0
    return event.forget();
221
0
  }
222
0
223
0
  // If we get here, then all queues except idle are empty.
224
0
  MOZ_ASSERT(queue == EventPriority::Idle);
225
0
226
0
  if (mIdleQueue->IsEmpty(aProofOfLock)) {
227
0
    MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent);
228
0
    return nullptr;
229
0
  }
230
0
231
0
  TimeStamp idleDeadline = GetIdleDeadline();
232
0
  if (!idleDeadline) {
233
0
    return nullptr;
234
0
  }
235
0
236
0
  nsCOMPtr<nsIRunnable> event = mIdleQueue->GetEvent(aPriority, aProofOfLock);
237
0
  if (event) {
238
0
    nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event);
239
0
    if (idleEvent) {
240
0
      idleEvent->SetDeadline(idleDeadline);
241
0
    }
242
0
243
0
#ifndef RELEASE_OR_BETA
244
0
    // Store the next idle deadline to be able to determine budget use
245
0
    // in ProcessNextEvent.
246
0
    *mNextIdleDeadline = idleDeadline;
247
0
#endif
248
0
  }
249
0
250
0
  return event.forget();
251
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::GetEvent(mozilla::EventPriority*, mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::GetEvent(mozilla::EventPriority*, mozilla::BaseAutoLock<mozilla::Mutex&> const&)
252
253
template<class InnerQueueT>
254
bool
255
PrioritizedEventQueue<InnerQueueT>::IsEmpty(const MutexAutoLock& aProofOfLock)
256
0
{
257
0
  // Just check IsEmpty() on the sub-queues. Don't bother checking the idle
258
0
  // deadline since that only determines whether an idle event is ready or not.
259
0
  return mHighQueue->IsEmpty(aProofOfLock)
260
0
      && mInputQueue->IsEmpty(aProofOfLock)
261
0
      && mNormalQueue->IsEmpty(aProofOfLock)
262
0
      && mIdleQueue->IsEmpty(aProofOfLock);
263
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::IsEmpty(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::IsEmpty(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
264
265
template<class InnerQueueT>
266
bool
267
PrioritizedEventQueue<InnerQueueT>::HasReadyEvent(const MutexAutoLock& aProofOfLock)
268
0
{
269
0
  mHasPendingEventsPromisedIdleEvent = false;
270
0
271
0
  EventPriority queue = SelectQueue(false, aProofOfLock);
272
0
273
0
  if (queue == EventPriority::High) {
274
0
    return mHighQueue->HasReadyEvent(aProofOfLock);
275
0
  } else if (queue == EventPriority::Input) {
276
0
    return mInputQueue->HasReadyEvent(aProofOfLock);
277
0
  } else if (queue == EventPriority::Normal) {
278
0
    return mNormalQueue->HasReadyEvent(aProofOfLock);
279
0
  }
280
0
281
0
  MOZ_ASSERT(queue == EventPriority::Idle);
282
0
283
0
  // If we get here, then both the high and normal queues are empty.
284
0
285
0
  if (mIdleQueue->IsEmpty(aProofOfLock)) {
286
0
    return false;
287
0
  }
288
0
289
0
  TimeStamp idleDeadline = GetIdleDeadline();
290
0
  if (idleDeadline && mIdleQueue->HasReadyEvent(aProofOfLock)) {
291
0
    mHasPendingEventsPromisedIdleEvent = true;
292
0
    return true;
293
0
  }
294
0
295
0
  return false;
296
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::HasReadyEvent(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::HasReadyEvent(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
297
298
template<class InnerQueueT>
299
size_t
300
PrioritizedEventQueue<InnerQueueT>::Count(const MutexAutoLock& aProofOfLock) const
301
0
{
302
0
  MOZ_CRASH("unimplemented");
303
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::Count(mozilla::BaseAutoLock<mozilla::Mutex&> const&) const
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::Count(mozilla::BaseAutoLock<mozilla::Mutex&> const&) const
304
305
template<class InnerQueueT>
306
void
307
PrioritizedEventQueue<InnerQueueT>::EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock)
308
0
{
309
0
  MOZ_ASSERT(mInputQueueState == STATE_DISABLED);
310
0
  mInputQueueState = STATE_ENABLED;
311
0
  mInputHandlingStartTime = TimeStamp();
312
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::EnableInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::EnableInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
313
314
template<class InnerQueueT>
315
void
316
PrioritizedEventQueue<InnerQueueT>::
317
FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock)
318
0
{
319
0
  MOZ_ASSERT(mInputQueueState == STATE_ENABLED || mInputQueueState == STATE_SUSPEND);
320
0
  mInputQueueState =
321
0
    mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND;
322
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::FlushInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::FlushInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
323
324
template<class InnerQueueT>
325
void
326
PrioritizedEventQueue<InnerQueueT>::
327
SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock)
328
0
{
329
0
  MOZ_ASSERT(mInputQueueState == STATE_ENABLED || mInputQueueState == STATE_FLUSHING);
330
0
  mInputQueueState = STATE_SUSPEND;
331
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::SuspendInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::SuspendInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
332
333
template<class InnerQueueT>
334
void
335
PrioritizedEventQueue<InnerQueueT>::
336
ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock)
337
0
{
338
0
  MOZ_ASSERT(mInputQueueState == STATE_SUSPEND);
339
0
  mInputQueueState = STATE_ENABLED;
340
0
}
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::EventQueue>::ResumeInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
Unexecuted instantiation: mozilla::PrioritizedEventQueue<mozilla::LabeledEventQueue>::ResumeInputEventPrioritization(mozilla::BaseAutoLock<mozilla::Mutex&> const&)
341
342
namespace mozilla {
343
template class PrioritizedEventQueue<EventQueue>;
344
template class PrioritizedEventQueue<LabeledEventQueue>;
345
}