Source code

Revision control

Other Tools

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 "mozilla/TaskQueue.h"
8
9
#include "nsISerialEventTarget.h"
10
#include "nsThreadUtils.h"
11
12
namespace mozilla {
13
14
class TaskQueue::EventTargetWrapper final : public nsISerialEventTarget {
15
RefPtr<TaskQueue> mTaskQueue;
16
17
~EventTargetWrapper() = default;
18
19
public:
20
explicit EventTargetWrapper(TaskQueue* aTaskQueue) : mTaskQueue(aTaskQueue) {
21
MOZ_ASSERT(mTaskQueue);
22
}
23
24
NS_IMETHOD
25
DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) override {
26
nsCOMPtr<nsIRunnable> ref = aEvent;
27
return Dispatch(ref.forget(), aFlags);
28
}
29
30
NS_IMETHOD
31
Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override {
32
nsCOMPtr<nsIRunnable> runnable = aEvent;
33
MonitorAutoLock mon(mTaskQueue->mQueueMonitor);
34
return mTaskQueue->DispatchLocked(/* passed by ref */ runnable, aFlags,
35
NormalDispatch);
36
}
37
38
NS_IMETHOD
39
DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t aFlags) override {
40
return NS_ERROR_NOT_IMPLEMENTED;
41
}
42
43
NS_IMETHOD
44
IsOnCurrentThread(bool* aResult) override {
45
*aResult = mTaskQueue->IsCurrentThreadIn();
46
return NS_OK;
47
}
48
49
NS_IMETHOD_(bool)
50
IsOnCurrentThreadInfallible() override {
51
return mTaskQueue->mTarget->IsOnCurrentThread();
52
}
53
54
NS_DECL_THREADSAFE_ISUPPORTS
55
};
56
57
NS_IMPL_ISUPPORTS(TaskQueue::EventTargetWrapper, nsIEventTarget,
58
nsISerialEventTarget)
59
60
TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
61
const char* aName, bool aRequireTailDispatch,
62
bool aRetainFlags)
63
: AbstractThread(aRequireTailDispatch),
64
mTarget(aTarget),
65
mQueueMonitor("TaskQueue::Queue"),
66
mTailDispatcher(nullptr),
67
mShouldRetainFlags(aRetainFlags),
68
mIsRunning(false),
69
mIsShutdown(false),
70
mName(aName) {}
71
72
TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
73
bool aSupportsTailDispatch, bool aRetainFlags)
74
: TaskQueue(std::move(aTarget), "Unnamed", aSupportsTailDispatch) {}
75
76
TaskQueue::~TaskQueue() {
77
// No one is referencing this TaskQueue anymore, meaning no tasks can be
78
// pending as all Runner hold a reference to this TaskQueue.
79
}
80
81
TaskDispatcher& TaskQueue::TailDispatcher() {
82
MOZ_ASSERT(IsCurrentThreadIn());
83
MOZ_ASSERT(mTailDispatcher);
84
return *mTailDispatcher;
85
}
86
87
// Note aRunnable is passed by ref to support conditional ownership transfer.
88
// See Dispatch() in TaskQueue.h for more details.
89
nsresult TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
90
uint32_t aFlags, DispatchReason aReason) {
91
mQueueMonitor.AssertCurrentThreadOwns();
92
if (mIsShutdown) {
93
return NS_ERROR_FAILURE;
94
}
95
96
AbstractThread* currentThread;
97
if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
98
RequiresTailDispatch(currentThread)) {
99
MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL,
100
"Tail dispatch doesn't support flags");
101
return currentThread->TailDispatcher().AddTask(this, aRunnable.forget());
102
}
103
104
// If the task queue cares about individual flags, retain them in the struct.
105
uint32_t retainFlags = mShouldRetainFlags ? aFlags : 0;
106
107
mTasks.push({std::move(aRunnable), retainFlags});
108
109
if (mIsRunning) {
110
return NS_OK;
111
}
112
RefPtr<nsIRunnable> runner(new Runner(this));
113
nsresult rv = mTarget->Dispatch(runner.forget(), aFlags);
114
if (NS_FAILED(rv)) {
115
NS_WARNING("Failed to dispatch runnable to run TaskQueue");
116
return rv;
117
}
118
mIsRunning = true;
119
120
return NS_OK;
121
}
122
123
void TaskQueue::AwaitIdle() {
124
MonitorAutoLock mon(mQueueMonitor);
125
AwaitIdleLocked();
126
}
127
128
void TaskQueue::AwaitIdleLocked() {
129
// Make sure there are no tasks for this queue waiting in the caller's tail
130
// dispatcher.
131
MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
132
!AbstractThread::GetCurrent()->HasTailTasksFor(this));
133
134
mQueueMonitor.AssertCurrentThreadOwns();
135
MOZ_ASSERT(mIsRunning || mTasks.empty());
136
while (mIsRunning) {
137
mQueueMonitor.Wait();
138
}
139
}
140
141
void TaskQueue::AwaitShutdownAndIdle() {
142
MOZ_ASSERT(!IsCurrentThreadIn());
143
// Make sure there are no tasks for this queue waiting in the caller's tail
144
// dispatcher.
145
MOZ_ASSERT_IF(AbstractThread::GetCurrent(),
146
!AbstractThread::GetCurrent()->HasTailTasksFor(this));
147
148
MonitorAutoLock mon(mQueueMonitor);
149
while (!mIsShutdown) {
150
mQueueMonitor.Wait();
151
}
152
AwaitIdleLocked();
153
}
154
155
RefPtr<ShutdownPromise> TaskQueue::BeginShutdown() {
156
// Dispatch any tasks for this queue waiting in the caller's tail dispatcher,
157
// since this is the last opportunity to do so.
158
if (AbstractThread* currentThread = AbstractThread::GetCurrent()) {
159
currentThread->TailDispatchTasksFor(this);
160
}
161
MonitorAutoLock mon(mQueueMonitor);
162
mIsShutdown = true;
163
RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
164
MaybeResolveShutdown();
165
mon.NotifyAll();
166
return p;
167
}
168
169
bool TaskQueue::IsEmpty() {
170
MonitorAutoLock mon(mQueueMonitor);
171
return mTasks.empty();
172
}
173
174
bool TaskQueue::IsCurrentThreadIn() const {
175
bool in = mRunningThread == PR_GetCurrentThread();
176
return in;
177
}
178
179
already_AddRefed<nsISerialEventTarget> TaskQueue::WrapAsEventTarget() {
180
nsCOMPtr<nsISerialEventTarget> ref = new EventTargetWrapper(this);
181
return ref.forget();
182
}
183
184
nsresult TaskQueue::Runner::Run() {
185
TaskStruct event;
186
{
187
MonitorAutoLock mon(mQueue->mQueueMonitor);
188
MOZ_ASSERT(mQueue->mIsRunning);
189
if (mQueue->mTasks.empty()) {
190
mQueue->mIsRunning = false;
191
mQueue->MaybeResolveShutdown();
192
mon.NotifyAll();
193
return NS_OK;
194
}
195
event = std::move(mQueue->mTasks.front());
196
mQueue->mTasks.pop();
197
}
198
MOZ_ASSERT(event.event);
199
200
// Note that dropping the queue monitor before running the task, and
201
// taking the monitor again after the task has run ensures we have memory
202
// fences enforced. This means that if the object we're calling wasn't
203
// designed to be threadsafe, it will be, provided we're only calling it
204
// in this task queue.
205
{
206
AutoTaskGuard g(mQueue);
207
event.event->Run();
208
}
209
210
// Drop the reference to event. The event will hold a reference to the
211
// object it's calling, and we don't want to keep it alive, it may be
212
// making assumptions what holds references to it. This is especially
213
// the case if the object is waiting for us to shutdown, so that it
214
// can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
215
event.event = nullptr;
216
217
{
218
MonitorAutoLock mon(mQueue->mQueueMonitor);
219
if (mQueue->mTasks.empty()) {
220
// No more events to run. Exit the task runner.
221
mQueue->mIsRunning = false;
222
mQueue->MaybeResolveShutdown();
223
mon.NotifyAll();
224
return NS_OK;
225
}
226
}
227
228
// There's at least one more event that we can run. Dispatch this Runner
229
// to the target again to ensure it runs again. Note that we don't just
230
// run in a loop here so that we don't hog the target. This means we may
231
// run on another thread next time, but we rely on the memory fences from
232
// mQueueMonitor for thread safety of non-threadsafe tasks.
233
nsresult rv;
234
{
235
MonitorAutoLock mon(mQueue->mQueueMonitor);
236
rv = mQueue->mTarget->Dispatch(
237
this, mQueue->mTasks.front().flags | NS_DISPATCH_AT_END);
238
}
239
if (NS_FAILED(rv)) {
240
// Failed to dispatch, shutdown!
241
MonitorAutoLock mon(mQueue->mQueueMonitor);
242
mQueue->mIsRunning = false;
243
mQueue->mIsShutdown = true;
244
mQueue->MaybeResolveShutdown();
245
mon.NotifyAll();
246
}
247
248
return NS_OK;
249
}
250
251
} // namespace mozilla