1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/SharedThreadPool.h"
8 #include "mozilla/Monitor.h"
9 #include "mozilla/ReentrantMonitor.h"
10 #include "mozilla/Services.h"
11 #include "mozilla/SpinEventLoopUntil.h"
12 #include "mozilla/StaticPtr.h"
13 #include "nsTHashMap.h"
14 #include "nsXPCOMCIDInternal.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsIObserver.h"
17 #include "nsIObserverService.h"
18 #include "nsIThreadManager.h"
19 #include "nsThreadPool.h"
23 // Created and destroyed on the main thread.
24 static StaticAutoPtr
<ReentrantMonitor
> sMonitor
;
26 // Hashtable, maps thread pool name to SharedThreadPool instance.
27 // Modified only on the main thread.
28 static StaticAutoPtr
<nsTHashMap
<nsCStringHashKey
, SharedThreadPool
*>> sPools
;
30 static already_AddRefed
<nsIThreadPool
> CreateThreadPool(const nsCString
& aName
);
32 class SharedThreadPoolShutdownObserver
: public nsIObserver
{
37 virtual ~SharedThreadPoolShutdownObserver() = default;
40 NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver
, nsIObserver
, nsISupports
)
43 SharedThreadPoolShutdownObserver::Observe(nsISupports
* aSubject
,
45 const char16_t
* aData
) {
46 MOZ_RELEASE_ASSERT(!strcmp(aTopic
, "xpcom-shutdown-threads"));
47 #ifdef EARLY_BETA_OR_EARLIER
49 ReentrantMonitorAutoEnter
mon(*sMonitor
);
50 if (!sPools
->IsEmpty()) {
52 for (const auto& key
: sPools
->Keys()) {
53 str
.AppendPrintf("\"%s\" ", nsAutoCString(key
).get());
56 "SharedThreadPool in xpcom-shutdown-threads. Waiting for "
62 SharedThreadPool::SpinUntilEmpty();
68 void SharedThreadPool::InitStatics() {
69 MOZ_ASSERT(NS_IsMainThread());
70 MOZ_ASSERT(!sMonitor
&& !sPools
);
71 sMonitor
= new ReentrantMonitor("SharedThreadPool");
72 sPools
= new nsTHashMap
<nsCStringHashKey
, SharedThreadPool
*>();
73 nsCOMPtr
<nsIObserverService
> obsService
=
74 mozilla::services::GetObserverService();
75 nsCOMPtr
<nsIObserver
> obs
= new SharedThreadPoolShutdownObserver();
76 obsService
->AddObserver(obs
, "xpcom-shutdown-threads", false);
80 bool SharedThreadPool::IsEmpty() {
81 ReentrantMonitorAutoEnter
mon(*sMonitor
);
82 return !sPools
->Count();
86 void SharedThreadPool::SpinUntilEmpty() {
87 MOZ_ASSERT(NS_IsMainThread());
88 SpinEventLoopUntil("SharedThreadPool::SpinUntilEmpty"_ns
, []() -> bool {
89 sMonitor
->AssertNotCurrentThreadIn();
94 already_AddRefed
<SharedThreadPool
> SharedThreadPool::Get(
95 const nsCString
& aName
, uint32_t aThreadLimit
) {
96 MOZ_ASSERT(sMonitor
&& sPools
);
97 ReentrantMonitorAutoEnter
mon(*sMonitor
);
98 RefPtr
<SharedThreadPool
> pool
;
100 return sPools
->WithEntryHandle(
101 aName
, [&](auto&& entry
) -> already_AddRefed
<SharedThreadPool
> {
104 if (NS_FAILED(pool
->EnsureThreadLimitIsAtLeast(aThreadLimit
))) {
105 NS_WARNING("Failed to set limits on thread pool");
108 nsCOMPtr
<nsIThreadPool
> threadPool(CreateThreadPool(aName
));
109 if (NS_WARN_IF(!threadPool
)) {
110 sPools
->Remove(aName
); // XXX entry.Remove()
113 pool
= new SharedThreadPool(aName
, threadPool
);
115 // Set the thread and idle limits. Note that we don't rely on the
116 // EnsureThreadLimitIsAtLeast() call below, as the default thread
117 // limit is 4, and if aThreadLimit is less than 4 we'll end up with a
118 // pool with 4 threads rather than what we expected; so we'll have
119 // unexpected behaviour.
120 nsresult rv
= pool
->SetThreadLimit(aThreadLimit
);
121 if (NS_WARN_IF(NS_FAILED(rv
))) {
122 sPools
->Remove(aName
); // XXX entry.Remove()
126 rv
= pool
->SetIdleThreadLimit(aThreadLimit
);
127 if (NS_WARN_IF(NS_FAILED(rv
))) {
128 sPools
->Remove(aName
); // XXX entry.Remove()
132 entry
.Insert(pool
.get());
135 return pool
.forget();
139 NS_IMETHODIMP_(MozExternalRefCountType
) SharedThreadPool::AddRef(void) {
140 MOZ_ASSERT(sMonitor
);
141 ReentrantMonitorAutoEnter
mon(*sMonitor
);
142 MOZ_ASSERT(int32_t(mRefCnt
) >= 0, "illegal refcnt");
143 nsrefcnt count
= ++mRefCnt
;
144 NS_LOG_ADDREF(this, count
, "SharedThreadPool", sizeof(*this));
148 NS_IMETHODIMP_(MozExternalRefCountType
) SharedThreadPool::Release(void) {
149 MOZ_ASSERT(sMonitor
);
150 ReentrantMonitorAutoEnter
mon(*sMonitor
);
151 nsrefcnt count
= --mRefCnt
;
152 NS_LOG_RELEASE(this, count
, "SharedThreadPool");
157 // Remove SharedThreadPool from table of pools.
158 sPools
->Remove(mName
);
159 MOZ_ASSERT(!sPools
->Get(mName
));
161 // Dispatch an event to the main thread to call Shutdown() on
162 // the nsIThreadPool. The Runnable here will add a refcount to the pool,
163 // and when the Runnable releases the nsIThreadPool it will be deleted.
164 NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool
,
165 &nsIThreadPool::Shutdown
));
167 // Stabilize refcount, so that if something in the dtor QIs, it won't explode.
173 NS_IMPL_QUERY_INTERFACE(SharedThreadPool
, nsIThreadPool
, nsIEventTarget
)
175 SharedThreadPool::SharedThreadPool(const nsCString
& aName
, nsIThreadPool
* aPool
)
176 : mName(aName
), mPool(aPool
), mRefCnt(0) {
177 mEventTarget
= aPool
;
180 SharedThreadPool::~SharedThreadPool() = default;
182 nsresult
SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit
) {
183 // We limit the number of threads that we use. Note that we
184 // set the thread limit to the same as the idle limit so that we're not
185 // constantly creating and destroying threads (see Bug 881954). When the
186 // thread pool threads shutdown they dispatch an event to the main thread
187 // to call nsIThread::Shutdown(), and if we're very busy that can take a
188 // while to run, and we end up with dozens of extra threads. Note that
189 // threads that are idle for 60 seconds are shutdown naturally.
190 uint32_t existingLimit
= 0;
193 rv
= mPool
->GetThreadLimit(&existingLimit
);
194 NS_ENSURE_SUCCESS(rv
, rv
);
195 if (aLimit
> existingLimit
) {
196 rv
= mPool
->SetThreadLimit(aLimit
);
197 NS_ENSURE_SUCCESS(rv
, rv
);
200 rv
= mPool
->GetIdleThreadLimit(&existingLimit
);
201 NS_ENSURE_SUCCESS(rv
, rv
);
202 if (aLimit
> existingLimit
) {
203 rv
= mPool
->SetIdleThreadLimit(aLimit
);
204 NS_ENSURE_SUCCESS(rv
, rv
);
210 static already_AddRefed
<nsIThreadPool
> CreateThreadPool(
211 const nsCString
& aName
) {
212 nsCOMPtr
<nsIThreadPool
> pool
= new nsThreadPool();
214 nsresult rv
= pool
->SetName(aName
);
215 NS_ENSURE_SUCCESS(rv
, nullptr);
217 rv
= pool
->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize
);
218 NS_ENSURE_SUCCESS(rv
, nullptr);
220 return pool
.forget();
223 } // namespace mozilla