Line data Source code
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/. */
6 :
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 : #ifdef XP_WIN
18 : #include "ThreadPoolCOMListener.h"
19 : #endif
20 :
21 : namespace mozilla {
22 :
23 : // Created and destroyed on the main thread.
24 3 : static StaticAutoPtr<ReentrantMonitor> sMonitor;
25 :
26 : // Hashtable, maps thread pool name to SharedThreadPool instance.
27 : // Modified only on the main thread.
28 3 : static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>> sPools;
29 :
30 : static already_AddRefed<nsIThreadPool>
31 : CreateThreadPool(const nsCString& aName);
32 :
33 3 : class SharedThreadPoolShutdownObserver : public nsIObserver
34 : {
35 : public:
36 : NS_DECL_ISUPPORTS
37 : NS_DECL_NSIOBSERVER
38 : protected:
39 0 : virtual ~SharedThreadPoolShutdownObserver() {}
40 : };
41 :
42 18 : NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports)
43 :
44 : NS_IMETHODIMP
45 0 : SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject, const char *aTopic,
46 : const char16_t *aData)
47 : {
48 0 : MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads"));
49 0 : SharedThreadPool::SpinUntilEmpty();
50 0 : sMonitor = nullptr;
51 0 : sPools = nullptr;
52 0 : return NS_OK;
53 : }
54 :
55 : void
56 3 : SharedThreadPool::InitStatics()
57 : {
58 3 : MOZ_ASSERT(NS_IsMainThread());
59 3 : MOZ_ASSERT(!sMonitor && !sPools);
60 3 : sMonitor = new ReentrantMonitor("SharedThreadPool");
61 3 : sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>();
62 6 : nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
63 6 : nsCOMPtr<nsIObserver> obs = new SharedThreadPoolShutdownObserver();
64 3 : obsService->AddObserver(obs, "xpcom-shutdown-threads", false);
65 3 : }
66 :
67 : /* static */
68 : bool
69 0 : SharedThreadPool::IsEmpty()
70 : {
71 0 : ReentrantMonitorAutoEnter mon(*sMonitor);
72 0 : return !sPools->Count();
73 : }
74 :
75 : /* static */
76 : void
77 0 : SharedThreadPool::SpinUntilEmpty()
78 : {
79 0 : MOZ_ASSERT(NS_IsMainThread());
80 0 : SpinEventLoopUntil([]() -> bool {
81 0 : sMonitor->AssertNotCurrentThreadIn();
82 0 : return IsEmpty();
83 0 : });
84 0 : }
85 :
86 : already_AddRefed<SharedThreadPool>
87 0 : SharedThreadPool::Get(const nsCString& aName, uint32_t aThreadLimit)
88 : {
89 0 : MOZ_ASSERT(sMonitor && sPools);
90 0 : ReentrantMonitorAutoEnter mon(*sMonitor);
91 0 : SharedThreadPool* pool = nullptr;
92 : nsresult rv;
93 :
94 0 : if (auto entry = sPools->LookupForAdd(aName)) {
95 0 : pool = entry.Data();
96 0 : if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) {
97 0 : NS_WARNING("Failed to set limits on thread pool");
98 : }
99 : } else {
100 0 : nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName));
101 0 : if (NS_WARN_IF(!threadPool)) {
102 0 : sPools->Remove(aName); // XXX entry.Remove()
103 0 : return nullptr;
104 : }
105 0 : pool = new SharedThreadPool(aName, threadPool);
106 :
107 : // Set the thread and idle limits. Note that we don't rely on the
108 : // EnsureThreadLimitIsAtLeast() call below, as the default thread limit
109 : // is 4, and if aThreadLimit is less than 4 we'll end up with a pool
110 : // with 4 threads rather than what we expected; so we'll have unexpected
111 : // behaviour.
112 0 : rv = pool->SetThreadLimit(aThreadLimit);
113 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
114 0 : sPools->Remove(aName); // XXX entry.Remove()
115 0 : return nullptr;
116 : }
117 :
118 0 : rv = pool->SetIdleThreadLimit(aThreadLimit);
119 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
120 0 : sPools->Remove(aName); // XXX entry.Remove()
121 0 : return nullptr;
122 : }
123 :
124 0 : entry.OrInsert([pool] () { return pool; });
125 : }
126 :
127 0 : MOZ_ASSERT(pool);
128 0 : RefPtr<SharedThreadPool> instance(pool);
129 0 : return instance.forget();
130 : }
131 :
132 0 : NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void)
133 : {
134 0 : MOZ_ASSERT(sMonitor);
135 0 : ReentrantMonitorAutoEnter mon(*sMonitor);
136 0 : MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
137 0 : nsrefcnt count = ++mRefCnt;
138 0 : NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this));
139 0 : return count;
140 : }
141 :
142 0 : NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void)
143 : {
144 0 : MOZ_ASSERT(sMonitor);
145 0 : ReentrantMonitorAutoEnter mon(*sMonitor);
146 0 : nsrefcnt count = --mRefCnt;
147 0 : NS_LOG_RELEASE(this, count, "SharedThreadPool");
148 0 : if (count) {
149 0 : return count;
150 : }
151 :
152 : // Remove SharedThreadPool from table of pools.
153 0 : sPools->Remove(mName);
154 0 : MOZ_ASSERT(!sPools->Get(mName));
155 :
156 : // Dispatch an event to the main thread to call Shutdown() on
157 : // the nsIThreadPool. The Runnable here will add a refcount to the pool,
158 : // and when the Runnable releases the nsIThreadPool it will be deleted.
159 0 : NS_DispatchToMainThread(NewRunnableMethod(
160 0 : "nsIThreadPool::Shutdown", mPool, &nsIThreadPool::Shutdown));
161 :
162 : // Stabilize refcount, so that if something in the dtor QIs, it won't explode.
163 0 : mRefCnt = 1;
164 0 : delete this;
165 0 : return 0;
166 : }
167 :
168 0 : NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget)
169 :
170 0 : SharedThreadPool::SharedThreadPool(const nsCString& aName,
171 0 : nsIThreadPool* aPool)
172 : : mName(aName)
173 : , mPool(aPool)
174 0 : , mRefCnt(0)
175 : {
176 0 : mEventTarget = do_QueryInterface(aPool);
177 0 : }
178 :
179 0 : SharedThreadPool::~SharedThreadPool()
180 : {
181 0 : }
182 :
183 : nsresult
184 0 : SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit)
185 : {
186 : // We limit the number of threads that we use. Note that we
187 : // set the thread limit to the same as the idle limit so that we're not
188 : // constantly creating and destroying threads (see Bug 881954). When the
189 : // thread pool threads shutdown they dispatch an event to the main thread
190 : // to call nsIThread::Shutdown(), and if we're very busy that can take a
191 : // while to run, and we end up with dozens of extra threads. Note that
192 : // threads that are idle for 60 seconds are shutdown naturally.
193 0 : uint32_t existingLimit = 0;
194 : nsresult rv;
195 :
196 0 : rv = mPool->GetThreadLimit(&existingLimit);
197 0 : NS_ENSURE_SUCCESS(rv, rv);
198 0 : if (aLimit > existingLimit) {
199 0 : rv = mPool->SetThreadLimit(aLimit);
200 0 : NS_ENSURE_SUCCESS(rv, rv);
201 : }
202 :
203 0 : rv = mPool->GetIdleThreadLimit(&existingLimit);
204 0 : NS_ENSURE_SUCCESS(rv, rv);
205 0 : if (aLimit > existingLimit) {
206 0 : rv = mPool->SetIdleThreadLimit(aLimit);
207 0 : NS_ENSURE_SUCCESS(rv, rv);
208 : }
209 :
210 0 : return NS_OK;
211 : }
212 :
213 : static already_AddRefed<nsIThreadPool>
214 0 : CreateThreadPool(const nsCString& aName)
215 : {
216 : nsresult rv;
217 0 : nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
218 0 : NS_ENSURE_SUCCESS(rv, nullptr);
219 :
220 0 : rv = pool->SetName(aName);
221 0 : NS_ENSURE_SUCCESS(rv, nullptr);
222 :
223 0 : rv = pool->SetThreadStackSize(SharedThreadPool::kStackSize);
224 0 : NS_ENSURE_SUCCESS(rv, nullptr);
225 :
226 : #ifdef XP_WIN
227 : // Ensure MSCOM is initialized on the thread pools threads.
228 : nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener();
229 : rv = pool->SetListener(listener);
230 : NS_ENSURE_SUCCESS(rv, nullptr);
231 : #endif
232 :
233 0 : return pool.forget();
234 : }
235 :
236 : } // namespace mozilla
|