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 : #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 "nsINamed.h"
14 : #include "nsQueryObject.h"
15 : #include "mozilla/dom/ScriptSettings.h"
16 : #include "nsThreadUtils.h"
17 :
18 : #include "mozilla/Telemetry.h"
19 :
20 : using namespace mozilla;
21 :
22 : /* SchedulerEventTarget */
23 :
24 : namespace {
25 :
26 : #define NS_DISPATCHEREVENTTARGET_IID \
27 : { 0xbf4e36c8, 0x7d04, 0x4ef4, \
28 : { 0xbb, 0xd8, 0x11, 0x09, 0x0a, 0xdb, 0x4d, 0xf7 } }
29 :
30 : class SchedulerEventTarget final : public nsISerialEventTarget
31 : {
32 : RefPtr<SchedulerGroup> mDispatcher;
33 : TaskCategory mCategory;
34 :
35 : public:
36 : NS_DECLARE_STATIC_IID_ACCESSOR(NS_DISPATCHEREVENTTARGET_IID)
37 :
38 40 : SchedulerEventTarget(SchedulerGroup* aDispatcher, TaskCategory aCategory)
39 40 : : mDispatcher(aDispatcher)
40 40 : , mCategory(aCategory)
41 40 : {}
42 :
43 : NS_DECL_THREADSAFE_ISUPPORTS
44 : NS_DECL_NSIEVENTTARGET_FULL
45 :
46 1 : SchedulerGroup* Dispatcher() const { return mDispatcher; }
47 :
48 : private:
49 0 : ~SchedulerEventTarget() {}
50 : };
51 :
52 : NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerEventTarget, NS_DISPATCHEREVENTTARGET_IID)
53 :
54 : static Atomic<uint64_t> gEarliestUnprocessedVsync(0);
55 :
56 : class MOZ_RAII AutoCollectVsyncTelemetry final
57 : {
58 : public:
59 129 : explicit AutoCollectVsyncTelemetry(SchedulerGroup::Runnable* aRunnable
60 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
61 129 : : mIsBackground(aRunnable->IsBackground())
62 : {
63 129 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
64 : #ifdef EARLY_BETA_OR_EARLIER
65 129 : aRunnable->GetName(mKey);
66 129 : mStart = TimeStamp::Now();
67 : #endif
68 129 : }
69 129 : ~AutoCollectVsyncTelemetry()
70 129 : {
71 : #ifdef EARLY_BETA_OR_EARLIER
72 129 : if (Telemetry::CanRecordBase()) {
73 129 : CollectTelemetry();
74 : }
75 : #endif
76 129 : }
77 :
78 : private:
79 : void CollectTelemetry();
80 :
81 : bool mIsBackground;
82 : nsCString mKey;
83 : TimeStamp mStart;
84 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
85 : };
86 :
87 : void
88 129 : AutoCollectVsyncTelemetry::CollectTelemetry()
89 : {
90 129 : TimeStamp now = TimeStamp::Now();
91 :
92 : mozilla::Telemetry::HistogramID eventsId =
93 129 : mIsBackground ? Telemetry::CONTENT_JS_BACKGROUND_TICK_DELAY_EVENTS_MS
94 129 : : Telemetry::CONTENT_JS_FOREGROUND_TICK_DELAY_EVENTS_MS;
95 : mozilla::Telemetry::HistogramID totalId =
96 129 : mIsBackground ? Telemetry::CONTENT_JS_BACKGROUND_TICK_DELAY_TOTAL_MS
97 129 : : Telemetry::CONTENT_JS_FOREGROUND_TICK_DELAY_TOTAL_MS;
98 :
99 129 : uint64_t lastSeenVsync = gEarliestUnprocessedVsync;
100 129 : if (!lastSeenVsync) {
101 127 : return;
102 : }
103 :
104 2 : bool inconsistent = false;
105 2 : TimeStamp creation = TimeStamp::ProcessCreation(&inconsistent);
106 2 : if (inconsistent) {
107 0 : return;
108 : }
109 :
110 : TimeStamp pendingVsync =
111 2 : creation + TimeDuration::FromMicroseconds(lastSeenVsync);
112 :
113 2 : if (pendingVsync > now) {
114 0 : return;
115 : }
116 :
117 : uint32_t duration =
118 2 : static_cast<uint32_t>((now - pendingVsync).ToMilliseconds());
119 :
120 2 : Telemetry::Accumulate(eventsId, mKey, duration);
121 2 : Telemetry::Accumulate(totalId, duration);
122 :
123 2 : if (pendingVsync > mStart) {
124 1 : return;
125 : }
126 :
127 1 : Telemetry::Accumulate(Telemetry::CONTENT_JS_KNOWN_TICK_DELAY_MS, duration);
128 :
129 1 : return;
130 : }
131 :
132 : } // namespace
133 :
134 1522 : NS_IMPL_ISUPPORTS(SchedulerEventTarget,
135 : SchedulerEventTarget,
136 : nsIEventTarget,
137 : nsISerialEventTarget)
138 :
139 : NS_IMETHODIMP
140 0 : SchedulerEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
141 : {
142 0 : return Dispatch(do_AddRef(aRunnable), aFlags);
143 : }
144 :
145 : NS_IMETHODIMP
146 157 : SchedulerEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags)
147 : {
148 157 : if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
149 0 : return NS_ERROR_UNEXPECTED;
150 : }
151 157 : return mDispatcher->Dispatch(nullptr, mCategory, Move(aRunnable));
152 : }
153 :
154 : NS_IMETHODIMP
155 0 : SchedulerEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
156 : {
157 0 : return NS_ERROR_NOT_IMPLEMENTED;
158 : }
159 :
160 : NS_IMETHODIMP
161 91 : SchedulerEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread)
162 : {
163 91 : *aIsOnCurrentThread = NS_IsMainThread();
164 91 : return NS_OK;
165 : }
166 :
167 : NS_IMETHODIMP_(bool)
168 0 : SchedulerEventTarget::IsOnCurrentThreadInfallible()
169 : {
170 0 : return NS_IsMainThread();
171 : }
172 :
173 : /* static */ nsresult
174 430 : SchedulerGroup::UnlabeledDispatch(const char* aName,
175 : TaskCategory aCategory,
176 : already_AddRefed<nsIRunnable>&& aRunnable)
177 : {
178 860 : nsCOMPtr<nsIRunnable> runnable(aRunnable);
179 430 : if (aName) {
180 546 : if (nsCOMPtr<nsINamed> named = do_QueryInterface(runnable)) {
181 273 : named->SetName(aName);
182 : }
183 : }
184 430 : if (NS_IsMainThread()) {
185 263 : return NS_DispatchToCurrentThread(runnable.forget());
186 : } else {
187 167 : return NS_DispatchToMainThread(runnable.forget());
188 : }
189 : }
190 :
191 : /* static */ void
192 57 : SchedulerGroup::MarkVsyncReceived()
193 : {
194 57 : if (gEarliestUnprocessedVsync) {
195 : // If we've seen a vsync already, but haven't handled it, keep the
196 : // older one.
197 106 : return;
198 : }
199 :
200 4 : MOZ_ASSERT(!NS_IsMainThread());
201 4 : bool inconsistent = false;
202 4 : TimeStamp creation = TimeStamp::ProcessCreation(&inconsistent);
203 4 : if (inconsistent) {
204 0 : return;
205 : }
206 :
207 4 : gEarliestUnprocessedVsync = (TimeStamp::Now() - creation).ToMicroseconds();
208 : }
209 :
210 : /* static */ void
211 4 : SchedulerGroup::MarkVsyncRan()
212 : {
213 4 : gEarliestUnprocessedVsync = 0;
214 4 : }
215 :
216 : SchedulerGroup* SchedulerGroup::sRunningDispatcher;
217 :
218 6 : SchedulerGroup::SchedulerGroup()
219 6 : : mAccessValid(false)
220 : {
221 6 : }
222 :
223 : nsresult
224 300 : SchedulerGroup::Dispatch(const char* aName,
225 : TaskCategory aCategory,
226 : already_AddRefed<nsIRunnable>&& aRunnable)
227 : {
228 300 : return LabeledDispatch(aName, aCategory, Move(aRunnable));
229 : }
230 :
231 : nsISerialEventTarget*
232 262 : SchedulerGroup::EventTargetFor(TaskCategory aCategory) const
233 : {
234 262 : MOZ_ASSERT(aCategory != TaskCategory::Count);
235 262 : MOZ_ASSERT(mEventTargets[size_t(aCategory)]);
236 262 : return mEventTargets[size_t(aCategory)];
237 : }
238 :
239 : AbstractThread*
240 2 : SchedulerGroup::AbstractMainThreadFor(TaskCategory aCategory)
241 : {
242 2 : MOZ_RELEASE_ASSERT(NS_IsMainThread());
243 2 : return AbstractMainThreadForImpl(aCategory);
244 : }
245 :
246 : AbstractThread*
247 1 : SchedulerGroup::AbstractMainThreadForImpl(TaskCategory aCategory)
248 : {
249 1 : MOZ_RELEASE_ASSERT(NS_IsMainThread());
250 1 : MOZ_ASSERT(aCategory != TaskCategory::Count);
251 1 : MOZ_ASSERT(mEventTargets[size_t(aCategory)]);
252 :
253 1 : if (!mAbstractThreads[size_t(aCategory)]) {
254 1 : mAbstractThreads[size_t(aCategory)] =
255 3 : AbstractThread::CreateEventTargetWrapper(mEventTargets[size_t(aCategory)],
256 2 : /* aDrainDirectTasks = */ true);
257 : }
258 :
259 1 : return mAbstractThreads[size_t(aCategory)];
260 : }
261 :
262 : void
263 6 : SchedulerGroup::CreateEventTargets(bool aNeedValidation)
264 : {
265 54 : for (size_t i = 0; i < size_t(TaskCategory::Count); i++) {
266 48 : TaskCategory category = static_cast<TaskCategory>(i);
267 48 : if (!aNeedValidation) {
268 : // The chrome TabGroup dispatches directly to the main thread. This means
269 : // that we don't have to worry about cyclical references when cleaning up
270 : // the chrome TabGroup.
271 8 : mEventTargets[i] = GetMainThreadSerialEventTarget();
272 : } else {
273 40 : mEventTargets[i] = CreateEventTargetFor(category);
274 : }
275 : }
276 6 : }
277 :
278 : void
279 0 : SchedulerGroup::Shutdown(bool aXPCOMShutdown)
280 : {
281 : // There is a RefPtr cycle TabGroup -> SchedulerEventTarget -> TabGroup. To
282 : // avoid leaks, we need to break the chain somewhere. We shouldn't be using
283 : // the ThrottledEventQueue for this TabGroup when no windows belong to it,
284 : // so it's safe to null out the queue here.
285 0 : for (size_t i = 0; i < size_t(TaskCategory::Count); i++) {
286 0 : mEventTargets[i] = aXPCOMShutdown ? nullptr : GetMainThreadSerialEventTarget();
287 0 : mAbstractThreads[i] = nullptr;
288 : }
289 0 : }
290 :
291 : already_AddRefed<nsISerialEventTarget>
292 40 : SchedulerGroup::CreateEventTargetFor(TaskCategory aCategory)
293 : {
294 : RefPtr<SchedulerEventTarget> target =
295 80 : new SchedulerEventTarget(this, aCategory);
296 80 : return target.forget();
297 : }
298 :
299 : /* static */ SchedulerGroup*
300 1 : SchedulerGroup::FromEventTarget(nsIEventTarget* aEventTarget)
301 : {
302 2 : RefPtr<SchedulerEventTarget> target = do_QueryObject(aEventTarget);
303 1 : if (!target) {
304 0 : return nullptr;
305 : }
306 1 : return target->Dispatcher();
307 : }
308 :
309 : nsresult
310 300 : SchedulerGroup::LabeledDispatch(const char* aName,
311 : TaskCategory aCategory,
312 : already_AddRefed<nsIRunnable>&& aRunnable)
313 : {
314 600 : nsCOMPtr<nsIRunnable> runnable(aRunnable);
315 300 : if (XRE_IsContentProcess()) {
316 270 : runnable = new Runnable(runnable.forget(), this);
317 : }
318 600 : return UnlabeledDispatch(aName, aCategory, runnable.forget());
319 : }
320 :
321 : void
322 258 : SchedulerGroup::SetValidatingAccess(ValidationType aType)
323 : {
324 258 : sRunningDispatcher = aType == StartValidation ? this : nullptr;
325 258 : mAccessValid = aType == StartValidation;
326 :
327 516 : dom::AutoJSAPI jsapi;
328 258 : jsapi.Init();
329 258 : js::EnableAccessValidation(jsapi.cx(), !!sRunningDispatcher);
330 258 : }
331 :
332 135 : SchedulerGroup::Runnable::Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
333 135 : SchedulerGroup* aGroup)
334 : : mozilla::Runnable("SchedulerGroup::Runnable")
335 135 : , mRunnable(Move(aRunnable))
336 270 : , mGroup(aGroup)
337 : {
338 135 : }
339 :
340 : NS_IMETHODIMP
341 258 : SchedulerGroup::Runnable::GetName(nsACString& aName)
342 : {
343 : // Try to get a name from the underlying runnable.
344 516 : nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable);
345 258 : if (named) {
346 258 : named->GetName(aName);
347 : }
348 258 : if (aName.IsEmpty()) {
349 0 : aName.AssignLiteral("anonymous");
350 : }
351 :
352 258 : aName.AppendASCII("(labeled)");
353 516 : return NS_OK;
354 : }
355 :
356 : NS_IMETHODIMP
357 129 : SchedulerGroup::Runnable::Run()
358 : {
359 129 : MOZ_RELEASE_ASSERT(NS_IsMainThread());
360 :
361 129 : mGroup->SetValidatingAccess(StartValidation);
362 :
363 : nsresult result;
364 :
365 : {
366 258 : AutoCollectVsyncTelemetry telemetry(this);
367 129 : result = mRunnable->Run();
368 : }
369 :
370 : // The runnable's destructor can have side effects, so try to execute it in
371 : // the scope of the TabGroup.
372 129 : mRunnable = nullptr;
373 :
374 129 : mGroup->SetValidatingAccess(EndValidation);
375 129 : return result;
376 : }
377 :
378 2175 : NS_IMPL_ISUPPORTS_INHERITED(SchedulerGroup::Runnable,
379 : mozilla::Runnable,
380 : SchedulerGroup::Runnable)
381 :
382 1230 : SchedulerGroup::AutoProcessEvent::AutoProcessEvent()
383 1230 : : mPrevRunningDispatcher(SchedulerGroup::sRunningDispatcher)
384 : {
385 1230 : SchedulerGroup* prev = sRunningDispatcher;
386 1230 : if (prev) {
387 0 : MOZ_ASSERT(prev->mAccessValid);
388 0 : prev->SetValidatingAccess(EndValidation);
389 : }
390 1230 : }
391 :
392 2454 : SchedulerGroup::AutoProcessEvent::~AutoProcessEvent()
393 : {
394 1227 : MOZ_ASSERT(!sRunningDispatcher);
395 :
396 1227 : SchedulerGroup* prev = mPrevRunningDispatcher;
397 1227 : if (prev) {
398 0 : prev->SetValidatingAccess(StartValidation);
399 : }
400 1227 : }
|