Coverage Report

Created: 2018-09-25 14:53

/work/obj-fuzz/dist/include/mozilla/TaskQueue.h
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
#ifndef TaskQueue_h_
8
#define TaskQueue_h_
9
10
#include "mozilla/Monitor.h"
11
#include "mozilla/MozPromise.h"
12
#include "mozilla/RefPtr.h"
13
#include "mozilla/TaskDispatcher.h"
14
#include "mozilla/Unused.h"
15
16
#include <queue>
17
18
#include "nsThreadUtils.h"
19
20
class nsIEventTarget;
21
class nsIRunnable;
22
23
namespace mozilla {
24
25
typedef MozPromise<bool, bool, false> ShutdownPromise;
26
27
// Abstracts executing runnables in order on an arbitrary event target. The
28
// runnables dispatched to the TaskQueue will be executed in the order in which
29
// they're received, and are guaranteed to not be executed concurrently.
30
// They may be executed on different threads, and a memory barrier is used
31
// to make this threadsafe for objects that aren't already threadsafe.
32
//
33
// Note, since a TaskQueue can also be converted to an nsIEventTarget using
34
// WrapAsEventTarget() its possible to construct a hierarchy of TaskQueues.
35
// Consider these three TaskQueues:
36
//
37
//  TQ1 dispatches to the main thread
38
//  TQ2 dispatches to TQ1
39
//  TQ3 dispatches to TQ1
40
//
41
// This ensures there is only ever a single runnable from the entire chain on
42
// the main thread.  It also ensures that TQ2 and TQ3 only have a single runnable
43
// in TQ1 at any time.
44
//
45
// This arrangement lets you prioritize work by dispatching runnables directly
46
// to TQ1.  You can issue many runnables for important work.  Meanwhile the TQ2
47
// and TQ3 work will always execute at most one runnable and then yield.
48
//
49
// A TaskQueue does not require explicit shutdown, however it provides a
50
// BeginShutdown() method that places TaskQueue in a shut down state and returns
51
// a promise that gets resolved once all pending tasks have completed
52
class TaskQueue : public AbstractThread
53
{
54
  class EventTargetWrapper;
55
56
public:
57
  explicit TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
58
                     bool aSupportsTailDispatch = false);
59
60
  TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
61
            const char* aName,
62
            bool aSupportsTailDispatch = false);
63
64
  TaskDispatcher& TailDispatcher() override;
65
66
0
  TaskQueue* AsTaskQueue() override { return this; }
67
68
  MOZ_MUST_USE nsresult
69
  Dispatch(already_AddRefed<nsIRunnable> aRunnable,
70
           DispatchReason aReason = NormalDispatch) override
71
0
  {
72
0
    nsCOMPtr<nsIRunnable> r = aRunnable;
73
0
    {
74
0
      MonitorAutoLock mon(mQueueMonitor);
75
0
      return DispatchLocked(/* passed by ref */r, aReason);
76
0
    }
77
0
    // If the ownership of |r| is not transferred in DispatchLocked() due to
78
0
    // dispatch failure, it will be deleted here outside the lock. We do so
79
0
    // since the destructor of the runnable might access TaskQueue and result
80
0
    // in deadlocks.
81
0
  }
82
83
  // Prevent a GCC warning about the other overload of Dispatch being hidden.
84
  using AbstractThread::Dispatch;
85
86
  // Puts the queue in a shutdown state and returns immediately. The queue will
87
  // remain alive at least until all the events are drained, because the Runners
88
  // hold a strong reference to the task queue, and one of them is always held
89
  // by the target event queue when the task queue is non-empty.
90
  //
91
  // The returned promise is resolved when the queue goes empty.
92
  RefPtr<ShutdownPromise> BeginShutdown();
93
94
  // Blocks until all task finish executing.
95
  void AwaitIdle();
96
97
  // Blocks until the queue is flagged for shutdown and all tasks have finished
98
  // executing.
99
  void AwaitShutdownAndIdle();
100
101
  bool IsEmpty();
102
103
  // Returns true if the current thread is currently running a Runnable in
104
  // the task queue.
105
  bool IsCurrentThreadIn() override;
106
107
  // Create a new nsIEventTarget wrapper object that dispatches to this
108
  // TaskQueue.
109
  already_AddRefed<nsISerialEventTarget> WrapAsEventTarget();
110
111
protected:
112
  virtual ~TaskQueue();
113
114
115
  // Blocks until all task finish executing. Called internally by methods
116
  // that need to wait until the task queue is idle.
117
  // mQueueMonitor must be held.
118
  void AwaitIdleLocked();
119
120
  nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
121
                          DispatchReason aReason = NormalDispatch);
122
123
  void MaybeResolveShutdown()
124
0
  {
125
0
    mQueueMonitor.AssertCurrentThreadOwns();
126
0
    if (mIsShutdown && !mIsRunning) {
127
0
      mShutdownPromise.ResolveIfExists(true, __func__);
128
0
      mTarget = nullptr;
129
0
    }
130
0
  }
131
132
  nsCOMPtr<nsIEventTarget> mTarget;
133
134
  // Monitor that protects the queue and mIsRunning;
135
  Monitor mQueueMonitor;
136
137
  // Queue of tasks to run.
138
  std::queue<nsCOMPtr<nsIRunnable>> mTasks;
139
140
  // The thread currently running the task queue. We store a reference
141
  // to this so that IsCurrentThreadIn() can tell if the current thread
142
  // is the thread currently running in the task queue.
143
  //
144
  // This may be read on any thread, but may only be written on mRunningThread.
145
  // The thread can't die while we're running in it, and we only use it for
146
  // pointer-comparison with the current thread anyway - so we make it atomic
147
  // and don't refcount it.
148
  Atomic<PRThread*> mRunningThread;
149
150
  // RAII class that gets instantiated for each dispatched task.
151
  class AutoTaskGuard : public AutoTaskDispatcher
152
  {
153
  public:
154
    explicit AutoTaskGuard(TaskQueue* aQueue)
155
      : AutoTaskDispatcher(/* aIsTailDispatcher = */ true), mQueue(aQueue)
156
      , mLastCurrentThread(nullptr)
157
0
    {
158
0
      // NB: We don't hold the lock to aQueue here. Don't do anything that
159
0
      // might require it.
160
0
      MOZ_ASSERT(!mQueue->mTailDispatcher);
161
0
      mQueue->mTailDispatcher = this;
162
0
163
0
      mLastCurrentThread = sCurrentThreadTLS.get();
164
0
      sCurrentThreadTLS.set(aQueue);
165
0
166
0
      MOZ_ASSERT(mQueue->mRunningThread == nullptr);
167
0
      mQueue->mRunningThread = GetCurrentPhysicalThread();
168
0
    }
169
170
    ~AutoTaskGuard()
171
0
    {
172
0
      DrainDirectTasks();
173
0
174
0
      MOZ_ASSERT(mQueue->mRunningThread == GetCurrentPhysicalThread());
175
0
      mQueue->mRunningThread = nullptr;
176
0
177
0
      sCurrentThreadTLS.set(mLastCurrentThread);
178
0
      mQueue->mTailDispatcher = nullptr;
179
0
    }
180
181
  private:
182
  TaskQueue* mQueue;
183
  AbstractThread* mLastCurrentThread;
184
  };
185
186
  TaskDispatcher* mTailDispatcher;
187
188
  // True if we've dispatched an event to the target to execute events from
189
  // the queue.
190
  bool mIsRunning;
191
192
  // True if we've started our shutdown process.
193
  bool mIsShutdown;
194
  MozPromiseHolder<ShutdownPromise> mShutdownPromise;
195
196
  // The name of this TaskQueue. Useful when debugging dispatch failures.
197
  const char* const mName;
198
199
  class Runner : public Runnable {
200
  public:
201
    explicit Runner(TaskQueue* aQueue)
202
      : Runnable("TaskQueue::Runner")
203
      , mQueue(aQueue)
204
0
    {
205
0
    }
206
    NS_IMETHOD Run() override;
207
  private:
208
    RefPtr<TaskQueue> mQueue;
209
  };
210
};
211
212
} // namespace mozilla
213
214
#endif // TaskQueue_h_