Line data Source code
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 : class TaskQueue : public AbstractThread
49 : {
50 : class EventTargetWrapper;
51 :
52 : public:
53 : explicit TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
54 : bool aSupportsTailDispatch = false);
55 :
56 : TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
57 : const char* aName,
58 : bool aSupportsTailDispatch = false);
59 :
60 : TaskDispatcher& TailDispatcher() override;
61 :
62 0 : TaskQueue* AsTaskQueue() override { return this; }
63 :
64 0 : void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
65 : DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
66 : DispatchReason aReason = NormalDispatch) override
67 : {
68 0 : nsCOMPtr<nsIRunnable> r = aRunnable;
69 : {
70 0 : MonitorAutoLock mon(mQueueMonitor);
71 0 : nsresult rv = DispatchLocked(/* passed by ref */r, aFailureHandling, aReason);
72 : #if defined(DEBUG) || !defined(RELEASE_OR_BETA) || defined(EARLY_BETA_OR_EARLIER)
73 0 : if (NS_FAILED(rv) && aFailureHandling == AssertDispatchSuccess) {
74 0 : MOZ_CRASH_UNSAFE_PRINTF("%s: Dispatch failed. rv=%x", mName, uint32_t(rv));
75 : }
76 : #endif
77 : Unused << rv;
78 : }
79 : // If the ownership of |r| is not transferred in DispatchLocked() due to
80 : // dispatch failure, it will be deleted here outside the lock. We do so
81 : // since the destructor of the runnable might access TaskQueue and result
82 : // in deadlocks.
83 0 : }
84 :
85 : // Prevent a GCC warning about the other overload of Dispatch being hidden.
86 : using AbstractThread::Dispatch;
87 :
88 : // Puts the queue in a shutdown state and returns immediately. The queue will
89 : // remain alive at least until all the events are drained, because the Runners
90 : // hold a strong reference to the task queue, and one of them is always held
91 : // by the target event queue when the task queue is non-empty.
92 : //
93 : // The returned promise is resolved when the queue goes empty.
94 : RefPtr<ShutdownPromise> BeginShutdown();
95 :
96 : // Blocks until all task finish executing.
97 : void AwaitIdle();
98 :
99 : // Blocks until the queue is flagged for shutdown and all tasks have finished
100 : // executing.
101 : void AwaitShutdownAndIdle();
102 :
103 : bool IsEmpty();
104 : uint32_t ImpreciseLengthForHeuristics();
105 :
106 : // Returns true if the current thread is currently running a Runnable in
107 : // the task queue.
108 : bool IsCurrentThreadIn() override;
109 :
110 : // Create a new nsIEventTarget wrapper object that dispatches to this
111 : // TaskQueue.
112 : already_AddRefed<nsISerialEventTarget> WrapAsEventTarget();
113 :
114 : protected:
115 : virtual ~TaskQueue();
116 :
117 :
118 : // Blocks until all task finish executing. Called internally by methods
119 : // that need to wait until the task queue is idle.
120 : // mQueueMonitor must be held.
121 : void AwaitIdleLocked();
122 :
123 : nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
124 : DispatchFailureHandling aFailureHandling,
125 : DispatchReason aReason = NormalDispatch);
126 :
127 0 : void MaybeResolveShutdown()
128 : {
129 0 : mQueueMonitor.AssertCurrentThreadOwns();
130 0 : if (mIsShutdown && !mIsRunning) {
131 0 : mShutdownPromise.ResolveIfExists(true, __func__);
132 0 : mTarget = nullptr;
133 : }
134 0 : }
135 :
136 : nsCOMPtr<nsIEventTarget> mTarget;
137 :
138 : // Monitor that protects the queue and mIsRunning;
139 : Monitor mQueueMonitor;
140 :
141 : // Queue of tasks to run.
142 : std::queue<nsCOMPtr<nsIRunnable>> mTasks;
143 :
144 : // The thread currently running the task queue. We store a reference
145 : // to this so that IsCurrentThreadIn() can tell if the current thread
146 : // is the thread currently running in the task queue.
147 : //
148 : // This may be read on any thread, but may only be written on mRunningThread.
149 : // The thread can't die while we're running in it, and we only use it for
150 : // pointer-comparison with the current thread anyway - so we make it atomic
151 : // and don't refcount it.
152 : Atomic<PRThread*> mRunningThread;
153 :
154 : // RAII class that gets instantiated for each dispatched task.
155 : class AutoTaskGuard : public AutoTaskDispatcher
156 : {
157 : public:
158 0 : explicit AutoTaskGuard(TaskQueue* aQueue)
159 0 : : AutoTaskDispatcher(/* aIsTailDispatcher = */ true), mQueue(aQueue)
160 0 : , mLastCurrentThread(nullptr)
161 : {
162 : // NB: We don't hold the lock to aQueue here. Don't do anything that
163 : // might require it.
164 0 : MOZ_ASSERT(!mQueue->mTailDispatcher);
165 0 : mQueue->mTailDispatcher = this;
166 :
167 0 : mLastCurrentThread = sCurrentThreadTLS.get();
168 0 : sCurrentThreadTLS.set(aQueue);
169 :
170 0 : MOZ_ASSERT(mQueue->mRunningThread == nullptr);
171 0 : mQueue->mRunningThread = GetCurrentPhysicalThread();
172 0 : }
173 :
174 0 : ~AutoTaskGuard()
175 0 : {
176 0 : DrainDirectTasks();
177 :
178 0 : MOZ_ASSERT(mQueue->mRunningThread == GetCurrentPhysicalThread());
179 0 : mQueue->mRunningThread = nullptr;
180 :
181 0 : sCurrentThreadTLS.set(mLastCurrentThread);
182 0 : mQueue->mTailDispatcher = nullptr;
183 0 : }
184 :
185 : private:
186 : TaskQueue* mQueue;
187 : AbstractThread* mLastCurrentThread;
188 : };
189 :
190 : TaskDispatcher* mTailDispatcher;
191 :
192 : // True if we've dispatched an event to the target to execute events from
193 : // the queue.
194 : bool mIsRunning;
195 :
196 : // True if we've started our shutdown process.
197 : bool mIsShutdown;
198 : MozPromiseHolder<ShutdownPromise> mShutdownPromise;
199 :
200 : // The name of this TaskQueue. Useful when debugging dispatch failures.
201 : const char* const mName;
202 :
203 0 : class Runner : public Runnable {
204 : public:
205 0 : explicit Runner(TaskQueue* aQueue)
206 0 : : Runnable("TaskQueue::Runner")
207 0 : , mQueue(aQueue)
208 : {
209 0 : }
210 : NS_IMETHOD Run() override;
211 : private:
212 : RefPtr<TaskQueue> mQueue;
213 : };
214 : };
215 :
216 : } // namespace mozilla
217 :
218 : #endif // TaskQueue_h_
|