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