forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			233 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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"
 | |
| #ifdef XP_WIN
 | |
| #  include "ThreadPoolCOMListener.h"
 | |
| #endif
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| // Created and destroyed on the main thread.
 | |
| static StaticAutoPtr<ReentrantMonitor> sMonitor;
 | |
| 
 | |
| // Hashtable, maps thread pool name to SharedThreadPool instance.
 | |
| // Modified only on the main thread.
 | |
| static StaticAutoPtr<nsTHashMap<nsCStringHashKey, SharedThreadPool*>> sPools;
 | |
| 
 | |
| static already_AddRefed<nsIThreadPool> 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<nsCStringHashKey, SharedThreadPool*>();
 | |
|   nsCOMPtr<nsIObserverService> obsService =
 | |
|       mozilla::services::GetObserverService();
 | |
|   nsCOMPtr<nsIObserver> 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([]() -> bool {
 | |
|     sMonitor->AssertNotCurrentThreadIn();
 | |
|     return IsEmpty();
 | |
|   });
 | |
| }
 | |
| 
 | |
| already_AddRefed<SharedThreadPool> SharedThreadPool::Get(
 | |
|     const nsCString& aName, uint32_t aThreadLimit) {
 | |
|   MOZ_ASSERT(sMonitor && sPools);
 | |
|   ReentrantMonitorAutoEnter mon(*sMonitor);
 | |
|   RefPtr<SharedThreadPool> pool;
 | |
| 
 | |
|   return sPools->WithEntryHandle(
 | |
|       aName, [&](auto&& entry) -> already_AddRefed<SharedThreadPool> {
 | |
|         if (entry) {
 | |
|           pool = entry.Data();
 | |
|           if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) {
 | |
|             NS_WARNING("Failed to set limits on thread pool");
 | |
|           }
 | |
|         } else {
 | |
|           nsCOMPtr<nsIThreadPool> 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) {
 | |
|   mEventTarget = aPool;
 | |
| }
 | |
| 
 | |
| 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<nsIThreadPool> CreateThreadPool(
 | |
|     const nsCString& aName) {
 | |
|   nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
 | |
| 
 | |
|   nsresult rv = pool->SetName(aName);
 | |
|   NS_ENSURE_SUCCESS(rv, nullptr);
 | |
| 
 | |
|   rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize);
 | |
|   NS_ENSURE_SUCCESS(rv, nullptr);
 | |
| 
 | |
| #ifdef XP_WIN
 | |
|   // Ensure MSCOM is initialized on the thread pools threads.
 | |
|   nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener();
 | |
|   rv = pool->SetListener(listener);
 | |
|   NS_ENSURE_SUCCESS(rv, nullptr);
 | |
| #endif
 | |
| 
 | |
|   return pool.forget();
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | 
