diff options
Diffstat (limited to 'xpcom/threads/LazyIdleThread.cpp')
-rw-r--r-- | xpcom/threads/LazyIdleThread.cpp | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp new file mode 100644 index 0000000000..f4f93de3ce --- /dev/null +++ b/xpcom/threads/LazyIdleThread.cpp @@ -0,0 +1,628 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LazyIdleThread.h" + +#include "nsIObserverService.h" + +#include "GeckoProfiler.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" + +#ifdef DEBUG +# define ASSERT_OWNING_THREAD() \ + do { \ + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \ + } while (0) +#else +# define ASSERT_OWNING_THREAD() /* nothing */ +#endif + +namespace mozilla { + +LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const nsACString& aName, + ShutdownMethod aShutdownMethod, + nsIObserver* aIdleObserver) + : mMutex("LazyIdleThread::mMutex"), + mOwningEventTarget(GetCurrentSerialEventTarget()), + mIdleObserver(aIdleObserver), + mQueuedRunnables(nullptr), + mIdleTimeoutMS(aIdleTimeoutMS), + mPendingEventCount(0), + mIdleNotificationCount(0), + mShutdownMethod(aShutdownMethod), + mShutdown(false), + mThreadIsShuttingDown(false), + mIdleTimeoutEnabled(true), + mName(aName) { + MOZ_ASSERT(mOwningEventTarget, "Need owning thread!"); +} + +LazyIdleThread::~LazyIdleThread() { + ASSERT_OWNING_THREAD(); + + Shutdown(); +} + +void LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver) { + ASSERT_OWNING_THREAD(); + + if (mShutdown) { + NS_WARNING_ASSERTION(!aObserver, + "Setting an observer after Shutdown was called!"); + return; + } + + mIdleObserver = aObserver; +} + +void LazyIdleThread::DisableIdleTimeout() { + ASSERT_OWNING_THREAD(); + if (!mIdleTimeoutEnabled) { + return; + } + mIdleTimeoutEnabled = false; + + if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) { + NS_WARNING("Failed to cancel timer!"); + } + + MutexAutoLock lock(mMutex); + + // Pretend we have a pending event to keep the idle timer from firing. + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); + mPendingEventCount++; +} + +void LazyIdleThread::EnableIdleTimeout() { + ASSERT_OWNING_THREAD(); + if (mIdleTimeoutEnabled) { + return; + } + mIdleTimeoutEnabled = true; + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); + --mPendingEventCount; + } + + if (mThread) { + nsCOMPtr<nsIRunnable> runnable(new Runnable("LazyIdleThreadDummyRunnable")); + if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch!"); + } + } +} + +void LazyIdleThread::PreDispatch() { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); + mPendingEventCount++; +} + +nsresult LazyIdleThread::EnsureThread() { + ASSERT_OWNING_THREAD(); + + if (mShutdown) { + return NS_ERROR_UNEXPECTED; + } + + if (mThread) { + return NS_OK; + } + + MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!"); + MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!"); + MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!"); + MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!"); + + nsresult rv; + + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> obs = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = obs->AddObserver(this, "xpcom-shutdown-threads", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mIdleTimer = NS_NewTimer(); + if (NS_WARN_IF(!mIdleTimer)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( + "LazyIdleThread::InitThread", this, &LazyIdleThread::InitThread); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + rv = NS_NewNamedThread(mName, getter_AddRefs(mThread), runnable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void LazyIdleThread::InitThread() { + // Happens on mThread but mThread may not be set yet... + + nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread())); + MOZ_ASSERT(thread, "This should always succeed!"); + + if (NS_FAILED(thread->SetObserver(this))) { + NS_WARNING("Failed to set thread observer!"); + } +} + +void LazyIdleThread::CleanupThread() { + nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread())); + MOZ_ASSERT(thread, "This should always succeed!"); + + if (NS_FAILED(thread->SetObserver(nullptr))) { + NS_WARNING("Failed to set thread observer!"); + } + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!"); + mThreadIsShuttingDown = true; + } +} + +void LazyIdleThread::ScheduleTimer() { + ASSERT_OWNING_THREAD(); + + bool shouldSchedule; + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!"); + --mIdleNotificationCount; + + shouldSchedule = !mIdleNotificationCount && !mPendingEventCount; + } + + if (mIdleTimer) { + if (NS_FAILED(mIdleTimer->Cancel())) { + NS_WARNING("Failed to cancel timer!"); + } + + if (shouldSchedule && NS_FAILED(mIdleTimer->InitWithCallback( + this, mIdleTimeoutMS, nsITimer::TYPE_ONE_SHOT))) { + NS_WARNING("Failed to schedule timer!"); + } + } +} + +nsresult LazyIdleThread::ShutdownThread() { + ASSERT_OWNING_THREAD(); + + // Before calling Shutdown() on the real thread we need to put a queue in + // place in case a runnable is posted to the thread while it's in the + // process of shutting down. This will be our queue. + AutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables; + + nsresult rv; + + // Make sure to cancel the shutdown timer before spinning the event loop + // during |mThread->Shutdown()| below. Otherwise the timer might fire and we + // could reenter here. + if (mIdleTimer) { + rv = mIdleTimer->Cancel(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mIdleTimer = nullptr; + } + + if (mThread) { + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + NS_WARNING_ASSERTION(obs, "Failed to get observer service!"); + + if (obs && + NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) { + NS_WARNING("Failed to remove observer!"); + } + } + + if (mIdleObserver) { + mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC, + nullptr); + } + +#ifdef DEBUG + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!"); + } +#endif + + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( + "LazyIdleThread::CleanupThread", this, &LazyIdleThread::CleanupThread); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + PreDispatch(); + + rv = mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Put the temporary queue in place before calling Shutdown(). + mQueuedRunnables = &queuedRunnables; + + if (NS_FAILED(mThread->Shutdown())) { + NS_ERROR("Failed to shutdown the thread!"); + } + + // Now unset the queue. + mQueuedRunnables = nullptr; + + mThread = nullptr; + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(!mPendingEventCount, "Huh?!"); + MOZ_ASSERT(!mIdleNotificationCount, "Huh?!"); + MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!"); + mThreadIsShuttingDown = false; + } + } + + // If our temporary queue has any runnables then we need to dispatch them. + if (queuedRunnables.Length()) { + // If the thread manager has gone away then these runnables will never run. + if (mShutdown) { + NS_ERROR("Runnables dispatched to LazyIdleThread will never run!"); + return NS_OK; + } + + // Re-dispatch the queued runnables. + for (uint32_t index = 0; index < queuedRunnables.Length(); index++) { + nsCOMPtr<nsIRunnable> runnable; + runnable.swap(queuedRunnables[index]); + MOZ_ASSERT(runnable, "Null runnable?!"); + + if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) { + NS_ERROR("Failed to re-dispatch queued runnable!"); + } + } + } + + return NS_OK; +} + +void LazyIdleThread::SelfDestruct() { + MOZ_ASSERT(mRefCnt == 1, "Bad refcount!"); + delete this; +} + +NS_IMPL_ADDREF(LazyIdleThread) + +NS_IMETHODIMP_(MozExternalRefCountType) +LazyIdleThread::Release() { + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "LazyIdleThread"); + + if (!count) { + // Stabilize refcount. + mRefCnt = 1; + + nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod( + "LazyIdleThread::SelfDestruct", this, &LazyIdleThread::SelfDestruct); + NS_WARNING_ASSERTION(runnable, "Couldn't make runnable!"); + + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + // The only way this could fail is if we're in shutdown, and in that case + // threads should have been joined already. Deleting here isn't dangerous + // anymore because we won't spin the event loop waiting to join the + // thread. + SelfDestruct(); + } + } + + return count; +} + +NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, nsIEventTarget, + nsISerialEventTarget, nsITimerCallback, + nsIThreadObserver, nsIObserver, nsINamed) + +NS_IMETHODIMP +LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + ASSERT_OWNING_THREAD(); + nsCOMPtr<nsIRunnable> event(aEvent); // avoid leaks + + // LazyIdleThread can't always support synchronous dispatch currently. + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_UNEXPECTED; + } + + // If our thread is shutting down then we can't actually dispatch right now. + // Queue this runnable for later. + if (UseRunnableQueue()) { + mQueuedRunnables->AppendElement(event); + return NS_OK; + } + + nsresult rv = EnsureThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + PreDispatch(); + + return mThread->Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) { + if (mThread) { + return mThread->GetRunningEventDelay(aDelay, aStart); + } + *aDelay = TimeDuration(); + *aStart = TimeStamp(); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) { + if (mThread) { + return mThread->SetRunningEventDelay(aDelay, aStart); + } + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) { + if (mThread) { + return mThread->IsOnCurrentThread(aIsOnCurrentThread); + } + + *aIsOnCurrentThread = false; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +LazyIdleThread::IsOnCurrentThreadInfallible() { + if (mThread) { + return mThread->IsOnCurrentThread(); + } + + return false; +} + +NS_IMETHODIMP +LazyIdleThread::GetPRThread(PRThread** aPRThread) { + if (mThread) { + return mThread->GetPRThread(aPRThread); + } + + *aPRThread = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +LazyIdleThread::GetCanInvokeJS(bool* aCanInvokeJS) { + *aCanInvokeJS = false; + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::SetCanInvokeJS(bool aCanInvokeJS) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::GetLastLongTaskEnd(TimeStamp* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::SetNameForWakeupTelemetry(const nsACString& aName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::AsyncShutdown() { + ASSERT_OWNING_THREAD(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::Shutdown() { + ASSERT_OWNING_THREAD(); + + mShutdown = true; + + nsresult rv = ShutdownThread(); + MOZ_ASSERT(!mThread, "Should have destroyed this by now!"); + + mIdleObserver = nullptr; + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents) { + // This is only supposed to be called from the thread itself so it's not + // implemented here. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::HasPendingHighPriorityEvents(bool* aHasPendingEvents) { + // This is only supposed to be called from the thread itself so it's not + // implemented here. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent, + EventQueuePriority aQueue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::ProcessNextEvent(bool aMayWait, bool* aEventWasProcessed) { + // This is only supposed to be called from the thread itself so it's not + // implemented here. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::Notify(nsITimer* aTimer) { + ASSERT_OWNING_THREAD(); + + { + MutexAutoLock lock(mMutex); + + if (mPendingEventCount || mIdleNotificationCount) { + // Another event was scheduled since this timer was set. Don't do + // anything and wait for the timer to fire again. + return NS_OK; + } + } + + nsresult rv = ShutdownThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::GetName(nsACString& aName) { + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::OnDispatchedEvent() { + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */, + bool /* aMayWait */) { + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, + bool aEventWasProcessed) { + bool shouldNotifyIdle; + { + MutexAutoLock lock(mMutex); + + if (aEventWasProcessed) { + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); + --mPendingEventCount; + } + + if (mThreadIsShuttingDown) { + // We're shutting down, no need to fire any timer. + return NS_OK; + } + + shouldNotifyIdle = !mPendingEventCount; + if (shouldNotifyIdle) { + MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!"); + mIdleNotificationCount++; + } + } + + if (shouldNotifyIdle) { + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( + "LazyIdleThread::ScheduleTimer", this, &LazyIdleThread::ScheduleTimer); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = + mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(mShutdownMethod == AutomaticShutdown, + "Should not receive notifications if not AutomaticShutdown!"); + MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); + + Shutdown(); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::GetEventTarget(nsIEventTarget** aEventTarget) { + nsCOMPtr<nsIEventTarget> target = this; + target.forget(aEventTarget); + return NS_OK; +} + +nsIEventTarget* LazyIdleThread::EventTarget() { return this; } + +nsISerialEventTarget* LazyIdleThread::SerialEventTarget() { return this; } + +} // namespace mozilla |