summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/LazyIdleThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/threads/LazyIdleThread.cpp')
-rw-r--r--xpcom/threads/LazyIdleThread.cpp628
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