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