|
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 "nsIClassInfoImpl.h" |
|
8 #include "nsThreadPool.h" |
|
9 #include "nsThreadManager.h" |
|
10 #include "nsThread.h" |
|
11 #include "nsMemory.h" |
|
12 #include "nsAutoPtr.h" |
|
13 #include "prinrval.h" |
|
14 #include "prlog.h" |
|
15 |
|
16 using namespace mozilla; |
|
17 |
|
18 #ifdef PR_LOGGING |
|
19 static PRLogModuleInfo * |
|
20 GetThreadPoolLog() |
|
21 { |
|
22 static PRLogModuleInfo *sLog; |
|
23 if (!sLog) |
|
24 sLog = PR_NewLogModule("nsThreadPool"); |
|
25 return sLog; |
|
26 } |
|
27 #endif |
|
28 #ifdef LOG |
|
29 #undef LOG |
|
30 #endif |
|
31 #define LOG(args) PR_LOG(GetThreadPoolLog(), PR_LOG_DEBUG, args) |
|
32 |
|
33 // DESIGN: |
|
34 // o Allocate anonymous threads. |
|
35 // o Use nsThreadPool::Run as the main routine for each thread. |
|
36 // o Each thread waits on the event queue's monitor, checking for |
|
37 // pending events and rescheduling itself as an idle thread. |
|
38 |
|
39 #define DEFAULT_THREAD_LIMIT 4 |
|
40 #define DEFAULT_IDLE_THREAD_LIMIT 1 |
|
41 #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60) |
|
42 |
|
43 NS_IMPL_ADDREF(nsThreadPool) |
|
44 NS_IMPL_RELEASE(nsThreadPool) |
|
45 NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE, |
|
46 NS_THREADPOOL_CID) |
|
47 NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget, |
|
48 nsIRunnable) |
|
49 NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget) |
|
50 |
|
51 nsThreadPool::nsThreadPool() |
|
52 : mThreadLimit(DEFAULT_THREAD_LIMIT) |
|
53 , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT) |
|
54 , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT) |
|
55 , mIdleCount(0) |
|
56 , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE) |
|
57 , mShutdown(false) |
|
58 { |
|
59 } |
|
60 |
|
61 nsThreadPool::~nsThreadPool() |
|
62 { |
|
63 // Threads keep a reference to the nsThreadPool until they return from Run() |
|
64 // after removing themselves from mThreads. |
|
65 MOZ_ASSERT(mThreads.IsEmpty()); |
|
66 } |
|
67 |
|
68 nsresult |
|
69 nsThreadPool::PutEvent(nsIRunnable *event) |
|
70 { |
|
71 // Avoid spawning a new thread while holding the event queue lock... |
|
72 |
|
73 bool spawnThread = false; |
|
74 uint32_t stackSize = 0; |
|
75 { |
|
76 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
77 |
|
78 LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(), |
|
79 mThreadLimit)); |
|
80 MOZ_ASSERT(mIdleCount <= (uint32_t) mThreads.Count(), "oops"); |
|
81 |
|
82 // Make sure we have a thread to service this event. |
|
83 if (mIdleCount == 0 && mThreads.Count() < (int32_t) mThreadLimit) |
|
84 spawnThread = true; |
|
85 |
|
86 mEvents.PutEvent(event); |
|
87 stackSize = mStackSize; |
|
88 } |
|
89 |
|
90 LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread)); |
|
91 if (!spawnThread) |
|
92 return NS_OK; |
|
93 |
|
94 nsCOMPtr<nsIThread> thread; |
|
95 nsThreadManager::get()->NewThread(0, |
|
96 stackSize, |
|
97 getter_AddRefs(thread)); |
|
98 if (NS_WARN_IF(!thread)) |
|
99 return NS_ERROR_UNEXPECTED; |
|
100 |
|
101 bool killThread = false; |
|
102 { |
|
103 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
104 if (mThreads.Count() < (int32_t) mThreadLimit) { |
|
105 mThreads.AppendObject(thread); |
|
106 } else { |
|
107 killThread = true; // okay, we don't need this thread anymore |
|
108 } |
|
109 } |
|
110 LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread)); |
|
111 if (killThread) { |
|
112 // Pending events are processed on the current thread during |
|
113 // nsIThread::Shutdown() execution, so if nsThreadPool::Dispatch() is called |
|
114 // under caller's lock then deadlock could occur. This happens e.g. in case |
|
115 // of nsStreamCopier. To prevent this situation, dispatch a shutdown event |
|
116 // to the current thread instead of calling nsIThread::Shutdown() directly. |
|
117 |
|
118 nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread, |
|
119 &nsIThread::Shutdown); |
|
120 NS_DispatchToCurrentThread(r); |
|
121 } else { |
|
122 thread->Dispatch(this, NS_DISPATCH_NORMAL); |
|
123 } |
|
124 |
|
125 return NS_OK; |
|
126 } |
|
127 |
|
128 void |
|
129 nsThreadPool::ShutdownThread(nsIThread *thread) |
|
130 { |
|
131 LOG(("THRD-P(%p) shutdown async [%p]\n", this, thread)); |
|
132 |
|
133 // This method is responsible for calling Shutdown on |thread|. This must be |
|
134 // done from some other thread, so we use the main thread of the application. |
|
135 |
|
136 MOZ_ASSERT(!NS_IsMainThread(), "wrong thread"); |
|
137 |
|
138 nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread, &nsIThread::Shutdown); |
|
139 NS_DispatchToMainThread(r); |
|
140 } |
|
141 |
|
142 NS_IMETHODIMP |
|
143 nsThreadPool::Run() |
|
144 { |
|
145 LOG(("THRD-P(%p) enter\n", this)); |
|
146 |
|
147 mThreadNaming.SetThreadPoolName(mName); |
|
148 |
|
149 nsCOMPtr<nsIThread> current; |
|
150 nsThreadManager::get()->GetCurrentThread(getter_AddRefs(current)); |
|
151 |
|
152 bool shutdownThreadOnExit = false; |
|
153 bool exitThread = false; |
|
154 bool wasIdle = false; |
|
155 PRIntervalTime idleSince; |
|
156 |
|
157 nsCOMPtr<nsIThreadPoolListener> listener; |
|
158 { |
|
159 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
160 listener = mListener; |
|
161 } |
|
162 |
|
163 if (listener) { |
|
164 listener->OnThreadCreated(); |
|
165 } |
|
166 |
|
167 do { |
|
168 nsCOMPtr<nsIRunnable> event; |
|
169 { |
|
170 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
171 if (!mEvents.GetPendingEvent(getter_AddRefs(event))) { |
|
172 PRIntervalTime now = PR_IntervalNow(); |
|
173 PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout); |
|
174 |
|
175 // If we are shutting down, then don't keep any idle threads |
|
176 if (mShutdown) { |
|
177 exitThread = true; |
|
178 } else { |
|
179 if (wasIdle) { |
|
180 // if too many idle threads or idle for too long, then bail. |
|
181 if (mIdleCount > mIdleThreadLimit || (now - idleSince) >= timeout) |
|
182 exitThread = true; |
|
183 } else { |
|
184 // if would be too many idle threads... |
|
185 if (mIdleCount == mIdleThreadLimit) { |
|
186 exitThread = true; |
|
187 } else { |
|
188 ++mIdleCount; |
|
189 idleSince = now; |
|
190 wasIdle = true; |
|
191 } |
|
192 } |
|
193 } |
|
194 |
|
195 if (exitThread) { |
|
196 if (wasIdle) |
|
197 --mIdleCount; |
|
198 shutdownThreadOnExit = mThreads.RemoveObject(current); |
|
199 } else { |
|
200 PRIntervalTime delta = timeout - (now - idleSince); |
|
201 LOG(("THRD-P(%p) waiting [%d]\n", this, delta)); |
|
202 mon.Wait(delta); |
|
203 } |
|
204 } else if (wasIdle) { |
|
205 wasIdle = false; |
|
206 --mIdleCount; |
|
207 } |
|
208 } |
|
209 if (event) { |
|
210 LOG(("THRD-P(%p) running [%p]\n", this, event.get())); |
|
211 event->Run(); |
|
212 } |
|
213 } while (!exitThread); |
|
214 |
|
215 if (listener) { |
|
216 listener->OnThreadShuttingDown(); |
|
217 } |
|
218 |
|
219 if (shutdownThreadOnExit) { |
|
220 ShutdownThread(current); |
|
221 } |
|
222 |
|
223 LOG(("THRD-P(%p) leave\n", this)); |
|
224 return NS_OK; |
|
225 } |
|
226 |
|
227 NS_IMETHODIMP |
|
228 nsThreadPool::Dispatch(nsIRunnable *event, uint32_t flags) |
|
229 { |
|
230 LOG(("THRD-P(%p) dispatch [%p %x]\n", this, event, flags)); |
|
231 |
|
232 if (NS_WARN_IF(mShutdown)) |
|
233 return NS_ERROR_NOT_AVAILABLE; |
|
234 |
|
235 if (flags & DISPATCH_SYNC) { |
|
236 nsCOMPtr<nsIThread> thread; |
|
237 nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thread)); |
|
238 if (NS_WARN_IF(!thread)) |
|
239 return NS_ERROR_NOT_AVAILABLE; |
|
240 |
|
241 nsRefPtr<nsThreadSyncDispatch> wrapper = |
|
242 new nsThreadSyncDispatch(thread, event); |
|
243 PutEvent(wrapper); |
|
244 |
|
245 while (wrapper->IsPending()) |
|
246 NS_ProcessNextEvent(thread); |
|
247 } else { |
|
248 NS_ASSERTION(flags == NS_DISPATCH_NORMAL, "unexpected dispatch flags"); |
|
249 PutEvent(event); |
|
250 } |
|
251 return NS_OK; |
|
252 } |
|
253 |
|
254 NS_IMETHODIMP |
|
255 nsThreadPool::IsOnCurrentThread(bool *result) |
|
256 { |
|
257 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
258 nsIThread* thread = NS_GetCurrentThread(); |
|
259 for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) { |
|
260 if (mThreads[i] == thread) { |
|
261 *result = true; |
|
262 return NS_OK; |
|
263 } |
|
264 } |
|
265 *result = false; |
|
266 return NS_OK; |
|
267 } |
|
268 |
|
269 NS_IMETHODIMP |
|
270 nsThreadPool::Shutdown() |
|
271 { |
|
272 nsCOMArray<nsIThread> threads; |
|
273 nsCOMPtr<nsIThreadPoolListener> listener; |
|
274 { |
|
275 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
276 mShutdown = true; |
|
277 mon.NotifyAll(); |
|
278 |
|
279 threads.AppendObjects(mThreads); |
|
280 mThreads.Clear(); |
|
281 |
|
282 // Swap in a null listener so that we release the listener at the end of |
|
283 // this method. The listener will be kept alive as long as the other threads |
|
284 // that were created when it was set. |
|
285 mListener.swap(listener); |
|
286 } |
|
287 |
|
288 // It's important that we shutdown the threads while outside the event queue |
|
289 // monitor. Otherwise, we could end up dead-locking. |
|
290 |
|
291 for (int32_t i = 0; i < threads.Count(); ++i) |
|
292 threads[i]->Shutdown(); |
|
293 |
|
294 return NS_OK; |
|
295 } |
|
296 |
|
297 NS_IMETHODIMP |
|
298 nsThreadPool::GetThreadLimit(uint32_t *value) |
|
299 { |
|
300 *value = mThreadLimit; |
|
301 return NS_OK; |
|
302 } |
|
303 |
|
304 NS_IMETHODIMP |
|
305 nsThreadPool::SetThreadLimit(uint32_t value) |
|
306 { |
|
307 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
308 mThreadLimit = value; |
|
309 if (mIdleThreadLimit > mThreadLimit) |
|
310 mIdleThreadLimit = mThreadLimit; |
|
311 |
|
312 if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) { |
|
313 mon.NotifyAll(); // wake up threads so they observe this change |
|
314 } |
|
315 return NS_OK; |
|
316 } |
|
317 |
|
318 NS_IMETHODIMP |
|
319 nsThreadPool::GetIdleThreadLimit(uint32_t *value) |
|
320 { |
|
321 *value = mIdleThreadLimit; |
|
322 return NS_OK; |
|
323 } |
|
324 |
|
325 NS_IMETHODIMP |
|
326 nsThreadPool::SetIdleThreadLimit(uint32_t value) |
|
327 { |
|
328 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
329 mIdleThreadLimit = value; |
|
330 if (mIdleThreadLimit > mThreadLimit) |
|
331 mIdleThreadLimit = mThreadLimit; |
|
332 |
|
333 // Do we need to kill some idle threads? |
|
334 if (mIdleCount > mIdleThreadLimit) { |
|
335 mon.NotifyAll(); // wake up threads so they observe this change |
|
336 } |
|
337 return NS_OK; |
|
338 } |
|
339 |
|
340 NS_IMETHODIMP |
|
341 nsThreadPool::GetIdleThreadTimeout(uint32_t *value) |
|
342 { |
|
343 *value = mIdleThreadTimeout; |
|
344 return NS_OK; |
|
345 } |
|
346 |
|
347 NS_IMETHODIMP |
|
348 nsThreadPool::SetIdleThreadTimeout(uint32_t value) |
|
349 { |
|
350 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
351 uint32_t oldTimeout = mIdleThreadTimeout; |
|
352 mIdleThreadTimeout = value; |
|
353 |
|
354 // Do we need to notify any idle threads that their sleep time has shortened? |
|
355 if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) { |
|
356 mon.NotifyAll(); // wake up threads so they observe this change |
|
357 } |
|
358 return NS_OK; |
|
359 } |
|
360 |
|
361 NS_IMETHODIMP |
|
362 nsThreadPool::GetThreadStackSize(uint32_t* value) |
|
363 { |
|
364 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
365 *value = mStackSize; |
|
366 return NS_OK; |
|
367 } |
|
368 |
|
369 NS_IMETHODIMP |
|
370 nsThreadPool::SetThreadStackSize(uint32_t value) |
|
371 { |
|
372 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
373 mStackSize = value; |
|
374 return NS_OK; |
|
375 } |
|
376 |
|
377 NS_IMETHODIMP |
|
378 nsThreadPool::GetListener(nsIThreadPoolListener** aListener) |
|
379 { |
|
380 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
381 NS_IF_ADDREF(*aListener = mListener); |
|
382 return NS_OK; |
|
383 } |
|
384 |
|
385 NS_IMETHODIMP |
|
386 nsThreadPool::SetListener(nsIThreadPoolListener* aListener) |
|
387 { |
|
388 nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener); |
|
389 { |
|
390 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
391 mListener.swap(swappedListener); |
|
392 } |
|
393 return NS_OK; |
|
394 } |
|
395 |
|
396 NS_IMETHODIMP |
|
397 nsThreadPool::SetName(const nsACString& aName) |
|
398 { |
|
399 { |
|
400 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor()); |
|
401 if (mThreads.Count()) |
|
402 return NS_ERROR_NOT_AVAILABLE; |
|
403 } |
|
404 |
|
405 mName = aName; |
|
406 return NS_OK; |
|
407 } |