1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=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 nsIThread* currentThread = NS_GetCurrentThread(); \
21 if (currentThread) { \
22 nsCOMPtr<nsISupports> current(do_QueryInterface(currentThread)); \
23 nsCOMPtr<nsISupports> test(do_QueryInterface(mOwningThread)); \
24 MOZ_ASSERT(current == test, "Wrong thread!"); \
28 #define ASSERT_OWNING_THREAD() /* nothing */
33 LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS
,
34 const nsCSubstring
& aName
,
35 ShutdownMethod aShutdownMethod
,
36 nsIObserver
* aIdleObserver
)
37 : mMutex("LazyIdleThread::mMutex"),
38 mOwningThread(NS_GetCurrentThread()),
39 mIdleObserver(aIdleObserver
),
40 mQueuedRunnables(nullptr),
41 mIdleTimeoutMS(aIdleTimeoutMS
),
42 mPendingEventCount(0),
43 mIdleNotificationCount(0),
44 mShutdownMethod(aShutdownMethod
),
46 mThreadIsShuttingDown(false),
47 mIdleTimeoutEnabled(true),
50 MOZ_ASSERT(mOwningThread
, "Need owning thread!");
53 LazyIdleThread::~LazyIdleThread()
55 ASSERT_OWNING_THREAD();
61 LazyIdleThread::SetWeakIdleObserver(nsIObserver
* aObserver
)
63 ASSERT_OWNING_THREAD();
66 NS_WARN_IF_FALSE(!aObserver
,
67 "Setting an observer after Shutdown was called!");
71 mIdleObserver
= aObserver
;
75 LazyIdleThread::DisableIdleTimeout()
77 ASSERT_OWNING_THREAD();
78 if (!mIdleTimeoutEnabled
) {
81 mIdleTimeoutEnabled
= false;
83 if (mIdleTimer
&& NS_FAILED(mIdleTimer
->Cancel())) {
84 NS_WARNING("Failed to cancel timer!");
87 MutexAutoLock
lock(mMutex
);
89 // Pretend we have a pending event to keep the idle timer from firing.
90 MOZ_ASSERT(mPendingEventCount
< UINT32_MAX
, "Way too many!");
95 LazyIdleThread::EnableIdleTimeout()
97 ASSERT_OWNING_THREAD();
98 if (mIdleTimeoutEnabled
) {
101 mIdleTimeoutEnabled
= true;
104 MutexAutoLock
lock(mMutex
);
106 MOZ_ASSERT(mPendingEventCount
, "Mismatched calls to observer methods!");
107 --mPendingEventCount
;
111 nsCOMPtr
<nsIRunnable
> runnable(new nsRunnable());
112 if (NS_FAILED(Dispatch(runnable
, NS_DISPATCH_NORMAL
))) {
113 NS_WARNING("Failed to dispatch!");
119 LazyIdleThread::PreDispatch()
121 MutexAutoLock
lock(mMutex
);
123 MOZ_ASSERT(mPendingEventCount
< UINT32_MAX
, "Way too many!");
124 mPendingEventCount
++;
128 LazyIdleThread::EnsureThread()
130 ASSERT_OWNING_THREAD();
133 return NS_ERROR_UNEXPECTED
;
140 MOZ_ASSERT(!mPendingEventCount
, "Shouldn't have events yet!");
141 MOZ_ASSERT(!mIdleNotificationCount
, "Shouldn't have idle events yet!");
142 MOZ_ASSERT(!mIdleTimer
, "Should have killed this long ago!");
143 MOZ_ASSERT(!mThreadIsShuttingDown
, "Should have cleared that!");
147 if (mShutdownMethod
== AutomaticShutdown
&& NS_IsMainThread()) {
148 nsCOMPtr
<nsIObserverService
> obs
=
149 do_GetService(NS_OBSERVERSERVICE_CONTRACTID
, &rv
);
150 NS_ENSURE_SUCCESS(rv
, rv
);
152 rv
= obs
->AddObserver(this, "xpcom-shutdown-threads", false);
153 NS_ENSURE_SUCCESS(rv
, rv
);
156 mIdleTimer
= do_CreateInstance(NS_TIMER_CONTRACTID
, &rv
);
157 NS_ENSURE_TRUE(mIdleTimer
, NS_ERROR_FAILURE
);
159 nsCOMPtr
<nsIRunnable
> runnable
=
160 NS_NewRunnableMethod(this, &LazyIdleThread::InitThread
);
161 NS_ENSURE_TRUE(runnable
, NS_ERROR_FAILURE
);
163 rv
= NS_NewThread(getter_AddRefs(mThread
), runnable
);
164 NS_ENSURE_SUCCESS(rv
, rv
);
170 LazyIdleThread::InitThread()
173 profiler_register_thread(mName
.get(), &aLocal
);
175 PR_SetCurrentThreadName(mName
.get());
177 // Happens on mThread but mThread may not be set yet...
179 nsCOMPtr
<nsIThreadInternal
> thread(do_QueryInterface(NS_GetCurrentThread()));
180 MOZ_ASSERT(thread
, "This should always succeed!");
182 if (NS_FAILED(thread
->SetObserver(this))) {
183 NS_WARNING("Failed to set thread observer!");
188 LazyIdleThread::CleanupThread()
190 nsCOMPtr
<nsIThreadInternal
> thread(do_QueryInterface(NS_GetCurrentThread()));
191 MOZ_ASSERT(thread
, "This should always succeed!");
193 if (NS_FAILED(thread
->SetObserver(nullptr))) {
194 NS_WARNING("Failed to set thread observer!");
198 MutexAutoLock
lock(mMutex
);
200 MOZ_ASSERT(!mThreadIsShuttingDown
, "Shouldn't be true ever!");
201 mThreadIsShuttingDown
= true;
204 profiler_unregister_thread();
208 LazyIdleThread::ScheduleTimer()
210 ASSERT_OWNING_THREAD();
214 MutexAutoLock
lock(mMutex
);
216 MOZ_ASSERT(mIdleNotificationCount
, "Should have at least one!");
217 --mIdleNotificationCount
;
219 shouldSchedule
= !mIdleNotificationCount
&& !mPendingEventCount
;
222 if (NS_FAILED(mIdleTimer
->Cancel())) {
223 NS_WARNING("Failed to cancel timer!");
226 if (shouldSchedule
&&
227 NS_FAILED(mIdleTimer
->InitWithCallback(this, mIdleTimeoutMS
,
228 nsITimer::TYPE_ONE_SHOT
))) {
229 NS_WARNING("Failed to schedule timer!");
234 LazyIdleThread::ShutdownThread()
236 ASSERT_OWNING_THREAD();
238 // Before calling Shutdown() on the real thread we need to put a queue in
239 // place in case a runnable is posted to the thread while it's in the
240 // process of shutting down. This will be our queue.
241 nsAutoTArray
<nsCOMPtr
<nsIRunnable
>, 10> queuedRunnables
;
246 if (mShutdownMethod
== AutomaticShutdown
&& NS_IsMainThread()) {
247 nsCOMPtr
<nsIObserverService
> obs
=
248 mozilla::services::GetObserverService();
249 NS_WARN_IF_FALSE(obs
, "Failed to get observer service!");
252 NS_FAILED(obs
->RemoveObserver(this, "xpcom-shutdown-threads"))) {
253 NS_WARNING("Failed to remove observer!");
258 mIdleObserver
->Observe(static_cast<nsIThread
*>(this), IDLE_THREAD_TOPIC
,
264 MutexAutoLock
lock(mMutex
);
265 MOZ_ASSERT(!mThreadIsShuttingDown
, "Huh?!");
269 nsCOMPtr
<nsIRunnable
> runnable
=
270 NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread
);
271 NS_ENSURE_TRUE(runnable
, NS_ERROR_FAILURE
);
275 rv
= mThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
276 NS_ENSURE_SUCCESS(rv
, rv
);
278 // Put the temporary queue in place before calling Shutdown().
279 mQueuedRunnables
= &queuedRunnables
;
281 if (NS_FAILED(mThread
->Shutdown())) {
282 NS_ERROR("Failed to shutdown the thread!");
285 // Now unset the queue.
286 mQueuedRunnables
= nullptr;
291 MutexAutoLock
lock(mMutex
);
293 MOZ_ASSERT(!mPendingEventCount
, "Huh?!");
294 MOZ_ASSERT(!mIdleNotificationCount
, "Huh?!");
295 MOZ_ASSERT(mThreadIsShuttingDown
, "Huh?!");
296 mThreadIsShuttingDown
= false;
301 rv
= mIdleTimer
->Cancel();
302 NS_ENSURE_SUCCESS(rv
, rv
);
304 mIdleTimer
= nullptr;
307 // If our temporary queue has any runnables then we need to dispatch them.
308 if (queuedRunnables
.Length()) {
309 // If the thread manager has gone away then these runnables will never run.
311 NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
315 // Re-dispatch the queued runnables.
316 for (uint32_t index
= 0; index
< queuedRunnables
.Length(); index
++) {
317 nsCOMPtr
<nsIRunnable
> runnable
;
318 runnable
.swap(queuedRunnables
[index
]);
319 MOZ_ASSERT(runnable
, "Null runnable?!");
321 if (NS_FAILED(Dispatch(runnable
, NS_DISPATCH_NORMAL
))) {
322 NS_ERROR("Failed to re-dispatch queued runnable!");
331 LazyIdleThread::SelfDestruct()
333 MOZ_ASSERT(mRefCnt
== 1, "Bad refcount!");
337 NS_IMPL_ADDREF(LazyIdleThread
)
339 NS_IMETHODIMP_(nsrefcnt
)
340 LazyIdleThread::Release()
342 nsrefcnt count
= --mRefCnt
;
343 NS_LOG_RELEASE(this, count
, "LazyIdleThread");
346 // Stabilize refcount.
349 nsCOMPtr
<nsIRunnable
> runnable
=
350 NS_NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct
);
351 NS_WARN_IF_FALSE(runnable
, "Couldn't make runnable!");
353 if (NS_FAILED(NS_DispatchToCurrentThread(runnable
))) {
354 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
355 // The only way this could fail is if we're in shutdown, and in that case
356 // threads should have been joined already. Deleting here isn't dangerous
357 // anymore because we won't spin the event loop waiting to join the
366 NS_IMPL_QUERY_INTERFACE5(LazyIdleThread
, nsIThread
,
373 LazyIdleThread::Dispatch(nsIRunnable
* aEvent
,
376 ASSERT_OWNING_THREAD();
378 // LazyIdleThread can't always support synchronous dispatch currently.
379 NS_ENSURE_TRUE(aFlags
== NS_DISPATCH_NORMAL
, NS_ERROR_NOT_IMPLEMENTED
);
381 // If our thread is shutting down then we can't actually dispatch right now.
382 // Queue this runnable for later.
383 if (UseRunnableQueue()) {
384 mQueuedRunnables
->AppendElement(aEvent
);
388 nsresult rv
= EnsureThread();
389 NS_ENSURE_SUCCESS(rv
, rv
);
393 return mThread
->Dispatch(aEvent
, aFlags
);
397 LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread
)
400 return mThread
->IsOnCurrentThread(aIsOnCurrentThread
);
403 *aIsOnCurrentThread
= false;
408 LazyIdleThread::GetPRThread(PRThread
** aPRThread
)
411 return mThread
->GetPRThread(aPRThread
);
414 *aPRThread
= nullptr;
415 return NS_ERROR_NOT_AVAILABLE
;
419 LazyIdleThread::Shutdown()
421 ASSERT_OWNING_THREAD();
425 nsresult rv
= ShutdownThread();
426 MOZ_ASSERT(!mThread
, "Should have destroyed this by now!");
428 mIdleObserver
= nullptr;
430 NS_ENSURE_SUCCESS(rv
, rv
);
436 LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents
)
438 // This is only supposed to be called from the thread itself so it's not
440 NS_NOTREACHED("Shouldn't ever call this!");
441 return NS_ERROR_UNEXPECTED
;
445 LazyIdleThread::ProcessNextEvent(bool aMayWait
,
446 bool* aEventWasProcessed
)
448 // This is only supposed to be called from the thread itself so it's not
450 NS_NOTREACHED("Shouldn't ever call this!");
451 return NS_ERROR_UNEXPECTED
;
455 LazyIdleThread::Notify(nsITimer
* aTimer
)
457 ASSERT_OWNING_THREAD();
460 MutexAutoLock
lock(mMutex
);
462 if (mPendingEventCount
|| mIdleNotificationCount
) {
463 // Another event was scheduled since this timer was set. Don't do
464 // anything and wait for the timer to fire again.
469 nsresult rv
= ShutdownThread();
470 NS_ENSURE_SUCCESS(rv
, rv
);
476 LazyIdleThread::OnDispatchedEvent(nsIThreadInternal
* /*aThread */)
478 MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread
, "Wrong thread!");
483 LazyIdleThread::OnProcessNextEvent(nsIThreadInternal
* /* aThread */,
485 uint32_t /* aRecursionDepth */)
491 LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal
* /* aThread */,
492 uint32_t /* aRecursionDepth */)
494 bool shouldNotifyIdle
;
496 MutexAutoLock
lock(mMutex
);
498 MOZ_ASSERT(mPendingEventCount
, "Mismatched calls to observer methods!");
499 --mPendingEventCount
;
501 if (mThreadIsShuttingDown
) {
502 // We're shutting down, no need to fire any timer.
506 shouldNotifyIdle
= !mPendingEventCount
;
507 if (shouldNotifyIdle
) {
508 MOZ_ASSERT(mIdleNotificationCount
< UINT32_MAX
, "Way too many!");
509 mIdleNotificationCount
++;
513 if (shouldNotifyIdle
) {
514 nsCOMPtr
<nsIRunnable
> runnable
=
515 NS_NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer
);
516 NS_ENSURE_TRUE(runnable
, NS_ERROR_FAILURE
);
518 nsresult rv
= mOwningThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
519 NS_ENSURE_SUCCESS(rv
, rv
);
526 LazyIdleThread::Observe(nsISupports
* /* aSubject */,
528 const PRUnichar
* /* aData */)
530 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
531 MOZ_ASSERT(mShutdownMethod
== AutomaticShutdown
,
532 "Should not receive notifications if not AutomaticShutdown!");
533 MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic
), "Bad topic!");
539 } // namespace mozilla