Backed out changeset 4887f7d34df2 (bug 896896) for causing bug 933733. a=lsblakk
[gecko.git] / xpcom / threads / LazyIdleThread.cpp
blob0cf14ad0acd5b1df96552e86a07c708af4cd2153
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"
17 #ifdef DEBUG
18 #define ASSERT_OWNING_THREAD() \
19 PR_BEGIN_MACRO \
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!"); \
25 } \
26 PR_END_MACRO
27 #else
28 #define ASSERT_OWNING_THREAD() /* nothing */
29 #endif
31 namespace mozilla {
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),
45 mShutdown(false),
46 mThreadIsShuttingDown(false),
47 mIdleTimeoutEnabled(true),
48 mName(aName)
50 MOZ_ASSERT(mOwningThread, "Need owning thread!");
53 LazyIdleThread::~LazyIdleThread()
55 ASSERT_OWNING_THREAD();
57 Shutdown();
60 void
61 LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver)
63 ASSERT_OWNING_THREAD();
65 if (mShutdown) {
66 NS_WARN_IF_FALSE(!aObserver,
67 "Setting an observer after Shutdown was called!");
68 return;
71 mIdleObserver = aObserver;
74 void
75 LazyIdleThread::DisableIdleTimeout()
77 ASSERT_OWNING_THREAD();
78 if (!mIdleTimeoutEnabled) {
79 return;
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!");
91 mPendingEventCount++;
94 void
95 LazyIdleThread::EnableIdleTimeout()
97 ASSERT_OWNING_THREAD();
98 if (mIdleTimeoutEnabled) {
99 return;
101 mIdleTimeoutEnabled = true;
104 MutexAutoLock lock(mMutex);
106 MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
107 --mPendingEventCount;
110 if (mThread) {
111 nsCOMPtr<nsIRunnable> runnable(new nsRunnable());
112 if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) {
113 NS_WARNING("Failed to dispatch!");
118 void
119 LazyIdleThread::PreDispatch()
121 MutexAutoLock lock(mMutex);
123 MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
124 mPendingEventCount++;
127 nsresult
128 LazyIdleThread::EnsureThread()
130 ASSERT_OWNING_THREAD();
132 if (mShutdown) {
133 return NS_ERROR_UNEXPECTED;
136 if (mThread) {
137 return NS_OK;
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!");
145 nsresult rv;
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);
166 return NS_OK;
169 void
170 LazyIdleThread::InitThread()
172 char aLocal;
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!");
187 void
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();
207 void
208 LazyIdleThread::ScheduleTimer()
210 ASSERT_OWNING_THREAD();
212 bool shouldSchedule;
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!");
233 nsresult
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;
243 nsresult rv;
245 if (mThread) {
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!");
251 if (obs &&
252 NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) {
253 NS_WARNING("Failed to remove observer!");
257 if (mIdleObserver) {
258 mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC,
259 nullptr);
262 #ifdef DEBUG
264 MutexAutoLock lock(mMutex);
265 MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!");
267 #endif
269 nsCOMPtr<nsIRunnable> runnable =
270 NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread);
271 NS_ENSURE_TRUE(runnable, NS_ERROR_FAILURE);
273 PreDispatch();
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;
288 mThread = nullptr;
291 MutexAutoLock lock(mMutex);
293 MOZ_ASSERT(!mPendingEventCount, "Huh?!");
294 MOZ_ASSERT(!mIdleNotificationCount, "Huh?!");
295 MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!");
296 mThreadIsShuttingDown = false;
300 if (mIdleTimer) {
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.
310 if (mShutdown) {
311 NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
312 return NS_OK;
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!");
327 return NS_OK;
330 void
331 LazyIdleThread::SelfDestruct()
333 MOZ_ASSERT(mRefCnt == 1, "Bad refcount!");
334 delete this;
337 NS_IMPL_ADDREF(LazyIdleThread)
339 NS_IMETHODIMP_(nsrefcnt)
340 LazyIdleThread::Release()
342 nsrefcnt count = --mRefCnt;
343 NS_LOG_RELEASE(this, count, "LazyIdleThread");
345 if (!count) {
346 // Stabilize refcount.
347 mRefCnt = 1;
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
358 // thread.
359 SelfDestruct();
363 return count;
366 NS_IMPL_QUERY_INTERFACE5(LazyIdleThread, nsIThread,
367 nsIEventTarget,
368 nsITimerCallback,
369 nsIThreadObserver,
370 nsIObserver)
372 NS_IMETHODIMP
373 LazyIdleThread::Dispatch(nsIRunnable* aEvent,
374 uint32_t aFlags)
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);
385 return NS_OK;
388 nsresult rv = EnsureThread();
389 NS_ENSURE_SUCCESS(rv, rv);
391 PreDispatch();
393 return mThread->Dispatch(aEvent, aFlags);
396 NS_IMETHODIMP
397 LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread)
399 if (mThread) {
400 return mThread->IsOnCurrentThread(aIsOnCurrentThread);
403 *aIsOnCurrentThread = false;
404 return NS_OK;
407 NS_IMETHODIMP
408 LazyIdleThread::GetPRThread(PRThread** aPRThread)
410 if (mThread) {
411 return mThread->GetPRThread(aPRThread);
414 *aPRThread = nullptr;
415 return NS_ERROR_NOT_AVAILABLE;
418 NS_IMETHODIMP
419 LazyIdleThread::Shutdown()
421 ASSERT_OWNING_THREAD();
423 mShutdown = true;
425 nsresult rv = ShutdownThread();
426 MOZ_ASSERT(!mThread, "Should have destroyed this by now!");
428 mIdleObserver = nullptr;
430 NS_ENSURE_SUCCESS(rv, rv);
432 return NS_OK;
435 NS_IMETHODIMP
436 LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents)
438 // This is only supposed to be called from the thread itself so it's not
439 // implemented here.
440 NS_NOTREACHED("Shouldn't ever call this!");
441 return NS_ERROR_UNEXPECTED;
444 NS_IMETHODIMP
445 LazyIdleThread::ProcessNextEvent(bool aMayWait,
446 bool* aEventWasProcessed)
448 // This is only supposed to be called from the thread itself so it's not
449 // implemented here.
450 NS_NOTREACHED("Shouldn't ever call this!");
451 return NS_ERROR_UNEXPECTED;
454 NS_IMETHODIMP
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.
465 return NS_OK;
469 nsresult rv = ShutdownThread();
470 NS_ENSURE_SUCCESS(rv, rv);
472 return NS_OK;
475 NS_IMETHODIMP
476 LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
478 MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!");
479 return NS_OK;
482 NS_IMETHODIMP
483 LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
484 bool /* aMayWait */,
485 uint32_t /* aRecursionDepth */)
487 return NS_OK;
490 NS_IMETHODIMP
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.
503 return NS_OK;
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);
522 return NS_OK;
525 NS_IMETHODIMP
526 LazyIdleThread::Observe(nsISupports* /* aSubject */,
527 const char* aTopic,
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!");
535 Shutdown();
536 return NS_OK;
539 } // namespace mozilla