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/. */
7 #include "LazyIdleThread.h"
9 #include "nsIObserverService.h"
11 #include "GeckoProfiler.h"
12 #include "nsComponentManagerUtils.h"
13 #include "nsServiceManagerUtils.h"
14 #include "nsThreadUtils.h"
15 #include "mozilla/Services.h"
18 # define ASSERT_OWNING_THREAD() \
20 MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \
23 # define ASSERT_OWNING_THREAD() /* nothing */
28 LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS
, const nsACString
& aName
,
29 ShutdownMethod aShutdownMethod
,
30 nsIObserver
* aIdleObserver
)
31 : mMutex("LazyIdleThread::mMutex"),
32 mOwningEventTarget(GetCurrentSerialEventTarget()),
33 mIdleObserver(aIdleObserver
),
34 mQueuedRunnables(nullptr),
35 mIdleTimeoutMS(aIdleTimeoutMS
),
36 mPendingEventCount(0),
37 mIdleNotificationCount(0),
38 mShutdownMethod(aShutdownMethod
),
40 mThreadIsShuttingDown(false),
41 mIdleTimeoutEnabled(true),
43 MOZ_ASSERT(mOwningEventTarget
, "Need owning thread!");
46 LazyIdleThread::~LazyIdleThread() {
47 ASSERT_OWNING_THREAD();
52 void LazyIdleThread::SetWeakIdleObserver(nsIObserver
* aObserver
) {
53 ASSERT_OWNING_THREAD();
56 NS_WARNING_ASSERTION(!aObserver
,
57 "Setting an observer after Shutdown was called!");
61 mIdleObserver
= aObserver
;
64 void LazyIdleThread::DisableIdleTimeout() {
65 ASSERT_OWNING_THREAD();
66 if (!mIdleTimeoutEnabled
) {
69 mIdleTimeoutEnabled
= false;
71 if (mIdleTimer
&& NS_FAILED(mIdleTimer
->Cancel())) {
72 NS_WARNING("Failed to cancel timer!");
75 MutexAutoLock
lock(mMutex
);
77 // Pretend we have a pending event to keep the idle timer from firing.
78 MOZ_ASSERT(mPendingEventCount
< UINT32_MAX
, "Way too many!");
82 void LazyIdleThread::EnableIdleTimeout() {
83 ASSERT_OWNING_THREAD();
84 if (mIdleTimeoutEnabled
) {
87 mIdleTimeoutEnabled
= true;
90 MutexAutoLock
lock(mMutex
);
92 MOZ_ASSERT(mPendingEventCount
, "Mismatched calls to observer methods!");
97 nsCOMPtr
<nsIRunnable
> runnable(new Runnable("LazyIdleThreadDummyRunnable"));
98 if (NS_FAILED(Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
))) {
99 NS_WARNING("Failed to dispatch!");
104 void LazyIdleThread::PreDispatch() {
105 MutexAutoLock
lock(mMutex
);
107 MOZ_ASSERT(mPendingEventCount
< UINT32_MAX
, "Way too many!");
108 mPendingEventCount
++;
111 nsresult
LazyIdleThread::EnsureThread() {
112 ASSERT_OWNING_THREAD();
115 return NS_ERROR_UNEXPECTED
;
122 MOZ_ASSERT(!mPendingEventCount
, "Shouldn't have events yet!");
123 MOZ_ASSERT(!mIdleNotificationCount
, "Shouldn't have idle events yet!");
124 MOZ_ASSERT(!mIdleTimer
, "Should have killed this long ago!");
125 MOZ_ASSERT(!mThreadIsShuttingDown
, "Should have cleared that!");
129 if (mShutdownMethod
== AutomaticShutdown
&& NS_IsMainThread()) {
130 nsCOMPtr
<nsIObserverService
> obs
=
131 do_GetService(NS_OBSERVERSERVICE_CONTRACTID
, &rv
);
132 if (NS_WARN_IF(NS_FAILED(rv
))) {
136 rv
= obs
->AddObserver(this, "xpcom-shutdown-threads", false);
137 if (NS_WARN_IF(NS_FAILED(rv
))) {
142 mIdleTimer
= NS_NewTimer();
143 if (NS_WARN_IF(!mIdleTimer
)) {
144 return NS_ERROR_UNEXPECTED
;
147 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
148 "LazyIdleThread::InitThread", this, &LazyIdleThread::InitThread
);
149 if (NS_WARN_IF(!runnable
)) {
150 return NS_ERROR_UNEXPECTED
;
153 rv
= NS_NewNamedThread(mName
, getter_AddRefs(mThread
), runnable
);
154 if (NS_WARN_IF(NS_FAILED(rv
))) {
161 void LazyIdleThread::InitThread() {
162 // Happens on mThread but mThread may not be set yet...
164 nsCOMPtr
<nsIThreadInternal
> thread(do_QueryInterface(NS_GetCurrentThread()));
165 MOZ_ASSERT(thread
, "This should always succeed!");
167 if (NS_FAILED(thread
->SetObserver(this))) {
168 NS_WARNING("Failed to set thread observer!");
172 void LazyIdleThread::CleanupThread() {
173 nsCOMPtr
<nsIThreadInternal
> thread(do_QueryInterface(NS_GetCurrentThread()));
174 MOZ_ASSERT(thread
, "This should always succeed!");
176 if (NS_FAILED(thread
->SetObserver(nullptr))) {
177 NS_WARNING("Failed to set thread observer!");
181 MutexAutoLock
lock(mMutex
);
183 MOZ_ASSERT(!mThreadIsShuttingDown
, "Shouldn't be true ever!");
184 mThreadIsShuttingDown
= true;
188 void LazyIdleThread::ScheduleTimer() {
189 ASSERT_OWNING_THREAD();
193 MutexAutoLock
lock(mMutex
);
195 MOZ_ASSERT(mIdleNotificationCount
, "Should have at least one!");
196 --mIdleNotificationCount
;
198 shouldSchedule
= !mIdleNotificationCount
&& !mPendingEventCount
;
202 if (NS_FAILED(mIdleTimer
->Cancel())) {
203 NS_WARNING("Failed to cancel timer!");
206 if (shouldSchedule
&& NS_FAILED(mIdleTimer
->InitWithCallback(
207 this, mIdleTimeoutMS
, nsITimer::TYPE_ONE_SHOT
))) {
208 NS_WARNING("Failed to schedule timer!");
213 nsresult
LazyIdleThread::ShutdownThread() {
214 ASSERT_OWNING_THREAD();
216 // Before calling Shutdown() on the real thread we need to put a queue in
217 // place in case a runnable is posted to the thread while it's in the
218 // process of shutting down. This will be our queue.
219 AutoTArray
<nsCOMPtr
<nsIRunnable
>, 10> queuedRunnables
;
223 // Make sure to cancel the shutdown timer before spinning the event loop
224 // during |mThread->Shutdown()| below. Otherwise the timer might fire and we
225 // could reenter here.
227 rv
= mIdleTimer
->Cancel();
228 if (NS_WARN_IF(NS_FAILED(rv
))) {
232 mIdleTimer
= nullptr;
236 if (mShutdownMethod
== AutomaticShutdown
&& NS_IsMainThread()) {
237 nsCOMPtr
<nsIObserverService
> obs
=
238 mozilla::services::GetObserverService();
239 NS_WARNING_ASSERTION(obs
, "Failed to get observer service!");
242 NS_FAILED(obs
->RemoveObserver(this, "xpcom-shutdown-threads"))) {
243 NS_WARNING("Failed to remove observer!");
248 mIdleObserver
->Observe(static_cast<nsIThread
*>(this), IDLE_THREAD_TOPIC
,
254 MutexAutoLock
lock(mMutex
);
255 MOZ_ASSERT(!mThreadIsShuttingDown
, "Huh?!");
259 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
260 "LazyIdleThread::CleanupThread", this, &LazyIdleThread::CleanupThread
);
261 if (NS_WARN_IF(!runnable
)) {
262 return NS_ERROR_UNEXPECTED
;
267 rv
= mThread
->Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
268 if (NS_WARN_IF(NS_FAILED(rv
))) {
272 // Put the temporary queue in place before calling Shutdown().
273 mQueuedRunnables
= &queuedRunnables
;
275 if (NS_FAILED(mThread
->Shutdown())) {
276 NS_ERROR("Failed to shutdown the thread!");
279 // Now unset the queue.
280 mQueuedRunnables
= nullptr;
285 MutexAutoLock
lock(mMutex
);
287 MOZ_ASSERT(!mPendingEventCount
, "Huh?!");
288 MOZ_ASSERT(!mIdleNotificationCount
, "Huh?!");
289 MOZ_ASSERT(mThreadIsShuttingDown
, "Huh?!");
290 mThreadIsShuttingDown
= false;
294 // If our temporary queue has any runnables then we need to dispatch them.
295 if (queuedRunnables
.Length()) {
296 // If the thread manager has gone away then these runnables will never run.
298 NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
302 // Re-dispatch the queued runnables.
303 for (uint32_t index
= 0; index
< queuedRunnables
.Length(); index
++) {
304 nsCOMPtr
<nsIRunnable
> runnable
;
305 runnable
.swap(queuedRunnables
[index
]);
306 MOZ_ASSERT(runnable
, "Null runnable?!");
308 if (NS_FAILED(Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
))) {
309 NS_ERROR("Failed to re-dispatch queued runnable!");
317 void LazyIdleThread::SelfDestruct() {
318 MOZ_ASSERT(mRefCnt
== 1, "Bad refcount!");
322 NS_IMPL_ADDREF(LazyIdleThread
)
324 NS_IMETHODIMP_(MozExternalRefCountType
)
325 LazyIdleThread::Release() {
326 nsrefcnt count
= --mRefCnt
;
327 NS_LOG_RELEASE(this, count
, "LazyIdleThread");
330 // Stabilize refcount.
333 nsCOMPtr
<nsIRunnable
> runnable
= NewNonOwningRunnableMethod(
334 "LazyIdleThread::SelfDestruct", this, &LazyIdleThread::SelfDestruct
);
335 NS_WARNING_ASSERTION(runnable
, "Couldn't make runnable!");
337 if (NS_FAILED(NS_DispatchToCurrentThread(runnable
))) {
338 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
339 // The only way this could fail is if we're in shutdown, and in that case
340 // threads should have been joined already. Deleting here isn't dangerous
341 // anymore because we won't spin the event loop waiting to join the
350 NS_IMPL_QUERY_INTERFACE(LazyIdleThread
, nsIThread
, nsIEventTarget
,
351 nsISerialEventTarget
, nsITimerCallback
,
352 nsIThreadObserver
, nsIObserver
, nsINamed
)
355 LazyIdleThread::DispatchFromScript(nsIRunnable
* aEvent
, uint32_t aFlags
) {
356 nsCOMPtr
<nsIRunnable
> event(aEvent
);
357 return Dispatch(event
.forget(), aFlags
);
361 LazyIdleThread::Dispatch(already_AddRefed
<nsIRunnable
> aEvent
,
363 ASSERT_OWNING_THREAD();
364 nsCOMPtr
<nsIRunnable
> event(aEvent
); // avoid leaks
366 // LazyIdleThread can't always support synchronous dispatch currently.
367 if (NS_WARN_IF(aFlags
!= NS_DISPATCH_NORMAL
)) {
368 return NS_ERROR_NOT_IMPLEMENTED
;
371 if (NS_WARN_IF(mShutdown
)) {
372 return NS_ERROR_UNEXPECTED
;
375 // If our thread is shutting down then we can't actually dispatch right now.
376 // Queue this runnable for later.
377 if (UseRunnableQueue()) {
378 mQueuedRunnables
->AppendElement(event
);
382 nsresult rv
= EnsureThread();
383 if (NS_WARN_IF(NS_FAILED(rv
))) {
389 return mThread
->Dispatch(event
.forget(), aFlags
);
393 LazyIdleThread::DelayedDispatch(already_AddRefed
<nsIRunnable
>, uint32_t) {
394 return NS_ERROR_NOT_IMPLEMENTED
;
398 LazyIdleThread::GetRunningEventDelay(TimeDuration
* aDelay
, TimeStamp
* aStart
) {
400 return mThread
->GetRunningEventDelay(aDelay
, aStart
);
402 *aDelay
= TimeDuration();
403 *aStart
= TimeStamp();
408 LazyIdleThread::SetRunningEventDelay(TimeDuration aDelay
, TimeStamp aStart
) {
410 return mThread
->SetRunningEventDelay(aDelay
, aStart
);
416 LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread
) {
418 return mThread
->IsOnCurrentThread(aIsOnCurrentThread
);
421 *aIsOnCurrentThread
= false;
426 LazyIdleThread::IsOnCurrentThreadInfallible() {
428 return mThread
->IsOnCurrentThread();
435 LazyIdleThread::GetPRThread(PRThread
** aPRThread
) {
437 return mThread
->GetPRThread(aPRThread
);
440 *aPRThread
= nullptr;
441 return NS_ERROR_NOT_AVAILABLE
;
445 LazyIdleThread::GetCanInvokeJS(bool* aCanInvokeJS
) {
446 *aCanInvokeJS
= false;
451 LazyIdleThread::SetCanInvokeJS(bool aCanInvokeJS
) {
452 return NS_ERROR_NOT_IMPLEMENTED
;
456 LazyIdleThread::GetLastLongTaskEnd(TimeStamp
* _retval
) {
457 return NS_ERROR_NOT_IMPLEMENTED
;
461 LazyIdleThread::GetLastLongNonIdleTaskEnd(TimeStamp
* _retval
) {
462 return NS_ERROR_NOT_IMPLEMENTED
;
466 LazyIdleThread::SetNameForWakeupTelemetry(const nsACString
& aName
) {
467 return NS_ERROR_NOT_IMPLEMENTED
;
471 LazyIdleThread::AsyncShutdown() {
472 ASSERT_OWNING_THREAD();
473 return NS_ERROR_NOT_IMPLEMENTED
;
477 LazyIdleThread::Shutdown() {
478 ASSERT_OWNING_THREAD();
482 nsresult rv
= ShutdownThread();
483 MOZ_ASSERT(!mThread
, "Should have destroyed this by now!");
485 mIdleObserver
= nullptr;
487 if (NS_WARN_IF(NS_FAILED(rv
))) {
495 LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents
) {
496 // This is only supposed to be called from the thread itself so it's not
498 MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
499 return NS_ERROR_UNEXPECTED
;
503 LazyIdleThread::HasPendingHighPriorityEvents(bool* aHasPendingEvents
) {
504 // This is only supposed to be called from the thread itself so it's not
506 MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
507 return NS_ERROR_UNEXPECTED
;
511 LazyIdleThread::DispatchToQueue(already_AddRefed
<nsIRunnable
> aEvent
,
512 EventQueuePriority aQueue
) {
513 return NS_ERROR_NOT_IMPLEMENTED
;
517 LazyIdleThread::ProcessNextEvent(bool aMayWait
, bool* aEventWasProcessed
) {
518 // This is only supposed to be called from the thread itself so it's not
520 MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!");
521 return NS_ERROR_UNEXPECTED
;
525 LazyIdleThread::Notify(nsITimer
* aTimer
) {
526 ASSERT_OWNING_THREAD();
529 MutexAutoLock
lock(mMutex
);
531 if (mPendingEventCount
|| mIdleNotificationCount
) {
532 // Another event was scheduled since this timer was set. Don't do
533 // anything and wait for the timer to fire again.
538 nsresult rv
= ShutdownThread();
539 if (NS_WARN_IF(NS_FAILED(rv
))) {
547 LazyIdleThread::GetName(nsACString
& aName
) {
553 LazyIdleThread::OnDispatchedEvent() {
554 MOZ_ASSERT(mOwningEventTarget
->IsOnCurrentThread());
559 LazyIdleThread::OnProcessNextEvent(nsIThreadInternal
* /* aThread */,
560 bool /* aMayWait */) {
565 LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal
* /* aThread */,
566 bool aEventWasProcessed
) {
567 bool shouldNotifyIdle
;
569 MutexAutoLock
lock(mMutex
);
571 if (aEventWasProcessed
) {
572 MOZ_ASSERT(mPendingEventCount
, "Mismatched calls to observer methods!");
573 --mPendingEventCount
;
576 if (mThreadIsShuttingDown
) {
577 // We're shutting down, no need to fire any timer.
581 shouldNotifyIdle
= !mPendingEventCount
;
582 if (shouldNotifyIdle
) {
583 MOZ_ASSERT(mIdleNotificationCount
< UINT32_MAX
, "Way too many!");
584 mIdleNotificationCount
++;
588 if (shouldNotifyIdle
) {
589 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod(
590 "LazyIdleThread::ScheduleTimer", this, &LazyIdleThread::ScheduleTimer
);
591 if (NS_WARN_IF(!runnable
)) {
592 return NS_ERROR_UNEXPECTED
;
596 mOwningEventTarget
->Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
597 if (NS_WARN_IF(NS_FAILED(rv
))) {
606 LazyIdleThread::Observe(nsISupports
* /* aSubject */, const char* aTopic
,
607 const char16_t
* /* aData */) {
608 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
609 MOZ_ASSERT(mShutdownMethod
== AutomaticShutdown
,
610 "Should not receive notifications if not AutomaticShutdown!");
611 MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic
), "Bad topic!");
618 LazyIdleThread::GetEventTarget(nsIEventTarget
** aEventTarget
) {
619 nsCOMPtr
<nsIEventTarget
> target
= this;
620 target
.forget(aEventTarget
);
624 nsIEventTarget
* LazyIdleThread::EventTarget() { return this; }
626 nsISerialEventTarget
* LazyIdleThread::SerialEventTarget() { return this; }
628 } // namespace mozilla