michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "LazyIdleThread.h" michael@0: michael@0: #include "nsIObserverService.h" michael@0: michael@0: #include "GeckoProfiler.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: #ifdef DEBUG michael@0: #define ASSERT_OWNING_THREAD() \ michael@0: PR_BEGIN_MACRO \ michael@0: nsIThread* currentThread = NS_GetCurrentThread(); \ michael@0: if (currentThread) { \ michael@0: nsCOMPtr current(do_QueryInterface(currentThread)); \ michael@0: nsCOMPtr test(do_QueryInterface(mOwningThread)); \ michael@0: MOZ_ASSERT(current == test, "Wrong thread!"); \ michael@0: } \ michael@0: PR_END_MACRO michael@0: #else michael@0: #define ASSERT_OWNING_THREAD() /* nothing */ michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, michael@0: const nsCSubstring& aName, michael@0: ShutdownMethod aShutdownMethod, michael@0: nsIObserver* aIdleObserver) michael@0: : mMutex("LazyIdleThread::mMutex"), michael@0: mOwningThread(NS_GetCurrentThread()), michael@0: mIdleObserver(aIdleObserver), michael@0: mQueuedRunnables(nullptr), michael@0: mIdleTimeoutMS(aIdleTimeoutMS), michael@0: mPendingEventCount(0), michael@0: mIdleNotificationCount(0), michael@0: mShutdownMethod(aShutdownMethod), michael@0: mShutdown(false), michael@0: mThreadIsShuttingDown(false), michael@0: mIdleTimeoutEnabled(true), michael@0: mName(aName) michael@0: { michael@0: MOZ_ASSERT(mOwningThread, "Need owning thread!"); michael@0: } michael@0: michael@0: LazyIdleThread::~LazyIdleThread() michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: Shutdown(); michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver) michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: if (mShutdown) { michael@0: NS_WARN_IF_FALSE(!aObserver, michael@0: "Setting an observer after Shutdown was called!"); michael@0: return; michael@0: } michael@0: michael@0: mIdleObserver = aObserver; michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::DisableIdleTimeout() michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: if (!mIdleTimeoutEnabled) { michael@0: return; michael@0: } michael@0: mIdleTimeoutEnabled = false; michael@0: michael@0: if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) { michael@0: NS_WARNING("Failed to cancel timer!"); michael@0: } michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: // Pretend we have a pending event to keep the idle timer from firing. michael@0: MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); michael@0: mPendingEventCount++; michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::EnableIdleTimeout() michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: if (mIdleTimeoutEnabled) { michael@0: return; michael@0: } michael@0: mIdleTimeoutEnabled = true; michael@0: michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); michael@0: --mPendingEventCount; michael@0: } michael@0: michael@0: if (mThread) { michael@0: nsCOMPtr runnable(new nsRunnable()); michael@0: if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) { michael@0: NS_WARNING("Failed to dispatch!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::PreDispatch() michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); michael@0: mPendingEventCount++; michael@0: } michael@0: michael@0: nsresult michael@0: LazyIdleThread::EnsureThread() michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: if (mShutdown) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (mThread) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!"); michael@0: MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!"); michael@0: MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!"); michael@0: MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { michael@0: nsCOMPtr obs = michael@0: do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: rv = obs->AddObserver(this, "xpcom-shutdown-threads", false); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: } michael@0: michael@0: mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); michael@0: if (NS_WARN_IF(!mIdleTimer)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &LazyIdleThread::InitThread); michael@0: if (NS_WARN_IF(!runnable)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: rv = NS_NewThread(getter_AddRefs(mThread), runnable); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::InitThread() michael@0: { michael@0: char aLocal; michael@0: profiler_register_thread(mName.get(), &aLocal); michael@0: michael@0: PR_SetCurrentThreadName(mName.get()); michael@0: michael@0: // Happens on mThread but mThread may not be set yet... michael@0: michael@0: nsCOMPtr thread(do_QueryInterface(NS_GetCurrentThread())); michael@0: MOZ_ASSERT(thread, "This should always succeed!"); michael@0: michael@0: if (NS_FAILED(thread->SetObserver(this))) { michael@0: NS_WARNING("Failed to set thread observer!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::CleanupThread() michael@0: { michael@0: nsCOMPtr thread(do_QueryInterface(NS_GetCurrentThread())); michael@0: MOZ_ASSERT(thread, "This should always succeed!"); michael@0: michael@0: if (NS_FAILED(thread->SetObserver(nullptr))) { michael@0: NS_WARNING("Failed to set thread observer!"); michael@0: } michael@0: michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!"); michael@0: mThreadIsShuttingDown = true; michael@0: } michael@0: michael@0: profiler_unregister_thread(); michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::ScheduleTimer() michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: bool shouldSchedule; michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!"); michael@0: --mIdleNotificationCount; michael@0: michael@0: shouldSchedule = !mIdleNotificationCount && !mPendingEventCount; michael@0: } michael@0: michael@0: if (NS_FAILED(mIdleTimer->Cancel())) { michael@0: NS_WARNING("Failed to cancel timer!"); michael@0: } michael@0: michael@0: if (shouldSchedule && michael@0: NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS, michael@0: nsITimer::TYPE_ONE_SHOT))) { michael@0: NS_WARNING("Failed to schedule timer!"); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: LazyIdleThread::ShutdownThread() michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: // Before calling Shutdown() on the real thread we need to put a queue in michael@0: // place in case a runnable is posted to the thread while it's in the michael@0: // process of shutting down. This will be our queue. michael@0: nsAutoTArray, 10> queuedRunnables; michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mThread) { michael@0: if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: NS_WARN_IF_FALSE(obs, "Failed to get observer service!"); michael@0: michael@0: if (obs && michael@0: NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) { michael@0: NS_WARNING("Failed to remove observer!"); michael@0: } michael@0: } michael@0: michael@0: if (mIdleObserver) { michael@0: mIdleObserver->Observe(static_cast(this), IDLE_THREAD_TOPIC, michael@0: nullptr); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!"); michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread); michael@0: if (NS_WARN_IF(!runnable)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: PreDispatch(); michael@0: michael@0: rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Put the temporary queue in place before calling Shutdown(). michael@0: mQueuedRunnables = &queuedRunnables; michael@0: michael@0: if (NS_FAILED(mThread->Shutdown())) { michael@0: NS_ERROR("Failed to shutdown the thread!"); michael@0: } michael@0: michael@0: // Now unset the queue. michael@0: mQueuedRunnables = nullptr; michael@0: michael@0: mThread = nullptr; michael@0: michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: MOZ_ASSERT(!mPendingEventCount, "Huh?!"); michael@0: MOZ_ASSERT(!mIdleNotificationCount, "Huh?!"); michael@0: MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!"); michael@0: mThreadIsShuttingDown = false; michael@0: } michael@0: } michael@0: michael@0: if (mIdleTimer) { michael@0: rv = mIdleTimer->Cancel(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: mIdleTimer = nullptr; michael@0: } michael@0: michael@0: // If our temporary queue has any runnables then we need to dispatch them. michael@0: if (queuedRunnables.Length()) { michael@0: // If the thread manager has gone away then these runnables will never run. michael@0: if (mShutdown) { michael@0: NS_ERROR("Runnables dispatched to LazyIdleThread will never run!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Re-dispatch the queued runnables. michael@0: for (uint32_t index = 0; index < queuedRunnables.Length(); index++) { michael@0: nsCOMPtr runnable; michael@0: runnable.swap(queuedRunnables[index]); michael@0: MOZ_ASSERT(runnable, "Null runnable?!"); michael@0: michael@0: if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) { michael@0: NS_ERROR("Failed to re-dispatch queued runnable!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: LazyIdleThread::SelfDestruct() michael@0: { michael@0: MOZ_ASSERT(mRefCnt == 1, "Bad refcount!"); michael@0: delete this; michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(LazyIdleThread) michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: LazyIdleThread::Release() michael@0: { michael@0: nsrefcnt count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "LazyIdleThread"); michael@0: michael@0: if (!count) { michael@0: // Stabilize refcount. michael@0: mRefCnt = 1; michael@0: michael@0: nsCOMPtr runnable = michael@0: NS_NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct); michael@0: NS_WARN_IF_FALSE(runnable, "Couldn't make runnable!"); michael@0: michael@0: if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: // The only way this could fail is if we're in shutdown, and in that case michael@0: // threads should have been joined already. Deleting here isn't dangerous michael@0: // anymore because we won't spin the event loop waiting to join the michael@0: // thread. michael@0: SelfDestruct(); michael@0: } michael@0: } michael@0: michael@0: return count; michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, michael@0: nsIEventTarget, michael@0: nsITimerCallback, michael@0: nsIThreadObserver, michael@0: nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::Dispatch(nsIRunnable* aEvent, michael@0: uint32_t aFlags) michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: // LazyIdleThread can't always support synchronous dispatch currently. michael@0: if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: // If our thread is shutting down then we can't actually dispatch right now. michael@0: // Queue this runnable for later. michael@0: if (UseRunnableQueue()) { michael@0: mQueuedRunnables->AppendElement(aEvent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = EnsureThread(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: PreDispatch(); michael@0: michael@0: return mThread->Dispatch(aEvent, aFlags); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) michael@0: { michael@0: if (mThread) { michael@0: return mThread->IsOnCurrentThread(aIsOnCurrentThread); michael@0: } michael@0: michael@0: *aIsOnCurrentThread = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::GetPRThread(PRThread** aPRThread) michael@0: { michael@0: if (mThread) { michael@0: return mThread->GetPRThread(aPRThread); michael@0: } michael@0: michael@0: *aPRThread = nullptr; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::Shutdown() michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: mShutdown = true; michael@0: michael@0: nsresult rv = ShutdownThread(); michael@0: MOZ_ASSERT(!mThread, "Should have destroyed this by now!"); michael@0: michael@0: mIdleObserver = nullptr; michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents) michael@0: { michael@0: // This is only supposed to be called from the thread itself so it's not michael@0: // implemented here. michael@0: NS_NOTREACHED("Shouldn't ever call this!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::ProcessNextEvent(bool aMayWait, michael@0: bool* aEventWasProcessed) michael@0: { michael@0: // This is only supposed to be called from the thread itself so it's not michael@0: // implemented here. michael@0: NS_NOTREACHED("Shouldn't ever call this!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::Notify(nsITimer* aTimer) michael@0: { michael@0: ASSERT_OWNING_THREAD(); michael@0: michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: if (mPendingEventCount || mIdleNotificationCount) { michael@0: // Another event was scheduled since this timer was set. Don't do michael@0: // anything and wait for the timer to fire again. michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsresult rv = ShutdownThread(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */) michael@0: { michael@0: MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */, michael@0: bool /* aMayWait */, michael@0: uint32_t /* aRecursionDepth */) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, michael@0: uint32_t /* aRecursionDepth */, michael@0: bool aEventWasProcessed) michael@0: { michael@0: bool shouldNotifyIdle; michael@0: { michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: if (aEventWasProcessed) { michael@0: MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); michael@0: --mPendingEventCount; michael@0: } michael@0: michael@0: if (mThreadIsShuttingDown) { michael@0: // We're shutting down, no need to fire any timer. michael@0: return NS_OK; michael@0: } michael@0: michael@0: shouldNotifyIdle = !mPendingEventCount; michael@0: if (shouldNotifyIdle) { michael@0: MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!"); michael@0: mIdleNotificationCount++; michael@0: } michael@0: } michael@0: michael@0: if (shouldNotifyIdle) { michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer); michael@0: if (NS_WARN_IF(!runnable)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsresult rv = mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LazyIdleThread::Observe(nsISupports* /* aSubject */, michael@0: const char* aTopic, michael@0: const char16_t* /* aData */) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_ASSERT(mShutdownMethod == AutomaticShutdown, michael@0: "Should not receive notifications if not AutomaticShutdown!"); michael@0: MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); michael@0: michael@0: Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla