Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
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"
16 using namespace mozilla;
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)
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.
39 #define DEFAULT_THREAD_LIMIT 4
40 #define DEFAULT_IDLE_THREAD_LIMIT 1
41 #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
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)
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 }
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 }
68 nsresult
69 nsThreadPool::PutEvent(nsIRunnable *event)
70 {
71 // Avoid spawning a new thread while holding the event queue lock...
73 bool spawnThread = false;
74 uint32_t stackSize = 0;
75 {
76 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
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");
82 // Make sure we have a thread to service this event.
83 if (mIdleCount == 0 && mThreads.Count() < (int32_t) mThreadLimit)
84 spawnThread = true;
86 mEvents.PutEvent(event);
87 stackSize = mStackSize;
88 }
90 LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
91 if (!spawnThread)
92 return NS_OK;
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;
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.
118 nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread,
119 &nsIThread::Shutdown);
120 NS_DispatchToCurrentThread(r);
121 } else {
122 thread->Dispatch(this, NS_DISPATCH_NORMAL);
123 }
125 return NS_OK;
126 }
128 void
129 nsThreadPool::ShutdownThread(nsIThread *thread)
130 {
131 LOG(("THRD-P(%p) shutdown async [%p]\n", this, thread));
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.
136 MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
138 nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread, &nsIThread::Shutdown);
139 NS_DispatchToMainThread(r);
140 }
142 NS_IMETHODIMP
143 nsThreadPool::Run()
144 {
145 LOG(("THRD-P(%p) enter\n", this));
147 mThreadNaming.SetThreadPoolName(mName);
149 nsCOMPtr<nsIThread> current;
150 nsThreadManager::get()->GetCurrentThread(getter_AddRefs(current));
152 bool shutdownThreadOnExit = false;
153 bool exitThread = false;
154 bool wasIdle = false;
155 PRIntervalTime idleSince;
157 nsCOMPtr<nsIThreadPoolListener> listener;
158 {
159 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
160 listener = mListener;
161 }
163 if (listener) {
164 listener->OnThreadCreated();
165 }
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);
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 }
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);
215 if (listener) {
216 listener->OnThreadShuttingDown();
217 }
219 if (shutdownThreadOnExit) {
220 ShutdownThread(current);
221 }
223 LOG(("THRD-P(%p) leave\n", this));
224 return NS_OK;
225 }
227 NS_IMETHODIMP
228 nsThreadPool::Dispatch(nsIRunnable *event, uint32_t flags)
229 {
230 LOG(("THRD-P(%p) dispatch [%p %x]\n", this, event, flags));
232 if (NS_WARN_IF(mShutdown))
233 return NS_ERROR_NOT_AVAILABLE;
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;
241 nsRefPtr<nsThreadSyncDispatch> wrapper =
242 new nsThreadSyncDispatch(thread, event);
243 PutEvent(wrapper);
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 }
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 }
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();
279 threads.AppendObjects(mThreads);
280 mThreads.Clear();
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 }
288 // It's important that we shutdown the threads while outside the event queue
289 // monitor. Otherwise, we could end up dead-locking.
291 for (int32_t i = 0; i < threads.Count(); ++i)
292 threads[i]->Shutdown();
294 return NS_OK;
295 }
297 NS_IMETHODIMP
298 nsThreadPool::GetThreadLimit(uint32_t *value)
299 {
300 *value = mThreadLimit;
301 return NS_OK;
302 }
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;
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 }
318 NS_IMETHODIMP
319 nsThreadPool::GetIdleThreadLimit(uint32_t *value)
320 {
321 *value = mIdleThreadLimit;
322 return NS_OK;
323 }
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;
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 }
340 NS_IMETHODIMP
341 nsThreadPool::GetIdleThreadTimeout(uint32_t *value)
342 {
343 *value = mIdleThreadTimeout;
344 return NS_OK;
345 }
347 NS_IMETHODIMP
348 nsThreadPool::SetIdleThreadTimeout(uint32_t value)
349 {
350 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
351 uint32_t oldTimeout = mIdleThreadTimeout;
352 mIdleThreadTimeout = value;
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 }
361 NS_IMETHODIMP
362 nsThreadPool::GetThreadStackSize(uint32_t* value)
363 {
364 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
365 *value = mStackSize;
366 return NS_OK;
367 }
369 NS_IMETHODIMP
370 nsThreadPool::SetThreadStackSize(uint32_t value)
371 {
372 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
373 mStackSize = value;
374 return NS_OK;
375 }
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 }
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 }
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 }
405 mName = aName;
406 return NS_OK;
407 }