Source code

Revision control

Copy as Markdown

Other Tools

/* 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/AppShutdown.h"
#include "mozilla/Logging.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/Monitor.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Services.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIThreadManager.h"
#include "nsThreadPool.h"
#include "nsTHashMap.h"
#include "nsXPCOMCIDInternal.h"
static mozilla::LazyLogModule sSharedThreadPoolLog("SharedThreadPool");
#define STP_LOG(level, msg, ...) \
MOZ_LOG_FMT(sSharedThreadPoolLog, level, msg, ##__VA_ARGS__)
namespace mozilla {
// Hash map, maps thread pool names to SharedThreadPool instances.
using PoolMap = nsTHashMap<nsCStringHashKey, RefPtr<SharedThreadPool>>;
static StaticMutex sPoolsMutex;
static StaticAutoPtr<PoolMap> sPools MOZ_GUARDED_BY(sPoolsMutex);
static bool sPoolsShutdownStarted MOZ_GUARDED_BY(sPoolsMutex) = false;
static already_AddRefed<nsIThreadPool> CreateThreadPool(const nsCString& aName,
uint32_t aThreadLimit) {
nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
nsresult rv = pool->SetName(aName);
NS_ENSURE_SUCCESS(rv, nullptr);
// Set the thread 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.
rv = pool->SetThreadLimit(aThreadLimit);
NS_ENSURE_SUCCESS(rv, nullptr);
// Note that we just keep the DEFAULT_IDLE_THREAD_LIMIT (currently 1).
// We keep any ever created SharedThreadPool until shutdown, but if
// it's not used, its threads will go away after a short while.
rv = pool->SetIdleThreadGraceTimeout(500);
NS_ENSURE_SUCCESS(rv, nullptr);
rv = pool->SetIdleThreadMaximumTimeout(5000);
NS_ENSURE_SUCCESS(rv, nullptr);
return pool.forget();
}
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"));
// During shutdown we do not allow the creation of new functional
// SharedThreadPools, but we do allow access to existing ones. This may lead
// to the situation that a dispatch to a SharedThreadPool fails because its
// underlying nsIThreadPool has already been shut down and is now defunct.
// However, it allows code to safely retrieve and use the same pool it is
// running on during shutdown.
nsTArray<RefPtr<SharedThreadPool>> pools;
{
StaticMutexAutoLock lock(sPoolsMutex);
sPoolsShutdownStarted = true;
AppendToArray(pools, sPools->Values());
}
for (const auto& pool : pools) {
// Note that nsThreadPool::Shutdown will annotate the crash report
// if it hangs.
STP_LOG(LogLevel::Debug, "Shutdown {}", fmt::ptr(pool.get()));
pool->Shutdown();
}
{
StaticMutexAutoLock lock(sPoolsMutex);
sPools->Clear();
}
pools.Clear();
return NS_OK;
}
void SharedThreadPool::InitStatics() {
MOZ_ASSERT(NS_IsMainThread());
StaticMutexAutoLock lock(sPoolsMutex);
MOZ_ASSERT(!sPools);
sPools = new nsTHashMap<nsCStringHashKey, RefPtr<SharedThreadPool>>();
nsCOMPtr<nsIObserverService> obsService =
mozilla::services::GetObserverService();
nsCOMPtr<nsIObserver> obs = new SharedThreadPoolShutdownObserver();
obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
}
already_AddRefed<SharedThreadPool> SharedThreadPool::Get(
StaticString aName, uint32_t aThreadLimit) {
StaticMutexAutoLock lock(sPoolsMutex);
MOZ_ASSERT(sPools);
nsCString name(aName);
return sPools->WithEntryHandle(
name, [&](auto&& entry) -> already_AddRefed<SharedThreadPool> {
RefPtr<SharedThreadPool> pool;
if (entry) {
pool = entry.Data();
if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) {
NS_WARNING("Failed to set limits on thread pool");
}
STP_LOG(LogLevel::Debug, "Existing {} found for {}",
fmt::ptr(pool.get()), name);
} else {
sPoolsMutex.AssertCurrentThreadOwns();
if (sPoolsShutdownStarted) {
// Do not allow the creation of new shared pools during shutdown.
return do_AddRef(new SharedThreadPool(nullptr));
}
nsCOMPtr<nsIThreadPool> threadPool(
CreateThreadPool(name, aThreadLimit));
if (NS_WARN_IF(!threadPool)) {
return do_AddRef(new SharedThreadPool(nullptr));
}
pool = new SharedThreadPool(threadPool);
// We do AddRef here and keep the pool alive in the hash map.
entry.Insert(pool.get());
STP_LOG(LogLevel::Debug, "New {} created for {}",
fmt::ptr(pool.get()), name);
}
return pool.forget();
});
}
NS_IMPL_ISUPPORTS(SharedThreadPool, nsIThreadPool, nsIEventTarget)
SharedThreadPool::SharedThreadPool(nsIThreadPool* aPool) : mPool(aPool) {}
SharedThreadPool::~SharedThreadPool() = default;
nsresult SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aThreadLimit) {
uint32_t existingLimit = 0;
nsresult rv;
rv = mPool->GetThreadLimit(&existingLimit);
NS_ENSURE_SUCCESS(rv, rv);
if (aThreadLimit > existingLimit) {
rv = mPool->SetThreadLimit(aThreadLimit);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
} // namespace mozilla
#undef STP_LOG