/* -*- 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 "mozilla/SharedThreadPool.h" #include "mozilla/Monitor.h" #include "mozilla/ReentrantMonitor.h" #include "mozilla/Services.h" #include "mozilla/SpinEventLoopUntil.h" #include "mozilla/StaticPtr.h" #include "nsTHashMap.h" #include "nsXPCOMCIDInternal.h" #include "nsComponentManagerUtils.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIThreadManager.h" #include "nsThreadPool.h" namespace mozilla { // Created and destroyed on the main thread. static StaticAutoPtr sMonitor; // Hashtable, maps thread pool name to SharedThreadPool instance. // Modified only on the main thread. static StaticAutoPtr> sPools; static already_AddRefed CreateThreadPool(const nsCString& aName); class SharedThreadPoolShutdownObserver : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER protected: virtual ~SharedThreadPoolShutdownObserver() = default; }; NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports) NS_IMETHODIMP SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads")); #ifdef EARLY_BETA_OR_EARLIER { ReentrantMonitorAutoEnter mon(*sMonitor); if (!sPools->IsEmpty()) { nsAutoCString str; for (const auto& key : sPools->Keys()) { str.AppendPrintf("\"%s\" ", nsAutoCString(key).get()); } printf_stderr( "SharedThreadPool in xpcom-shutdown-threads. Waiting for " "pools %s\n", str.get()); } } #endif SharedThreadPool::SpinUntilEmpty(); sMonitor = nullptr; sPools = nullptr; return NS_OK; } void SharedThreadPool::InitStatics() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!sMonitor && !sPools); sMonitor = new ReentrantMonitor("SharedThreadPool"); sPools = new nsTHashMap(); nsCOMPtr obsService = mozilla::services::GetObserverService(); nsCOMPtr obs = new SharedThreadPoolShutdownObserver(); obsService->AddObserver(obs, "xpcom-shutdown-threads", false); } /* static */ bool SharedThreadPool::IsEmpty() { ReentrantMonitorAutoEnter mon(*sMonitor); return !sPools->Count(); } /* static */ void SharedThreadPool::SpinUntilEmpty() { MOZ_ASSERT(NS_IsMainThread()); SpinEventLoopUntil("SharedThreadPool::SpinUntilEmpty"_ns, []() -> bool { sMonitor->AssertNotCurrentThreadIn(); return IsEmpty(); }); } already_AddRefed SharedThreadPool::Get( const nsCString& aName, uint32_t aThreadLimit) { MOZ_ASSERT(sMonitor && sPools); ReentrantMonitorAutoEnter mon(*sMonitor); RefPtr pool; return sPools->WithEntryHandle( aName, [&](auto&& entry) -> already_AddRefed { if (entry) { pool = entry.Data(); if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { NS_WARNING("Failed to set limits on thread pool"); } } else { nsCOMPtr threadPool(CreateThreadPool(aName)); if (NS_WARN_IF(!threadPool)) { sPools->Remove(aName); // XXX entry.Remove() return nullptr; } pool = new SharedThreadPool(aName, threadPool); // Set the thread and idle limits. Note that we don't rely on the // EnsureThreadLimitIsAtLeast() call below, as the default thread // limit is 4, and if aThreadLimit is less than 4 we'll end up with a // pool with 4 threads rather than what we expected; so we'll have // unexpected behaviour. nsresult rv = pool->SetThreadLimit(aThreadLimit); if (NS_WARN_IF(NS_FAILED(rv))) { sPools->Remove(aName); // XXX entry.Remove() return nullptr; } rv = pool->SetIdleThreadLimit(aThreadLimit); if (NS_WARN_IF(NS_FAILED(rv))) { sPools->Remove(aName); // XXX entry.Remove() return nullptr; } entry.Insert(pool.get()); } return pool.forget(); }); } NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) { MOZ_ASSERT(sMonitor); ReentrantMonitorAutoEnter mon(*sMonitor); MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); nsrefcnt count = ++mRefCnt; NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this)); return count; } NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) { MOZ_ASSERT(sMonitor); ReentrantMonitorAutoEnter mon(*sMonitor); nsrefcnt count = --mRefCnt; NS_LOG_RELEASE(this, count, "SharedThreadPool"); if (count) { return count; } // Remove SharedThreadPool from table of pools. sPools->Remove(mName); MOZ_ASSERT(!sPools->Get(mName)); // Dispatch an event to the main thread to call Shutdown() on // the nsIThreadPool. The Runnable here will add a refcount to the pool, // and when the Runnable releases the nsIThreadPool it will be deleted. NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool, &nsIThreadPool::Shutdown)); // Stabilize refcount, so that if something in the dtor QIs, it won't explode. mRefCnt = 1; delete this; return 0; } NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) SharedThreadPool::SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool) : mName(aName), mPool(aPool), mRefCnt(0) {} SharedThreadPool::~SharedThreadPool() = default; nsresult SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) { // We limit the number of threads that we use. Note that we // set the thread limit to the same as the idle limit so that we're not // constantly creating and destroying threads (see Bug 881954). When the // thread pool threads shutdown they dispatch an event to the main thread // to call nsIThread::Shutdown(), and if we're very busy that can take a // while to run, and we end up with dozens of extra threads. Note that // threads that are idle for 60 seconds are shutdown naturally. uint32_t existingLimit = 0; nsresult rv; rv = mPool->GetThreadLimit(&existingLimit); NS_ENSURE_SUCCESS(rv, rv); if (aLimit > existingLimit) { rv = mPool->SetThreadLimit(aLimit); NS_ENSURE_SUCCESS(rv, rv); } rv = mPool->GetIdleThreadLimit(&existingLimit); NS_ENSURE_SUCCESS(rv, rv); if (aLimit > existingLimit) { rv = mPool->SetIdleThreadLimit(aLimit); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } static already_AddRefed CreateThreadPool( const nsCString& aName) { nsCOMPtr pool = new nsThreadPool(); nsresult rv = pool->SetName(aName); NS_ENSURE_SUCCESS(rv, nullptr); rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); NS_ENSURE_SUCCESS(rv, nullptr); return pool.forget(); } } // namespace mozilla