summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/SharedThreadPool.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/threads/SharedThreadPool.cpp221
1 files changed, 221 insertions, 0 deletions
diff --git a/xpcom/threads/SharedThreadPool.cpp b/xpcom/threads/SharedThreadPool.cpp
new file mode 100644
index 0000000000..a2de1c4495
--- /dev/null
+++ b/xpcom/threads/SharedThreadPool.cpp
@@ -0,0 +1,221 @@
+/* -*- 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<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("SharedThreadPool::SpinUntilEmpty"_ns, []() -> 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) {}
+
+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);
+
+ return pool.forget();
+}
+
+} // namespace mozilla