Bumping manifests a=b2g-bump
[gecko.git] / xpcom / threads / nsThreadPool.cpp
blob91c769438ef282c92fe73daeb487cc5e0ef2ae07
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/. */
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()
22 static PRLogModuleInfo* sLog;
23 if (!sLog) {
24 sLog = PR_NewLogModule("nsThreadPool");
26 return sLog;
28 #endif
29 #ifdef LOG
30 #undef LOG
31 #endif
32 #define LOG(args) PR_LOG(GetThreadPoolLog(), PR_LOG_DEBUG, args)
34 // DESIGN:
35 // o Allocate anonymous threads.
36 // o Use nsThreadPool::Run as the main routine for each thread.
37 // o Each thread waits on the event queue's monitor, checking for
38 // pending events and rescheduling itself as an idle thread.
40 #define DEFAULT_THREAD_LIMIT 4
41 #define DEFAULT_IDLE_THREAD_LIMIT 1
42 #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
44 NS_IMPL_ADDREF(nsThreadPool)
45 NS_IMPL_RELEASE(nsThreadPool)
46 NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE,
47 NS_THREADPOOL_CID)
48 NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
49 nsIRunnable)
50 NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget)
52 nsThreadPool::nsThreadPool()
53 : mThreadLimit(DEFAULT_THREAD_LIMIT)
54 , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
55 , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
56 , mIdleCount(0)
57 , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
58 , mShutdown(false)
62 nsThreadPool::~nsThreadPool()
64 // Threads keep a reference to the nsThreadPool until they return from Run()
65 // after removing themselves from mThreads.
66 MOZ_ASSERT(mThreads.IsEmpty());
69 nsresult
70 nsThreadPool::PutEvent(nsIRunnable* aEvent)
72 // Avoid spawning a new thread while holding the event queue lock...
74 bool spawnThread = false;
75 uint32_t stackSize = 0;
77 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
79 if (NS_WARN_IF(mShutdown)) {
80 return NS_ERROR_NOT_AVAILABLE;
82 LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(),
83 mThreadLimit));
84 MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops");
86 // Make sure we have a thread to service this event.
87 if (mIdleCount == 0 && mThreads.Count() < (int32_t)mThreadLimit) {
88 spawnThread = true;
91 mEvents.PutEvent(aEvent);
92 stackSize = mStackSize;
95 LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
96 if (!spawnThread) {
97 return NS_OK;
100 nsCOMPtr<nsIThread> thread;
101 nsThreadManager::get()->NewThread(0,
102 stackSize,
103 getter_AddRefs(thread));
104 if (NS_WARN_IF(!thread)) {
105 return NS_ERROR_UNEXPECTED;
108 bool killThread = false;
110 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
111 if (mThreads.Count() < (int32_t)mThreadLimit) {
112 mThreads.AppendObject(thread);
113 } else {
114 killThread = true; // okay, we don't need this thread anymore
117 LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread));
118 if (killThread) {
119 // Pending events are processed on the current thread during
120 // nsIThread::Shutdown() execution, so if nsThreadPool::Dispatch() is called
121 // under caller's lock then deadlock could occur. This happens e.g. in case
122 // of nsStreamCopier. To prevent this situation, dispatch a shutdown event
123 // to the current thread instead of calling nsIThread::Shutdown() directly.
125 nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(thread,
126 &nsIThread::Shutdown);
127 NS_DispatchToCurrentThread(r);
128 } else {
129 thread->Dispatch(this, NS_DISPATCH_NORMAL);
132 return NS_OK;
135 void
136 nsThreadPool::ShutdownThread(nsIThread* aThread)
138 LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread));
140 // This method is responsible for calling Shutdown on |aThread|. This must be
141 // done from some other thread, so we use the main thread of the application.
143 MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
145 nsRefPtr<nsIRunnable> r = NS_NewRunnableMethod(aThread, &nsIThread::Shutdown);
146 NS_DispatchToMainThread(r);
149 NS_IMETHODIMP
150 nsThreadPool::Run()
152 LOG(("THRD-P(%p) enter\n", this));
154 mThreadNaming.SetThreadPoolName(mName);
156 nsCOMPtr<nsIThread> current;
157 nsThreadManager::get()->GetCurrentThread(getter_AddRefs(current));
159 bool shutdownThreadOnExit = false;
160 bool exitThread = false;
161 bool wasIdle = false;
162 PRIntervalTime idleSince;
164 nsCOMPtr<nsIThreadPoolListener> listener;
166 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
167 listener = mListener;
170 if (listener) {
171 listener->OnThreadCreated();
174 do {
175 nsCOMPtr<nsIRunnable> event;
177 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
178 if (!mEvents.GetPendingEvent(getter_AddRefs(event))) {
179 PRIntervalTime now = PR_IntervalNow();
180 PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout);
182 // If we are shutting down, then don't keep any idle threads
183 if (mShutdown) {
184 exitThread = true;
185 } else {
186 if (wasIdle) {
187 // if too many idle threads or idle for too long, then bail.
188 if (mIdleCount > mIdleThreadLimit || (now - idleSince) >= timeout) {
189 exitThread = true;
191 } else {
192 // if would be too many idle threads...
193 if (mIdleCount == mIdleThreadLimit) {
194 exitThread = true;
195 } else {
196 ++mIdleCount;
197 idleSince = now;
198 wasIdle = true;
203 if (exitThread) {
204 if (wasIdle) {
205 --mIdleCount;
207 shutdownThreadOnExit = mThreads.RemoveObject(current);
208 } else {
209 PRIntervalTime delta = timeout - (now - idleSince);
210 LOG(("THRD-P(%p) waiting [%d]\n", this, delta));
211 mon.Wait(delta);
213 } else if (wasIdle) {
214 wasIdle = false;
215 --mIdleCount;
218 if (event) {
219 LOG(("THRD-P(%p) running [%p]\n", this, event.get()));
220 event->Run();
222 } while (!exitThread);
224 if (listener) {
225 listener->OnThreadShuttingDown();
228 if (shutdownThreadOnExit) {
229 ShutdownThread(current);
232 LOG(("THRD-P(%p) leave\n", this));
233 return NS_OK;
236 NS_IMETHODIMP
237 nsThreadPool::Dispatch(nsIRunnable* aEvent, uint32_t aFlags)
239 LOG(("THRD-P(%p) dispatch [%p %x]\n", this, aEvent, aFlags));
241 if (NS_WARN_IF(mShutdown)) {
242 return NS_ERROR_NOT_AVAILABLE;
245 if (aFlags & DISPATCH_SYNC) {
246 nsCOMPtr<nsIThread> thread;
247 nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thread));
248 if (NS_WARN_IF(!thread)) {
249 return NS_ERROR_NOT_AVAILABLE;
252 nsRefPtr<nsThreadSyncDispatch> wrapper =
253 new nsThreadSyncDispatch(thread, aEvent);
254 PutEvent(wrapper);
256 while (wrapper->IsPending()) {
257 NS_ProcessNextEvent(thread);
259 } else {
260 NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "unexpected dispatch flags");
261 PutEvent(aEvent);
263 return NS_OK;
266 NS_IMETHODIMP
267 nsThreadPool::IsOnCurrentThread(bool* aResult)
269 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
270 if (NS_WARN_IF(mShutdown)) {
271 return NS_ERROR_NOT_AVAILABLE;
274 nsIThread* thread = NS_GetCurrentThread();
275 for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
276 if (mThreads[i] == thread) {
277 *aResult = true;
278 return NS_OK;
281 *aResult = false;
282 return NS_OK;
285 NS_IMETHODIMP
286 nsThreadPool::Shutdown()
288 nsCOMArray<nsIThread> threads;
289 nsCOMPtr<nsIThreadPoolListener> listener;
291 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
292 mShutdown = true;
293 mon.NotifyAll();
295 threads.AppendObjects(mThreads);
296 mThreads.Clear();
298 // Swap in a null listener so that we release the listener at the end of
299 // this method. The listener will be kept alive as long as the other threads
300 // that were created when it was set.
301 mListener.swap(listener);
304 // It's important that we shutdown the threads while outside the event queue
305 // monitor. Otherwise, we could end up dead-locking.
307 for (int32_t i = 0; i < threads.Count(); ++i) {
308 threads[i]->Shutdown();
311 return NS_OK;
314 NS_IMETHODIMP
315 nsThreadPool::GetThreadLimit(uint32_t* aValue)
317 *aValue = mThreadLimit;
318 return NS_OK;
321 NS_IMETHODIMP
322 nsThreadPool::SetThreadLimit(uint32_t aValue)
324 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
325 mThreadLimit = aValue;
326 if (mIdleThreadLimit > mThreadLimit) {
327 mIdleThreadLimit = mThreadLimit;
330 if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) {
331 mon.NotifyAll(); // wake up threads so they observe this change
333 return NS_OK;
336 NS_IMETHODIMP
337 nsThreadPool::GetIdleThreadLimit(uint32_t* aValue)
339 *aValue = mIdleThreadLimit;
340 return NS_OK;
343 NS_IMETHODIMP
344 nsThreadPool::SetIdleThreadLimit(uint32_t aValue)
346 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
347 mIdleThreadLimit = aValue;
348 if (mIdleThreadLimit > mThreadLimit) {
349 mIdleThreadLimit = mThreadLimit;
352 // Do we need to kill some idle threads?
353 if (mIdleCount > mIdleThreadLimit) {
354 mon.NotifyAll(); // wake up threads so they observe this change
356 return NS_OK;
359 NS_IMETHODIMP
360 nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue)
362 *aValue = mIdleThreadTimeout;
363 return NS_OK;
366 NS_IMETHODIMP
367 nsThreadPool::SetIdleThreadTimeout(uint32_t aValue)
369 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
370 uint32_t oldTimeout = mIdleThreadTimeout;
371 mIdleThreadTimeout = aValue;
373 // Do we need to notify any idle threads that their sleep time has shortened?
374 if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) {
375 mon.NotifyAll(); // wake up threads so they observe this change
377 return NS_OK;
380 NS_IMETHODIMP
381 nsThreadPool::GetThreadStackSize(uint32_t* aValue)
383 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
384 *aValue = mStackSize;
385 return NS_OK;
388 NS_IMETHODIMP
389 nsThreadPool::SetThreadStackSize(uint32_t aValue)
391 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
392 mStackSize = aValue;
393 return NS_OK;
396 NS_IMETHODIMP
397 nsThreadPool::GetListener(nsIThreadPoolListener** aListener)
399 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
400 NS_IF_ADDREF(*aListener = mListener);
401 return NS_OK;
404 NS_IMETHODIMP
405 nsThreadPool::SetListener(nsIThreadPoolListener* aListener)
407 nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
409 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
410 mListener.swap(swappedListener);
412 return NS_OK;
415 NS_IMETHODIMP
416 nsThreadPool::SetName(const nsACString& aName)
419 ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
420 if (mThreads.Count()) {
421 return NS_ERROR_NOT_AVAILABLE;
425 mName = aName;
426 return NS_OK;