Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/threads/ThrottledEventQueue.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 "ThrottledEventQueue.h"
8
9
#include "mozilla/Atomics.h"
10
#include "mozilla/ClearOnShutdown.h"
11
#include "mozilla/EventQueue.h"
12
#include "mozilla/Mutex.h"
13
#include "mozilla/Services.h"
14
#include "mozilla/Unused.h"
15
#include "nsThreadUtils.h"
16
17
namespace mozilla {
18
19
namespace {
20
21
} // anonymous namespace
22
23
// The ThrottledEventQueue is designed with inner and outer objects:
24
//
25
//       XPCOM code     base event target
26
//            |               |
27
//            v               v
28
//        +-------+       +--------+
29
//        | Outer |   +-->|executor|
30
//        +-------+   |   +--------+
31
//            |       |       |
32
//            |   +-------+   |
33
//            +-->| Inner |<--+
34
//                +-------+
35
//
36
// Client code references the outer nsIEventTarget which in turn references
37
// an inner object, which actually holds the queue of runnables.
38
//
39
// Whenever the queue is non-empty (and not paused), it keeps an "executor"
40
// runnable dispatched to the base event target. Each time the executor is run,
41
// it draws the next event from Inner's queue and runs it. If that queue has
42
// more events, the executor is dispatched to the base again.
43
//
44
// The executor holds a strong reference to the Inner object. This means that if
45
// the outer object is dereferenced and destroyed, the Inner object will remain
46
// live for as long as the executor exists - that is, until the Inner's queue is
47
// empty.
48
//
49
// The xpcom shutdown process drains the main thread's event queue several
50
// times, so if a ThrottledEventQueue is being driven by the main thread, it
51
// should get emptied out by the time we reach the "eventq shutdown" phase.
52
class ThrottledEventQueue::Inner final : public nsISupports
53
{
54
  // The runnable which is dispatched to the underlying base target.  Since
55
  // we only execute one event at a time we just re-use a single instance
56
  // of this class while there are events left in the queue.
57
  class Executor final : public Runnable
58
  {
59
    // The Inner whose runnables we execute. mInner->mExecutor points
60
    // to this executor, forming a reference loop.
61
    RefPtr<Inner> mInner;
62
63
  public:
64
    explicit Executor(Inner* aInner)
65
      : Runnable("ThrottledEventQueue::Inner::Executor")
66
      , mInner(aInner)
67
0
    { }
68
69
    NS_IMETHODIMP
70
    Run() override
71
0
    {
72
0
      mInner->ExecuteRunnable();
73
0
      return NS_OK;
74
0
    }
75
76
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
77
    NS_IMETHODIMP
78
    GetName(nsACString& aName) override
79
0
    {
80
0
      return mInner->CurrentName(aName);
81
0
    }
82
#endif
83
  };
84
85
  mutable Mutex mMutex;
86
  mutable CondVar mIdleCondVar;
87
88
  // As-of-yet unexecuted runnables queued on this ThrottledEventQueue.
89
  // (Used from any thread, protected by mMutex.)
90
  EventQueue mEventQueue;
91
92
  // The event target we dispatch our events (actually, just our Executor) to.
93
  // (Written during construction on main thread; read by any thread.)
94
  nsCOMPtr<nsISerialEventTarget> mBaseTarget;
95
96
  // The Executor that we dispatch to mBaseTarget to draw runnables from our
97
  // queue. mExecutor->mInner points to this Inner, forming a reference loop.
98
  // (Used from any thread, protected by mMutex.)
99
  nsCOMPtr<nsIRunnable> mExecutor;
100
101
  explicit Inner(nsISerialEventTarget* aBaseTarget)
102
    : mMutex("ThrottledEventQueue")
103
    , mIdleCondVar(mMutex, "ThrottledEventQueue:Idle")
104
    , mBaseTarget(aBaseTarget)
105
0
  {
106
0
  }
107
108
  ~Inner()
109
0
  {
110
#ifdef DEBUG
111
    MutexAutoLock lock(mMutex);
112
    MOZ_ASSERT(!mExecutor);
113
    MOZ_ASSERT(mEventQueue.IsEmpty(lock));
114
#endif
115
  }
116
117
  nsresult
118
  CurrentName(nsACString& aName)
119
0
  {
120
0
    nsCOMPtr<nsIRunnable> event;
121
0
122
#ifdef DEBUG
123
    bool currentThread = false;
124
    mBaseTarget->IsOnCurrentThread(&currentThread);
125
    MOZ_ASSERT(currentThread);
126
#endif
127
128
0
    {
129
0
      MutexAutoLock lock(mMutex);
130
0
131
0
      // We only check the name of an executor runnable when we know there is something
132
0
      // in the queue, so this should never fail.
133
0
      event = mEventQueue.PeekEvent(lock);
134
0
      MOZ_ALWAYS_TRUE(event);
135
0
    }
136
0
137
0
    if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) {
138
0
      nsresult rv = named->GetName(aName);
139
0
      return rv;
140
0
    }
141
0
142
0
    aName.AssignLiteral("non-nsINamed ThrottledEventQueue runnable");
143
0
    return NS_OK;
144
0
  }
145
146
  void
147
  ExecuteRunnable()
148
0
  {
149
0
    // Any thread
150
0
    nsCOMPtr<nsIRunnable> event;
151
0
152
#ifdef DEBUG
153
    bool currentThread = false;
154
    mBaseTarget->IsOnCurrentThread(&currentThread);
155
    MOZ_ASSERT(currentThread);
156
#endif
157
158
0
    {
159
0
      MutexAutoLock lock(mMutex);
160
0
161
0
      // We only dispatch an executor runnable when we know there is something
162
0
      // in the queue, so this should never fail.
163
0
      event = mEventQueue.GetEvent(nullptr, lock);
164
0
      MOZ_ASSERT(event);
165
0
166
0
      // If there are more events in the queue, then dispatch the next
167
0
      // executor.  We do this now, before running the event, because
168
0
      // the event might spin the event loop and we don't want to stall
169
0
      // the queue.
170
0
      if (mEventQueue.HasReadyEvent(lock)) {
171
0
        // Dispatch the next base target runnable to attempt to execute
172
0
        // the next throttled event.  We must do this before executing
173
0
        // the event in case the event spins the event loop.
174
0
        MOZ_ALWAYS_SUCCEEDS(
175
0
          mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL));
176
0
      }
177
0
178
0
      // Otherwise the queue is empty and we can stop dispatching the
179
0
      // executor.
180
0
      else {
181
0
        // Break the Executor::mInner / Inner::mExecutor reference loop.
182
0
        mExecutor = nullptr;
183
0
        mIdleCondVar.NotifyAll();
184
0
      }
185
0
    }
186
0
187
0
    // Execute the event now that we have unlocked.
188
0
    Unused << event->Run();
189
0
  }
190
191
public:
192
  static already_AddRefed<Inner>
193
  Create(nsISerialEventTarget* aBaseTarget)
194
0
  {
195
0
    MOZ_ASSERT(NS_IsMainThread());
196
0
    MOZ_ASSERT(ClearOnShutdown_Internal::sCurrentShutdownPhase == ShutdownPhase::NotInShutdown);
197
0
198
0
    RefPtr<Inner> ref = new Inner(aBaseTarget);
199
0
    return ref.forget();
200
0
  }
201
202
  bool
203
  IsEmpty() const
204
0
  {
205
0
    // Any thread
206
0
    return Length() == 0;
207
0
  }
208
209
  uint32_t
210
  Length() const
211
0
  {
212
0
    // Any thread
213
0
    MutexAutoLock lock(mMutex);
214
0
    return mEventQueue.Count(lock);
215
0
  }
216
217
  void
218
  AwaitIdle() const
219
0
  {
220
0
    // Any thread, except the main thread or our base target.  Blocking the
221
0
    // main thread is forbidden.  Blocking the base target is guaranteed to
222
0
    // produce a deadlock.
223
0
    MOZ_ASSERT(!NS_IsMainThread());
224
#ifdef DEBUG
225
    bool onBaseTarget = false;
226
    Unused << mBaseTarget->IsOnCurrentThread(&onBaseTarget);
227
    MOZ_ASSERT(!onBaseTarget);
228
#endif
229
230
0
    MutexAutoLock lock(mMutex);
231
0
    while (mExecutor) {
232
0
      mIdleCondVar.Wait();
233
0
    }
234
0
  }
235
236
  nsresult
237
  DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
238
0
  {
239
0
    // Any thread
240
0
    nsCOMPtr<nsIRunnable> r = aEvent;
241
0
    return Dispatch(r.forget(), aFlags);
242
0
  }
243
244
  nsresult
245
  Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
246
0
  {
247
0
    MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL ||
248
0
               aFlags == NS_DISPATCH_AT_END);
249
0
250
0
    // Any thread
251
0
    MutexAutoLock lock(mMutex);
252
0
253
0
    // We are not currently processing events, so we must start
254
0
    // operating on our base target.  This is fallible, so do
255
0
    // it first.  Our lock will prevent the executor from accessing
256
0
    // the event queue before we add the event below.
257
0
    if (!mExecutor) {
258
0
      // Note, this creates a ref cycle keeping the inner alive
259
0
      // until the queue is drained.
260
0
      mExecutor = new Executor(this);
261
0
      nsresult rv = mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL);
262
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
263
0
        mExecutor = nullptr;
264
0
        return rv;
265
0
      }
266
0
    }
267
0
268
0
    // Only add the event to the underlying queue if are able to
269
0
    // dispatch to our base target.
270
0
    mEventQueue.PutEvent(std::move(aEvent), EventPriority::Normal, lock);
271
0
    return NS_OK;
272
0
  }
273
274
  nsresult
275
  DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelay)
276
0
  {
277
0
    // The base target may implement this, but we don't.  Always fail
278
0
    // to provide consistent behavior.
279
0
    return NS_ERROR_NOT_IMPLEMENTED;
280
0
  }
281
282
  bool
283
  IsOnCurrentThread()
284
0
  {
285
0
    return mBaseTarget->IsOnCurrentThread();
286
0
  }
287
288
  NS_DECL_THREADSAFE_ISUPPORTS
289
};
290
291
NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsISupports);
292
293
NS_IMPL_ISUPPORTS(ThrottledEventQueue,
294
                  ThrottledEventQueue,
295
                  nsIEventTarget,
296
                  nsISerialEventTarget);
297
298
ThrottledEventQueue::ThrottledEventQueue(already_AddRefed<Inner> aInner)
299
  : mInner(aInner)
300
0
{
301
0
  MOZ_ASSERT(mInner);
302
0
}
303
304
already_AddRefed<ThrottledEventQueue>
305
ThrottledEventQueue::Create(nsISerialEventTarget* aBaseTarget)
306
0
{
307
0
  MOZ_ASSERT(NS_IsMainThread());
308
0
  MOZ_ASSERT(aBaseTarget);
309
0
310
0
  RefPtr<Inner> inner = Inner::Create(aBaseTarget);
311
0
  if (NS_WARN_IF(!inner)) {
312
0
    return nullptr;
313
0
  }
314
0
315
0
  RefPtr<ThrottledEventQueue> ref =
316
0
    new ThrottledEventQueue(inner.forget());
317
0
  return ref.forget();
318
0
}
319
320
bool
321
ThrottledEventQueue::IsEmpty() const
322
0
{
323
0
  return mInner->IsEmpty();
324
0
}
325
326
uint32_t
327
ThrottledEventQueue::Length() const
328
0
{
329
0
  return mInner->Length();
330
0
}
331
332
void
333
ThrottledEventQueue::AwaitIdle() const
334
0
{
335
0
  return mInner->AwaitIdle();
336
0
}
337
338
NS_IMETHODIMP
339
ThrottledEventQueue::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
340
0
{
341
0
  return mInner->DispatchFromScript(aEvent, aFlags);
342
0
}
343
344
NS_IMETHODIMP
345
ThrottledEventQueue::Dispatch(already_AddRefed<nsIRunnable> aEvent,
346
                                     uint32_t aFlags)
347
0
{
348
0
  return mInner->Dispatch(std::move(aEvent), aFlags);
349
0
}
350
351
NS_IMETHODIMP
352
ThrottledEventQueue::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
353
                                            uint32_t aFlags)
354
0
{
355
0
  return mInner->DelayedDispatch(std::move(aEvent), aFlags);
356
0
}
357
358
NS_IMETHODIMP
359
ThrottledEventQueue::IsOnCurrentThread(bool* aResult)
360
0
{
361
0
  *aResult = mInner->IsOnCurrentThread();
362
0
  return NS_OK;
363
0
}
364
365
NS_IMETHODIMP_(bool)
366
ThrottledEventQueue::IsOnCurrentThreadInfallible()
367
0
{
368
0
  return mInner->IsOnCurrentThread();
369
0
}
370
371
} // namespace mozilla