/src/mozilla-central/xpcom/threads/SchedulerGroup.cpp
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 | | #include "mozilla/SchedulerGroup.h" |
8 | | |
9 | | #include "jsfriendapi.h" |
10 | | #include "mozilla/AbstractThread.h" |
11 | | #include "mozilla/Atomics.h" |
12 | | #include "mozilla/Move.h" |
13 | | #include "mozilla/Unused.h" |
14 | | #include "mozilla/dom/DocGroup.h" |
15 | | #include "nsINamed.h" |
16 | | #include "nsQueryObject.h" |
17 | | #include "mozilla/dom/ScriptSettings.h" |
18 | | #include "nsThreadUtils.h" |
19 | | |
20 | | #include "mozilla/Telemetry.h" |
21 | | |
22 | | using namespace mozilla; |
23 | | |
24 | | /* SchedulerEventTarget */ |
25 | | |
26 | | namespace { |
27 | | |
28 | | #define NS_DISPATCHEREVENTTARGET_IID \ |
29 | | { 0xbf4e36c8, 0x7d04, 0x4ef4, \ |
30 | | { 0xbb, 0xd8, 0x11, 0x09, 0x0a, 0xdb, 0x4d, 0xf7 } } |
31 | | |
32 | | class SchedulerEventTarget final : public nsISerialEventTarget |
33 | | { |
34 | | RefPtr<SchedulerGroup> mDispatcher; |
35 | | TaskCategory mCategory; |
36 | | |
37 | | public: |
38 | | NS_DECLARE_STATIC_IID_ACCESSOR(NS_DISPATCHEREVENTTARGET_IID) |
39 | | |
40 | | SchedulerEventTarget(SchedulerGroup* aDispatcher, TaskCategory aCategory) |
41 | | : mDispatcher(aDispatcher) |
42 | | , mCategory(aCategory) |
43 | 27 | {} |
44 | | |
45 | | NS_DECL_THREADSAFE_ISUPPORTS |
46 | | NS_DECL_NSIEVENTTARGET_FULL |
47 | | |
48 | 0 | SchedulerGroup* Dispatcher() const { return mDispatcher; } |
49 | | |
50 | | private: |
51 | 0 | ~SchedulerEventTarget() {} |
52 | | }; |
53 | | |
54 | | NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerEventTarget, NS_DISPATCHEREVENTTARGET_IID) |
55 | | |
56 | | static Atomic<uint64_t> gEarliestUnprocessedVsync(0); |
57 | | |
58 | | } // namespace |
59 | | |
60 | | NS_IMPL_ISUPPORTS(SchedulerEventTarget, |
61 | | SchedulerEventTarget, |
62 | | nsIEventTarget, |
63 | | nsISerialEventTarget) |
64 | | |
65 | | NS_IMETHODIMP |
66 | | SchedulerEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) |
67 | 0 | { |
68 | 0 | return Dispatch(do_AddRef(aRunnable), aFlags); |
69 | 0 | } |
70 | | |
71 | | NS_IMETHODIMP |
72 | | SchedulerEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) |
73 | 84 | { |
74 | 84 | if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { |
75 | 0 | return NS_ERROR_UNEXPECTED; |
76 | 0 | } |
77 | 84 | return mDispatcher->Dispatch(mCategory, std::move(aRunnable)); |
78 | 84 | } |
79 | | |
80 | | NS_IMETHODIMP |
81 | | SchedulerEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) |
82 | 0 | { |
83 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
84 | 0 | } |
85 | | |
86 | | NS_IMETHODIMP |
87 | | SchedulerEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) |
88 | 0 | { |
89 | 0 | *aIsOnCurrentThread = NS_IsMainThread(); |
90 | 0 | return NS_OK; |
91 | 0 | } |
92 | | |
93 | | NS_IMETHODIMP_(bool) |
94 | | SchedulerEventTarget::IsOnCurrentThreadInfallible() |
95 | 0 | { |
96 | 0 | return NS_IsMainThread(); |
97 | 0 | } |
98 | | |
99 | | /* static */ nsresult |
100 | | SchedulerGroup::UnlabeledDispatch(TaskCategory aCategory, |
101 | | already_AddRefed<nsIRunnable>&& aRunnable) |
102 | 102 | { |
103 | 102 | if (NS_IsMainThread()) { |
104 | 18 | return NS_DispatchToCurrentThread(std::move(aRunnable)); |
105 | 84 | } else { |
106 | 84 | return NS_DispatchToMainThread(std::move(aRunnable)); |
107 | 84 | } |
108 | 102 | } |
109 | | |
110 | | /* static */ void |
111 | | SchedulerGroup::MarkVsyncReceived() |
112 | 0 | { |
113 | 0 | if (gEarliestUnprocessedVsync) { |
114 | 0 | // If we've seen a vsync already, but haven't handled it, keep the |
115 | 0 | // older one. |
116 | 0 | return; |
117 | 0 | } |
118 | 0 | |
119 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
120 | 0 | bool inconsistent = false; |
121 | 0 | TimeStamp creation = TimeStamp::ProcessCreation(&inconsistent); |
122 | 0 | if (inconsistent) { |
123 | 0 | return; |
124 | 0 | } |
125 | 0 | |
126 | 0 | gEarliestUnprocessedVsync = (TimeStamp::Now() - creation).ToMicroseconds(); |
127 | 0 | } |
128 | | |
129 | | /* static */ void |
130 | | SchedulerGroup::MarkVsyncRan() |
131 | 0 | { |
132 | 0 | gEarliestUnprocessedVsync = 0; |
133 | 0 | } |
134 | | |
135 | | MOZ_THREAD_LOCAL(bool) SchedulerGroup::sTlsValidatingAccess; |
136 | | |
137 | | SchedulerGroup::SchedulerGroup() |
138 | | : mIsRunning(false) |
139 | 3 | { |
140 | 3 | if (NS_IsMainThread()) { |
141 | 3 | sTlsValidatingAccess.infallibleInit(); |
142 | 3 | } |
143 | 3 | } |
144 | | |
145 | | nsresult |
146 | | SchedulerGroup::DispatchWithDocGroup(TaskCategory aCategory, |
147 | | already_AddRefed<nsIRunnable>&& aRunnable, |
148 | | dom::DocGroup* aDocGroup) |
149 | 0 | { |
150 | 0 | return LabeledDispatch(aCategory, std::move(aRunnable), aDocGroup); |
151 | 0 | } |
152 | | |
153 | | nsresult |
154 | | SchedulerGroup::Dispatch(TaskCategory aCategory, |
155 | | already_AddRefed<nsIRunnable>&& aRunnable) |
156 | 102 | { |
157 | 102 | return LabeledDispatch(aCategory, std::move(aRunnable), nullptr); |
158 | 102 | } |
159 | | |
160 | | nsISerialEventTarget* |
161 | | SchedulerGroup::EventTargetFor(TaskCategory aCategory) const |
162 | 84 | { |
163 | 84 | MOZ_ASSERT(aCategory != TaskCategory::Count); |
164 | 84 | MOZ_ASSERT(mEventTargets[size_t(aCategory)]); |
165 | 84 | return mEventTargets[size_t(aCategory)]; |
166 | 84 | } |
167 | | |
168 | | AbstractThread* |
169 | | SchedulerGroup::AbstractMainThreadFor(TaskCategory aCategory) |
170 | 0 | { |
171 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
172 | 0 | return AbstractMainThreadForImpl(aCategory); |
173 | 0 | } |
174 | | |
175 | | AbstractThread* |
176 | | SchedulerGroup::AbstractMainThreadForImpl(TaskCategory aCategory) |
177 | 0 | { |
178 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
179 | 0 | MOZ_ASSERT(aCategory != TaskCategory::Count); |
180 | 0 | MOZ_ASSERT(mEventTargets[size_t(aCategory)]); |
181 | 0 |
|
182 | 0 | if (!mAbstractThreads[size_t(aCategory)]) { |
183 | 0 | mAbstractThreads[size_t(aCategory)] = |
184 | 0 | AbstractThread::CreateEventTargetWrapper(mEventTargets[size_t(aCategory)], |
185 | 0 | /* aDrainDirectTasks = */ true); |
186 | 0 | } |
187 | 0 |
|
188 | 0 | return mAbstractThreads[size_t(aCategory)]; |
189 | 0 | } |
190 | | |
191 | | void |
192 | | SchedulerGroup::CreateEventTargets(bool aNeedValidation) |
193 | 3 | { |
194 | 30 | for (size_t i = 0; i < size_t(TaskCategory::Count); i++) { |
195 | 27 | TaskCategory category = static_cast<TaskCategory>(i); |
196 | 27 | if (!aNeedValidation) { |
197 | 0 | // The chrome TabGroup dispatches directly to the main thread. This means |
198 | 0 | // that we don't have to worry about cyclical references when cleaning up |
199 | 0 | // the chrome TabGroup. |
200 | 0 | mEventTargets[i] = GetMainThreadSerialEventTarget(); |
201 | 27 | } else { |
202 | 27 | mEventTargets[i] = CreateEventTargetFor(category); |
203 | 27 | } |
204 | 27 | } |
205 | 3 | } |
206 | | |
207 | | void |
208 | | SchedulerGroup::Shutdown(bool aXPCOMShutdown) |
209 | 0 | { |
210 | 0 | // There is a RefPtr cycle TabGroup -> SchedulerEventTarget -> TabGroup. To |
211 | 0 | // avoid leaks, we need to break the chain somewhere. We shouldn't be using |
212 | 0 | // the ThrottledEventQueue for this TabGroup when no windows belong to it, |
213 | 0 | // so it's safe to null out the queue here. |
214 | 0 | for (size_t i = 0; i < size_t(TaskCategory::Count); i++) { |
215 | 0 | mEventTargets[i] = aXPCOMShutdown ? nullptr : GetMainThreadSerialEventTarget(); |
216 | 0 | mAbstractThreads[i] = nullptr; |
217 | 0 | } |
218 | 0 | } |
219 | | |
220 | | already_AddRefed<nsISerialEventTarget> |
221 | | SchedulerGroup::CreateEventTargetFor(TaskCategory aCategory) |
222 | 27 | { |
223 | 27 | RefPtr<SchedulerEventTarget> target = |
224 | 27 | new SchedulerEventTarget(this, aCategory); |
225 | 27 | return target.forget(); |
226 | 27 | } |
227 | | |
228 | | /* static */ SchedulerGroup* |
229 | | SchedulerGroup::FromEventTarget(nsIEventTarget* aEventTarget) |
230 | 0 | { |
231 | 0 | RefPtr<SchedulerEventTarget> target = do_QueryObject(aEventTarget); |
232 | 0 | if (!target) { |
233 | 0 | return nullptr; |
234 | 0 | } |
235 | 0 | return target->Dispatcher(); |
236 | 0 | } |
237 | | |
238 | | nsresult |
239 | | SchedulerGroup::LabeledDispatch(TaskCategory aCategory, |
240 | | already_AddRefed<nsIRunnable>&& aRunnable, |
241 | | dom::DocGroup* aDocGroup) |
242 | 102 | { |
243 | 102 | nsCOMPtr<nsIRunnable> runnable(aRunnable); |
244 | 102 | if (XRE_IsContentProcess()) { |
245 | 0 | RefPtr<Runnable> internalRunnable = new Runnable(runnable.forget(), this, aDocGroup); |
246 | 0 | return InternalUnlabeledDispatch(aCategory, internalRunnable.forget()); |
247 | 0 | } |
248 | 102 | return UnlabeledDispatch(aCategory, runnable.forget()); |
249 | 102 | } |
250 | | |
251 | | /*static*/ nsresult |
252 | | SchedulerGroup::InternalUnlabeledDispatch(TaskCategory aCategory, |
253 | | already_AddRefed<Runnable>&& aRunnable) |
254 | 0 | { |
255 | 0 | if (NS_IsMainThread()) { |
256 | 0 | // NS_DispatchToCurrentThread will not leak the passed in runnable |
257 | 0 | // when it fails, so we don't need to do anything special. |
258 | 0 | return NS_DispatchToCurrentThread(std::move(aRunnable)); |
259 | 0 | } |
260 | 0 | |
261 | 0 | RefPtr<Runnable> runnable(aRunnable); |
262 | 0 | nsresult rv = NS_DispatchToMainThread(do_AddRef(runnable)); |
263 | 0 | if (NS_FAILED(rv)) { |
264 | 0 | // Dispatch failed. This is a situation where we would have used |
265 | 0 | // NS_DispatchToMainThread rather than calling into the SchedulerGroup |
266 | 0 | // machinery, and the caller would be expecting to leak the nsIRunnable |
267 | 0 | // originally passed in. But because we've had to wrap things up |
268 | 0 | // internally, we were going to leak the nsIRunnable *and* our Runnable |
269 | 0 | // wrapper. But there's no reason that we have to leak our Runnable |
270 | 0 | // wrapper; we can just leak the wrapped nsIRunnable, and let the caller |
271 | 0 | // take care of unleaking it if they need to. |
272 | 0 | Unused << runnable->mRunnable.forget().take(); |
273 | 0 | nsrefcnt refcnt = runnable.get()->Release(); |
274 | 0 | MOZ_RELEASE_ASSERT(refcnt == 1, "still holding an unexpected reference!"); |
275 | 0 | } |
276 | 0 |
|
277 | 0 | return rv; |
278 | 0 | } |
279 | | |
280 | | /* static */ void |
281 | | SchedulerGroup::SetValidatingAccess(ValidationType aType) |
282 | 0 | { |
283 | 0 | bool validating = aType == StartValidation; |
284 | 0 | sTlsValidatingAccess.set(validating); |
285 | 0 |
|
286 | 0 | dom::AutoJSAPI jsapi; |
287 | 0 | jsapi.Init(); |
288 | 0 | js::EnableAccessValidation(jsapi.cx(), validating); |
289 | 0 | } |
290 | | |
291 | | SchedulerGroup::Runnable::Runnable(already_AddRefed<nsIRunnable>&& aRunnable, |
292 | | SchedulerGroup* aGroup, |
293 | | dom::DocGroup* aDocGroup) |
294 | | : mozilla::Runnable("SchedulerGroup::Runnable") |
295 | | , mRunnable(std::move(aRunnable)) |
296 | | , mGroup(aGroup) |
297 | | , mDocGroup(aDocGroup) |
298 | 0 | { |
299 | 0 | } |
300 | | |
301 | | bool |
302 | | SchedulerGroup::Runnable::GetAffectedSchedulerGroups(SchedulerGroupSet& aGroups) |
303 | 0 | { |
304 | 0 | aGroups.Clear(); |
305 | 0 | aGroups.Put(Group()); |
306 | 0 | return true; |
307 | 0 | } |
308 | | |
309 | | dom::DocGroup* |
310 | | SchedulerGroup::Runnable::DocGroup() const |
311 | 0 | { |
312 | 0 | return mDocGroup; |
313 | 0 | } |
314 | | |
315 | | #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY |
316 | | NS_IMETHODIMP |
317 | | SchedulerGroup::Runnable::GetName(nsACString& aName) |
318 | 0 | { |
319 | 0 | // Try to get a name from the underlying runnable. |
320 | 0 | nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable); |
321 | 0 | if (named) { |
322 | 0 | named->GetName(aName); |
323 | 0 | } |
324 | 0 | if (aName.IsEmpty()) { |
325 | 0 | aName.AssignLiteral("anonymous"); |
326 | 0 | } |
327 | 0 |
|
328 | 0 | return NS_OK; |
329 | 0 | } |
330 | | #endif |
331 | | |
332 | | NS_IMETHODIMP |
333 | | SchedulerGroup::Runnable::Run() |
334 | 0 | { |
335 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
336 | 0 |
|
337 | 0 | nsresult result = mRunnable->Run(); |
338 | 0 |
|
339 | 0 | // The runnable's destructor can have side effects, so try to execute it in |
340 | 0 | // the scope of the TabGroup. |
341 | 0 | mRunnable = nullptr; |
342 | 0 |
|
343 | 0 | mGroup->SetValidatingAccess(EndValidation); |
344 | 0 | return result; |
345 | 0 | } |
346 | | |
347 | | NS_IMETHODIMP |
348 | | SchedulerGroup::Runnable::GetPriority(uint32_t* aPriority) |
349 | 0 | { |
350 | 0 | *aPriority = nsIRunnablePriority::PRIORITY_NORMAL; |
351 | 0 | nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable); |
352 | 0 | return runnablePrio ? runnablePrio->GetPriority(aPriority) : NS_OK; |
353 | 0 | } |
354 | | |
355 | | NS_IMPL_ISUPPORTS_INHERITED(SchedulerGroup::Runnable, |
356 | | mozilla::Runnable, |
357 | | nsIRunnablePriority, |
358 | | nsILabelableRunnable, |
359 | | SchedulerGroup::Runnable) |