|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
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 "SharedThreadPool.h" |
|
8 #include "mozilla/Monitor.h" |
|
9 #include "mozilla/StaticPtr.h" |
|
10 #include "nsDataHashtable.h" |
|
11 #include "VideoUtils.h" |
|
12 #include "nsXPCOMCIDInternal.h" |
|
13 #include "nsComponentManagerUtils.h" |
|
14 |
|
15 #ifdef XP_WIN |
|
16 // Required to init MSCOM by MSCOMInitThreadPoolListener. |
|
17 #include <objbase.h> |
|
18 #endif |
|
19 |
|
20 namespace mozilla { |
|
21 |
|
22 // Created and destroyed on the main thread. |
|
23 static StaticAutoPtr<ReentrantMonitor> sMonitor; |
|
24 |
|
25 // Hashtable, maps thread pool name to SharedThreadPool instance. |
|
26 // Modified only on the main thread. |
|
27 static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>> sPools; |
|
28 |
|
29 static already_AddRefed<nsIThreadPool> |
|
30 CreateThreadPool(const nsCString& aName); |
|
31 |
|
32 static void |
|
33 DestroySharedThreadPoolHashTable(); |
|
34 |
|
35 void |
|
36 SharedThreadPool::EnsureInitialized() |
|
37 { |
|
38 MOZ_ASSERT(NS_IsMainThread()); |
|
39 if (sMonitor || sPools) { |
|
40 // Already initalized. |
|
41 return; |
|
42 } |
|
43 sMonitor = new ReentrantMonitor("SharedThreadPool"); |
|
44 sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>(); |
|
45 } |
|
46 |
|
47 class ShutdownPoolsEvent : public nsRunnable { |
|
48 public: |
|
49 NS_IMETHODIMP Run() { |
|
50 MOZ_ASSERT(NS_IsMainThread()); |
|
51 DestroySharedThreadPoolHashTable(); |
|
52 return NS_OK; |
|
53 } |
|
54 }; |
|
55 |
|
56 static void |
|
57 DestroySharedThreadPoolHashTable() |
|
58 { |
|
59 MOZ_ASSERT(NS_IsMainThread()); |
|
60 MOZ_ASSERT(sMonitor && sPools); |
|
61 if (!sPools->Count()) { |
|
62 // No more SharedThreadPool singletons. Delete the hash table. |
|
63 // Note we don't need to lock sMonitor, since we only modify the |
|
64 // hash table on the main thread, and if the hash table is empty |
|
65 // there are no external references into its contents. |
|
66 sPools = nullptr; |
|
67 sMonitor = nullptr; |
|
68 } |
|
69 } |
|
70 |
|
71 /* static */ |
|
72 void |
|
73 SharedThreadPool::SpinUntilShutdown() |
|
74 { |
|
75 MOZ_ASSERT(NS_IsMainThread()); |
|
76 // Wait until the ShutdownPoolsEvent has been run and shutdown the pool. |
|
77 while (sPools) { |
|
78 if (!NS_ProcessNextEvent(NS_GetCurrentThread(), true)) { |
|
79 break; |
|
80 } |
|
81 } |
|
82 MOZ_ASSERT(!sPools); |
|
83 MOZ_ASSERT(!sMonitor); |
|
84 } |
|
85 |
|
86 TemporaryRef<SharedThreadPool> |
|
87 SharedThreadPool::Get(const nsCString& aName, uint32_t aThreadLimit) |
|
88 { |
|
89 MOZ_ASSERT(NS_IsMainThread()); |
|
90 EnsureInitialized(); |
|
91 MOZ_ASSERT(sMonitor); |
|
92 ReentrantMonitorAutoEnter mon(*sMonitor); |
|
93 SharedThreadPool* pool = nullptr; |
|
94 nsresult rv; |
|
95 if (!sPools->Get(aName, &pool)) { |
|
96 nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName)); |
|
97 NS_ENSURE_TRUE(threadPool, nullptr); |
|
98 pool = new SharedThreadPool(aName, threadPool); |
|
99 |
|
100 // Set the thread and idle limits. Note that we don't rely on the |
|
101 // EnsureThreadLimitIsAtLeast() call below, as the default thread limit |
|
102 // is 4, and if aThreadLimit is less than 4 we'll end up with a pool |
|
103 // with 4 threads rather than what we expected; so we'll have unexpected |
|
104 // behaviour. |
|
105 rv = pool->SetThreadLimit(aThreadLimit); |
|
106 NS_ENSURE_SUCCESS(rv, nullptr); |
|
107 |
|
108 rv = pool->SetIdleThreadLimit(aThreadLimit); |
|
109 NS_ENSURE_SUCCESS(rv, nullptr); |
|
110 |
|
111 sPools->Put(aName, pool); |
|
112 } else if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { |
|
113 NS_WARNING("Failed to set limits on thread pool"); |
|
114 } |
|
115 |
|
116 MOZ_ASSERT(pool); |
|
117 RefPtr<SharedThreadPool> instance(pool); |
|
118 return instance.forget(); |
|
119 } |
|
120 |
|
121 NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) |
|
122 { |
|
123 MOZ_ASSERT(sMonitor); |
|
124 ReentrantMonitorAutoEnter mon(*sMonitor); |
|
125 MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); |
|
126 nsrefcnt count = ++mRefCnt; |
|
127 NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this)); |
|
128 return count; |
|
129 } |
|
130 |
|
131 NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) |
|
132 { |
|
133 MOZ_ASSERT(sMonitor); |
|
134 bool dispatchShutdownEvent; |
|
135 { |
|
136 ReentrantMonitorAutoEnter mon(*sMonitor); |
|
137 nsrefcnt count = --mRefCnt; |
|
138 NS_LOG_RELEASE(this, count, "SharedThreadPool"); |
|
139 if (count) { |
|
140 return count; |
|
141 } |
|
142 |
|
143 // Zero refcount. Must shutdown and then delete the thread pool. |
|
144 |
|
145 // First, dispatch an event to the main thread to call Shutdown() on |
|
146 // the nsIThreadPool. The Runnable here will add a refcount to the pool, |
|
147 // and when the Runnable releases the nsIThreadPool it will be deleted. |
|
148 RefPtr<nsIRunnable> r = NS_NewRunnableMethod(mPool, &nsIThreadPool::Shutdown); |
|
149 NS_DispatchToMainThread(r); |
|
150 |
|
151 // Remove SharedThreadPool from table of pools. |
|
152 sPools->Remove(mName); |
|
153 MOZ_ASSERT(!sPools->Get(mName)); |
|
154 |
|
155 // Stabilize refcount, so that if something in the dtor QIs, |
|
156 // it won't explode. |
|
157 mRefCnt = 1; |
|
158 |
|
159 delete this; |
|
160 |
|
161 dispatchShutdownEvent = sPools->Count() == 0; |
|
162 } |
|
163 if (dispatchShutdownEvent) { |
|
164 // No more SharedThreadPools alive. Destroy the hash table. |
|
165 // Ensure that we only run on the main thread. |
|
166 // Do this in an event so that if something holds the monitor we won't |
|
167 // be deleting the monitor while it's held. |
|
168 NS_DispatchToMainThread(new ShutdownPoolsEvent(), NS_DISPATCH_NORMAL); |
|
169 } |
|
170 return 0; |
|
171 } |
|
172 |
|
173 NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) |
|
174 |
|
175 SharedThreadPool::SharedThreadPool(const nsCString& aName, |
|
176 nsIThreadPool* aPool) |
|
177 : mName(aName) |
|
178 , mPool(aPool) |
|
179 , mRefCnt(0) |
|
180 { |
|
181 MOZ_COUNT_CTOR(SharedThreadPool); |
|
182 mEventTarget = do_QueryInterface(aPool); |
|
183 } |
|
184 |
|
185 SharedThreadPool::~SharedThreadPool() |
|
186 { |
|
187 MOZ_COUNT_DTOR(SharedThreadPool); |
|
188 } |
|
189 |
|
190 #ifdef XP_WIN |
|
191 |
|
192 // Thread pool listener which ensures that MSCOM is initialized and |
|
193 // deinitialized on the thread pool thread. We may call into WMF or |
|
194 // DirectShow on this thread, so we need MSCOM working. |
|
195 class MSCOMInitThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener { |
|
196 public: |
|
197 NS_DECL_THREADSAFE_ISUPPORTS |
|
198 NS_DECL_NSITHREADPOOLLISTENER |
|
199 }; |
|
200 |
|
201 NS_IMPL_ISUPPORTS(MSCOMInitThreadPoolListener, nsIThreadPoolListener) |
|
202 |
|
203 NS_IMETHODIMP |
|
204 MSCOMInitThreadPoolListener::OnThreadCreated() |
|
205 { |
|
206 HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); |
|
207 if (FAILED(hr)) { |
|
208 NS_WARNING("Failed to initialize MSCOM on WMFByteStream thread."); |
|
209 } |
|
210 return NS_OK; |
|
211 } |
|
212 |
|
213 NS_IMETHODIMP |
|
214 MSCOMInitThreadPoolListener::OnThreadShuttingDown() |
|
215 { |
|
216 CoUninitialize(); |
|
217 return NS_OK; |
|
218 } |
|
219 |
|
220 #endif // XP_WIN |
|
221 |
|
222 nsresult |
|
223 SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) |
|
224 { |
|
225 // We limit the number of threads that we use for media. Note that we |
|
226 // set the thread limit to the same as the idle limit so that we're not |
|
227 // constantly creating and destroying threads (see Bug 881954). When the |
|
228 // thread pool threads shutdown they dispatch an event to the main thread |
|
229 // to call nsIThread::Shutdown(), and if we're very busy that can take a |
|
230 // while to run, and we end up with dozens of extra threads. Note that |
|
231 // threads that are idle for 60 seconds are shutdown naturally. |
|
232 uint32_t existingLimit = 0; |
|
233 nsresult rv; |
|
234 |
|
235 rv = mPool->GetThreadLimit(&existingLimit); |
|
236 NS_ENSURE_SUCCESS(rv, rv); |
|
237 if (aLimit > existingLimit) { |
|
238 rv = mPool->SetThreadLimit(aLimit); |
|
239 NS_ENSURE_SUCCESS(rv, rv); |
|
240 } |
|
241 |
|
242 rv = mPool->GetIdleThreadLimit(&existingLimit); |
|
243 NS_ENSURE_SUCCESS(rv, rv); |
|
244 if (aLimit > existingLimit) { |
|
245 rv = mPool->SetIdleThreadLimit(aLimit); |
|
246 NS_ENSURE_SUCCESS(rv, rv); |
|
247 } |
|
248 |
|
249 return NS_OK; |
|
250 } |
|
251 |
|
252 static already_AddRefed<nsIThreadPool> |
|
253 CreateThreadPool(const nsCString& aName) |
|
254 { |
|
255 MOZ_ASSERT(NS_IsMainThread()); |
|
256 |
|
257 nsresult rv; |
|
258 nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv); |
|
259 NS_ENSURE_SUCCESS(rv, nullptr); |
|
260 |
|
261 rv = pool->SetName(aName); |
|
262 NS_ENSURE_SUCCESS(rv, nullptr); |
|
263 |
|
264 rv = pool->SetThreadStackSize(MEDIA_THREAD_STACK_SIZE); |
|
265 NS_ENSURE_SUCCESS(rv, nullptr); |
|
266 |
|
267 #ifdef XP_WIN |
|
268 // Ensure MSCOM is initialized on the thread pools threads. |
|
269 nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener(); |
|
270 rv = pool->SetListener(listener); |
|
271 NS_ENSURE_SUCCESS(rv, nullptr); |
|
272 #endif |
|
273 |
|
274 return pool.forget(); |
|
275 } |
|
276 |
|
277 } // namespace mozilla |