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