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/StaticPtr.h"
12 #include "nsDataHashtable.h"
13 #include "nsXPCOMCIDInternal.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsIObserver.h"
16 #include "nsIObserverService.h"
17 #include "nsIThreadManager.h"
18 #include "nsThreadPool.h"
20 # include "ThreadPoolCOMListener.h"
25 // Created and destroyed on the main thread.
26 static StaticAutoPtr
<ReentrantMonitor
> sMonitor
;
28 // Hashtable, maps thread pool name to SharedThreadPool instance.
29 // Modified only on the main thread.
30 static StaticAutoPtr
<nsDataHashtable
<nsCStringHashKey
, SharedThreadPool
*>>
33 static already_AddRefed
<nsIThreadPool
> CreateThreadPool(const nsCString
& aName
);
35 class SharedThreadPoolShutdownObserver
: public nsIObserver
{
40 virtual ~SharedThreadPoolShutdownObserver() = default;
43 NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver
, nsIObserver
, nsISupports
)
46 SharedThreadPoolShutdownObserver::Observe(nsISupports
* aSubject
,
48 const char16_t
* aData
) {
49 MOZ_RELEASE_ASSERT(!strcmp(aTopic
, "xpcom-shutdown-threads"));
50 #ifdef EARLY_BETA_OR_EARLIER
52 ReentrantMonitorAutoEnter
mon(*sMonitor
);
53 if (!sPools
->Iter().Done()) {
55 for (auto i
= sPools
->Iter(); !i
.Done(); i
.Next()) {
56 str
.AppendPrintf("\"%s\" ", nsAutoCString(i
.Key()).get());
59 "SharedThreadPool in xpcom-shutdown-threads. Waiting for "
65 SharedThreadPool::SpinUntilEmpty();
71 void SharedThreadPool::InitStatics() {
72 MOZ_ASSERT(NS_IsMainThread());
73 MOZ_ASSERT(!sMonitor
&& !sPools
);
74 sMonitor
= new ReentrantMonitor("SharedThreadPool");
75 sPools
= new nsDataHashtable
<nsCStringHashKey
, SharedThreadPool
*>();
76 nsCOMPtr
<nsIObserverService
> obsService
=
77 mozilla::services::GetObserverService();
78 nsCOMPtr
<nsIObserver
> obs
= new SharedThreadPoolShutdownObserver();
79 obsService
->AddObserver(obs
, "xpcom-shutdown-threads", false);
83 bool SharedThreadPool::IsEmpty() {
84 ReentrantMonitorAutoEnter
mon(*sMonitor
);
85 return !sPools
->Count();
89 void SharedThreadPool::SpinUntilEmpty() {
90 MOZ_ASSERT(NS_IsMainThread());
91 SpinEventLoopUntil([]() -> bool {
92 sMonitor
->AssertNotCurrentThreadIn();
97 already_AddRefed
<SharedThreadPool
> SharedThreadPool::Get(
98 const nsCString
& aName
, uint32_t aThreadLimit
) {
99 MOZ_ASSERT(sMonitor
&& sPools
);
100 ReentrantMonitorAutoEnter
mon(*sMonitor
);
101 RefPtr
<SharedThreadPool
> pool
;
104 if (auto entry
= sPools
->LookupForAdd(aName
)) {
106 if (NS_FAILED(pool
->EnsureThreadLimitIsAtLeast(aThreadLimit
))) {
107 NS_WARNING("Failed to set limits on thread pool");
110 nsCOMPtr
<nsIThreadPool
> threadPool(CreateThreadPool(aName
));
111 if (NS_WARN_IF(!threadPool
)) {
112 sPools
->Remove(aName
); // XXX entry.Remove()
115 pool
= new SharedThreadPool(aName
, threadPool
);
117 // Set the thread and idle limits. Note that we don't rely on the
118 // EnsureThreadLimitIsAtLeast() call below, as the default thread limit
119 // is 4, and if aThreadLimit is less than 4 we'll end up with a pool
120 // with 4 threads rather than what we expected; so we'll have unexpected
122 rv
= pool
->SetThreadLimit(aThreadLimit
);
123 if (NS_WARN_IF(NS_FAILED(rv
))) {
124 sPools
->Remove(aName
); // XXX entry.Remove()
128 rv
= pool
->SetIdleThreadLimit(aThreadLimit
);
129 if (NS_WARN_IF(NS_FAILED(rv
))) {
130 sPools
->Remove(aName
); // XXX entry.Remove()
134 entry
.OrInsert([pool
]() { return pool
.get(); });
137 return pool
.forget();
140 NS_IMETHODIMP_(MozExternalRefCountType
) SharedThreadPool::AddRef(void) {
141 MOZ_ASSERT(sMonitor
);
142 ReentrantMonitorAutoEnter
mon(*sMonitor
);
143 MOZ_ASSERT(int32_t(mRefCnt
) >= 0, "illegal refcnt");
144 nsrefcnt count
= ++mRefCnt
;
145 NS_LOG_ADDREF(this, count
, "SharedThreadPool", sizeof(*this));
149 NS_IMETHODIMP_(MozExternalRefCountType
) SharedThreadPool::Release(void) {
150 MOZ_ASSERT(sMonitor
);
151 ReentrantMonitorAutoEnter
mon(*sMonitor
);
152 nsrefcnt count
= --mRefCnt
;
153 NS_LOG_RELEASE(this, count
, "SharedThreadPool");
158 // Remove SharedThreadPool from table of pools.
159 sPools
->Remove(mName
);
160 MOZ_ASSERT(!sPools
->Get(mName
));
162 // Dispatch an event to the main thread to call Shutdown() on
163 // the nsIThreadPool. The Runnable here will add a refcount to the pool,
164 // and when the Runnable releases the nsIThreadPool it will be deleted.
165 NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool
,
166 &nsIThreadPool::Shutdown
));
168 // Stabilize refcount, so that if something in the dtor QIs, it won't explode.
174 NS_IMPL_QUERY_INTERFACE(SharedThreadPool
, nsIThreadPool
, nsIEventTarget
)
176 SharedThreadPool::SharedThreadPool(const nsCString
& aName
, nsIThreadPool
* aPool
)
177 : mName(aName
), mPool(aPool
), mRefCnt(0) {
178 mEventTarget
= aPool
;
181 SharedThreadPool::~SharedThreadPool() = default;
183 nsresult
SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit
) {
184 // We limit the number of threads that we use. Note that we
185 // set the thread limit to the same as the idle limit so that we're not
186 // constantly creating and destroying threads (see Bug 881954). When the
187 // thread pool threads shutdown they dispatch an event to the main thread
188 // to call nsIThread::Shutdown(), and if we're very busy that can take a
189 // while to run, and we end up with dozens of extra threads. Note that
190 // threads that are idle for 60 seconds are shutdown naturally.
191 uint32_t existingLimit
= 0;
194 rv
= mPool
->GetThreadLimit(&existingLimit
);
195 NS_ENSURE_SUCCESS(rv
, rv
);
196 if (aLimit
> existingLimit
) {
197 rv
= mPool
->SetThreadLimit(aLimit
);
198 NS_ENSURE_SUCCESS(rv
, rv
);
201 rv
= mPool
->GetIdleThreadLimit(&existingLimit
);
202 NS_ENSURE_SUCCESS(rv
, rv
);
203 if (aLimit
> existingLimit
) {
204 rv
= mPool
->SetIdleThreadLimit(aLimit
);
205 NS_ENSURE_SUCCESS(rv
, rv
);
211 static already_AddRefed
<nsIThreadPool
> CreateThreadPool(
212 const nsCString
& aName
) {
213 nsCOMPtr
<nsIThreadPool
> pool
= new nsThreadPool();
215 nsresult rv
= pool
->SetName(aName
);
216 NS_ENSURE_SUCCESS(rv
, nullptr);
218 rv
= pool
->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize
);
219 NS_ENSURE_SUCCESS(rv
, nullptr);
222 // Ensure MSCOM is initialized on the thread pools threads.
223 nsCOMPtr
<nsIThreadPoolListener
> listener
= new MSCOMInitThreadPoolListener();
224 rv
= pool
->SetListener(listener
);
225 NS_ENSURE_SUCCESS(rv
, nullptr);
228 return pool
.forget();
231 } // namespace mozilla