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 "LazyIdleThread.h"
8
9
#include "nsIObserverService.h"
10
11
#include "GeckoProfiler.h"
12
#include "nsComponentManagerUtils.h"
13
#include "nsIIdlePeriod.h"
14
#include "nsServiceManagerUtils.h"
15
#include "nsThreadUtils.h"
16
#include "mozilla/Services.h"
17
18
#ifdef DEBUG
19
# define ASSERT_OWNING_THREAD() \
20
do { \
21
MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \
22
} while (0)
23
#else
24
# define ASSERT_OWNING_THREAD() /* nothing */
25
#endif
26
27
namespace mozilla {
28
29
LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const nsACString& aName,
30
ShutdownMethod aShutdownMethod,
31
nsIObserver* aIdleObserver)
32
: mMutex("LazyIdleThread::mMutex"),
33
mOwningEventTarget(GetCurrentThreadSerialEventTarget()),
34
mIdleObserver(aIdleObserver),
35
mQueuedRunnables(nullptr),
36
mIdleTimeoutMS(aIdleTimeoutMS),
37
mPendingEventCount(0),
38
mIdleNotificationCount(0),
39
mShutdownMethod(aShutdownMethod),
40
mShutdown(false),
41
mThreadIsShuttingDown(false),
42
mIdleTimeoutEnabled(true),
43
mName(aName) {
44
MOZ_ASSERT(mOwningEventTarget, "Need owning thread!");
45
}
46
47
LazyIdleThread::~LazyIdleThread() {
48
ASSERT_OWNING_THREAD();
49
50
Shutdown();
51
}
52
53
void LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver) {
54
ASSERT_OWNING_THREAD();
55
56
if (mShutdown) {
57
NS_WARNING_ASSERTION(!aObserver,
58
"Setting an observer after Shutdown was called!");
59
return;
60
}
61
62
mIdleObserver = aObserver;
63
}
64
65
void LazyIdleThread::DisableIdleTimeout() {
66
ASSERT_OWNING_THREAD();
67
if (!mIdleTimeoutEnabled) {
68
return;
69
}
70
mIdleTimeoutEnabled = false;
71
72
if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) {
73
NS_WARNING("Failed to cancel timer!");
74
}
75
76
MutexAutoLock lock(mMutex);
77
78
// Pretend we have a pending event to keep the idle timer from firing.
79
MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
80
mPendingEventCount++;
81
}
82
83
void LazyIdleThread::EnableIdleTimeout() {
84
ASSERT_OWNING_THREAD();
85
if (mIdleTimeoutEnabled) {
86
return;
87
}
88
mIdleTimeoutEnabled = true;
89
90
{
91
MutexAutoLock lock(mMutex);
92
93
MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
94
--mPendingEventCount;
95
}
96
97
if (mThread) {
98
nsCOMPtr<nsIRunnable> runnable(new Runnable("LazyIdleThreadDummyRunnable"));
99
if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) {
100
NS_WARNING("Failed to dispatch!");
101
}
102
}
103
}
104
105
void LazyIdleThread::PreDispatch() {
106
MutexAutoLock lock(mMutex);
107
108
MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
109
mPendingEventCount++;
110
}
111
112
nsresult LazyIdleThread::EnsureThread() {
113
ASSERT_OWNING_THREAD();
114
115
if (mShutdown) {
116
return NS_ERROR_UNEXPECTED;
117
}
118
119
if (mThread) {
120
return NS_OK;
121
}
122
123
MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!");
124
MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!");
125
MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!");
126
MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!");
127
128
nsresult rv;
129
130
if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
131
nsCOMPtr<nsIObserverService> obs =
132
do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
133
if (NS_WARN_IF(NS_FAILED(rv))) {
134
return rv;
135
}
136
137
rv = obs->AddObserver(this, "xpcom-shutdown-threads", false);
138
if (NS_WARN_IF(NS_FAILED(rv))) {
139
return rv;
140
}
141
}
142
143
mIdleTimer = NS_NewTimer();
144
if (NS_WARN_IF(!mIdleTimer)) {
145
return NS_ERROR_UNEXPECTED;
146
}
147
148
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
149
"LazyIdleThread::InitThread", this, &LazyIdleThread::InitThread);
150
if (NS_WARN_IF(!runnable)) {
151
return NS_ERROR_UNEXPECTED;
152
}
153
154
rv = NS_NewNamedThread(mName, getter_AddRefs(mThread), runnable);
155
if (NS_WARN_IF(NS_FAILED(rv))) {
156
return rv;
157
}
158
159
return NS_OK;
160
}
161
162
void LazyIdleThread::InitThread() {
163
// Happens on mThread but mThread may not be set yet...
164
165
nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
166
MOZ_ASSERT(thread, "This should always succeed!");
167
168
if (NS_FAILED(thread->SetObserver(this))) {
169
NS_WARNING("Failed to set thread observer!");
170
}
171
}
172
173
void LazyIdleThread::CleanupThread() {
174
nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
175
MOZ_ASSERT(thread, "This should always succeed!");
176
177
if (NS_FAILED(thread->SetObserver(nullptr))) {
178
NS_WARNING("Failed to set thread observer!");
179
}
180
181
{
182
MutexAutoLock lock(mMutex);
183
184
MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!");
185
mThreadIsShuttingDown = true;
186
}
187
}
188
189
void LazyIdleThread::ScheduleTimer() {
190
ASSERT_OWNING_THREAD();
191
192
bool shouldSchedule;
193
{
194
MutexAutoLock lock(mMutex);
195
196
MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!");
197
--mIdleNotificationCount;
198
199
shouldSchedule = !mIdleNotificationCount && !mPendingEventCount;
200
}
201
202
if (mIdleTimer) {
203
if (NS_FAILED(mIdleTimer->Cancel())) {
204
NS_WARNING("Failed to cancel timer!");
205
}
206
207
if (shouldSchedule && NS_FAILED(mIdleTimer->InitWithCallback(
208
this, mIdleTimeoutMS, nsITimer::TYPE_ONE_SHOT))) {
209
NS_WARNING("Failed to schedule timer!");
210
}
211
}
212
}
213
214
nsresult LazyIdleThread::ShutdownThread() {
215
ASSERT_OWNING_THREAD();
216
217
// Before calling Shutdown() on the real thread we need to put a queue in
218
// place in case a runnable is posted to the thread while it's in the
219
// process of shutting down. This will be our queue.
220
AutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables;
221
222
nsresult rv;
223
224
// Make sure to cancel the shutdown timer before spinning the event loop
225
// during |mThread->Shutdown()| below. Otherwise the timer might fire and we
226
// could reenter here.
227
if (mIdleTimer) {
228
rv = mIdleTimer->Cancel();
229
if (NS_WARN_IF(NS_FAILED(rv))) {
230
return rv;
231
}
232
233
mIdleTimer = nullptr;
234
}
235
236
if (mThread) {
237
if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
238
nsCOMPtr<nsIObserverService> obs =
239
mozilla::services::GetObserverService();
240
NS_WARNING_ASSERTION(obs, "Failed to get observer service!");
241
242
if (obs &&
243
NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) {
244
NS_WARNING("Failed to remove observer!");
245
}
246
}
247
248
if (mIdleObserver) {
249
mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC,
250
nullptr);
251
}
252
253
#ifdef DEBUG
254
{
255
MutexAutoLock lock(mMutex);
256
MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!");
257
}
258
#endif
259
260
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
261
"LazyIdleThread::CleanupThread", this, &LazyIdleThread::CleanupThread);
262
if (NS_WARN_IF(!runnable)) {
263
return NS_ERROR_UNEXPECTED;
264
}
265
266
PreDispatch();
267
268
rv = mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
269
if (NS_WARN_IF(NS_FAILED(rv))) {
270
return rv;
271
}
272
273
// Put the temporary queue in place before calling Shutdown().
274
mQueuedRunnables = &queuedRunnables;
275
276
if (NS_FAILED(mThread->Shutdown())) {
277
NS_ERROR("Failed to shutdown the thread!");
278
}
279
280
// Now unset the queue.
281
mQueuedRunnables = nullptr;
282
283
mThread = nullptr;
284
285
{
286
MutexAutoLock lock(mMutex);
287
288
MOZ_ASSERT(!mPendingEventCount, "Huh?!");
289
MOZ_ASSERT(!mIdleNotificationCount, "Huh?!");
290
MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!");
291
mThreadIsShuttingDown = false;
292
}
293
}
294
295
// If our temporary queue has any runnables then we need to dispatch them.
296
if (queuedRunnables.Length()) {
297
// If the thread manager has gone away then these runnables will never run.
298
if (mShutdown) {
299
NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
300
return NS_OK;
301
}
302
303
// Re-dispatch the queued runnables.
304
for (uint32_t index = 0; index < queuedRunnables.Length(); index++) {
305
nsCOMPtr<nsIRunnable> runnable;
306
runnable.swap(queuedRunnables[index]);
307
MOZ_ASSERT(runnable, "Null runnable?!");
308
309
if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) {
310
NS_ERROR("Failed to re-dispatch queued runnable!");
311
}
312
}
313
}
314
315
return NS_OK;
316
}
317
318
void LazyIdleThread::SelfDestruct() {
319
MOZ_ASSERT(mRefCnt == 1, "Bad refcount!");
320
delete this;
321
}
322
323
NS_IMPL_ADDREF(LazyIdleThread)
324
325
NS_IMETHODIMP_(MozExternalRefCountType)
326
LazyIdleThread::Release() {
327
nsrefcnt count = --mRefCnt;
328
NS_LOG_RELEASE(this, count, "LazyIdleThread");
329
330
if (!count) {
331
// Stabilize refcount.
332
mRefCnt = 1;
333
334
nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod(
335
"LazyIdleThread::SelfDestruct", this, &LazyIdleThread::SelfDestruct);
336
NS_WARNING_ASSERTION(runnable, "Couldn't make runnable!");
337
338
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
339
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
340
// The only way this could fail is if we're in shutdown, and in that case
341
// threads should have been joined already. Deleting here isn't dangerous
342
// anymore because we won't spin the event loop waiting to join the
343
// thread.
344
SelfDestruct();
345
}
346
}
347
348
return count;
349
}
350
351
NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, nsIEventTarget,
352
nsISerialEventTarget, nsITimerCallback,
353
nsIThreadObserver, nsIObserver, nsINamed)
354
355
NS_IMETHODIMP
356
LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
357
nsCOMPtr<nsIRunnable> event(aEvent);
358
return Dispatch(event.forget(), aFlags);
359
}
360
361
NS_IMETHODIMP
362
LazyIdleThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
363
uint32_t aFlags) {
364
ASSERT_OWNING_THREAD();
365
nsCOMPtr<nsIRunnable> event(aEvent); // avoid leaks
366
367
// LazyIdleThread can't always support synchronous dispatch currently.
368
if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
369
return NS_ERROR_NOT_IMPLEMENTED;
370
}
371
372
if (NS_WARN_IF(mShutdown)) {
373
return NS_ERROR_UNEXPECTED;
374
}
375
376
// If our thread is shutting down then we can't actually dispatch right now.
377
// Queue this runnable for later.
378
if (UseRunnableQueue()) {
379
mQueuedRunnables->AppendElement(event);
380
return NS_OK;
381
}
382
383
nsresult rv = EnsureThread();
384
if (NS_WARN_IF(NS_FAILED(rv))) {
385
return rv;
386
}
387
388
PreDispatch();
389
390
return mThread->Dispatch(event.forget(), aFlags);
391
}
392
393
NS_IMETHODIMP
394
LazyIdleThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
395
return NS_ERROR_NOT_IMPLEMENTED;
396
}
397
398
NS_IMETHODIMP
399
LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) {
400
if (mThread) {
401
return mThread->IsOnCurrentThread(aIsOnCurrentThread);
402
}
403
404
*aIsOnCurrentThread = false;
405
return NS_OK;
406
}
407
408
NS_IMETHODIMP_(bool)
409
LazyIdleThread::IsOnCurrentThreadInfallible() {
410
if (mThread) {
411
return mThread->IsOnCurrentThread();
412
}
413
414
return false;
415
}
416
417
NS_IMETHODIMP
418
LazyIdleThread::GetPRThread(PRThread** aPRThread) {
419
if (mThread) {
420
return mThread->GetPRThread(aPRThread);
421
}
422
423
*aPRThread = nullptr;
424
return NS_ERROR_NOT_AVAILABLE;
425
}
426
427
NS_IMETHODIMP
428
LazyIdleThread::GetCanInvokeJS(bool* aCanInvokeJS) {
429
*aCanInvokeJS = false;
430
return NS_OK;
431
}
432
433
NS_IMETHODIMP
434
LazyIdleThread::SetCanInvokeJS(bool aCanInvokeJS) {
435
return NS_ERROR_NOT_IMPLEMENTED;
436
}
437
438
NS_IMETHODIMP
439
LazyIdleThread::GetLastLongTaskEnd(TimeStamp* _retval) {
440
return NS_ERROR_NOT_IMPLEMENTED;
441
}
442
443
NS_IMETHODIMP
444
LazyIdleThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) {
445
return NS_ERROR_NOT_IMPLEMENTED;
446
}
447
448
NS_IMETHODIMP
449
LazyIdleThread::SetNameForWakeupTelemetry(const nsACString& aName) {
450
return NS_ERROR_NOT_IMPLEMENTED;
451
}
452
453
NS_IMETHODIMP
454
LazyIdleThread::AsyncShutdown() {
455
ASSERT_OWNING_THREAD();
456
return NS_ERROR_NOT_IMPLEMENTED;
457
}
458
459
NS_IMETHODIMP
460
LazyIdleThread::Shutdown() {
461
ASSERT_OWNING_THREAD();
462
463
mShutdown = true;
464
465
nsresult rv = ShutdownThread();
466
MOZ_ASSERT(!mThread, "Should have destroyed this by now!");
467
468
mIdleObserver = nullptr;
469
470
if (NS_WARN_IF(NS_FAILED(rv))) {
471
return rv;
472
}
473
474
return NS_OK;
475
}
476
477
NS_IMETHODIMP
478
LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents) {
479
// This is only supposed to be called from the thread itself so it's not
480
// implemented here.
481
MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
482
return NS_ERROR_UNEXPECTED;
483
}
484
485
NS_IMETHODIMP
486
LazyIdleThread::HasPendingHighPriorityEvents(bool* aHasPendingEvents) {
487
// This is only supposed to be called from the thread itself so it's not
488
// implemented here.
489
MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
490
return NS_ERROR_UNEXPECTED;
491
}
492
493
NS_IMETHODIMP
494
LazyIdleThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent,
495
EventQueuePriority aQueue) {
496
return NS_ERROR_NOT_IMPLEMENTED;
497
}
498
499
NS_IMETHODIMP
500
LazyIdleThread::ProcessNextEvent(bool aMayWait, bool* aEventWasProcessed) {
501
// This is only supposed to be called from the thread itself so it's not
502
// implemented here.
503
MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
504
return NS_ERROR_UNEXPECTED;
505
}
506
507
NS_IMETHODIMP
508
LazyIdleThread::Notify(nsITimer* aTimer) {
509
ASSERT_OWNING_THREAD();
510
511
{
512
MutexAutoLock lock(mMutex);
513
514
if (mPendingEventCount || mIdleNotificationCount) {
515
// Another event was scheduled since this timer was set. Don't do
516
// anything and wait for the timer to fire again.
517
return NS_OK;
518
}
519
}
520
521
nsresult rv = ShutdownThread();
522
if (NS_WARN_IF(NS_FAILED(rv))) {
523
return rv;
524
}
525
526
return NS_OK;
527
}
528
529
NS_IMETHODIMP
530
LazyIdleThread::GetName(nsACString& aName) {
531
aName.Assign(mName);
532
return NS_OK;
533
}
534
535
NS_IMETHODIMP
536
LazyIdleThread::OnDispatchedEvent() {
537
MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
538
return NS_OK;
539
}
540
541
NS_IMETHODIMP
542
LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
543
bool /* aMayWait */) {
544
return NS_OK;
545
}
546
547
NS_IMETHODIMP
548
LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
549
bool aEventWasProcessed) {
550
bool shouldNotifyIdle;
551
{
552
MutexAutoLock lock(mMutex);
553
554
if (aEventWasProcessed) {
555
MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
556
--mPendingEventCount;
557
}
558
559
if (mThreadIsShuttingDown) {
560
// We're shutting down, no need to fire any timer.
561
return NS_OK;
562
}
563
564
shouldNotifyIdle = !mPendingEventCount;
565
if (shouldNotifyIdle) {
566
MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!");
567
mIdleNotificationCount++;
568
}
569
}
570
571
if (shouldNotifyIdle) {
572
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
573
"LazyIdleThread::ScheduleTimer", this, &LazyIdleThread::ScheduleTimer);
574
if (NS_WARN_IF(!runnable)) {
575
return NS_ERROR_UNEXPECTED;
576
}
577
578
nsresult rv =
579
mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
580
if (NS_WARN_IF(NS_FAILED(rv))) {
581
return rv;
582
}
583
}
584
585
return NS_OK;
586
}
587
588
NS_IMETHODIMP
589
LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic,
590
const char16_t* /* aData */) {
591
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
592
MOZ_ASSERT(mShutdownMethod == AutomaticShutdown,
593
"Should not receive notifications if not AutomaticShutdown!");
594
MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");
595
596
Shutdown();
597
return NS_OK;
598
}
599
600
NS_IMETHODIMP
601
LazyIdleThread::GetEventTarget(nsIEventTarget** aEventTarget) {
602
nsCOMPtr<nsIEventTarget> target = this;
603
target.forget(aEventTarget);
604
return NS_OK;
605
}
606
607
nsIEventTarget* LazyIdleThread::EventTarget() { return this; }
608
609
nsISerialEventTarget* LazyIdleThread::SerialEventTarget() { return this; }
610
611
} // namespace mozilla