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"
12 #include "nsAutoPtr.h"
16 using namespace mozilla
;
19 static PRLogModuleInfo
*
22 static PRLogModuleInfo
* sLog
;
24 sLog
= PR_NewLogModule("nsThreadPool");
32 #define LOG(args) PR_LOG(GetThreadPoolLog(), PR_LOG_DEBUG, args)
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
,
48 NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool
, nsIThreadPool
, nsIEventTarget
,
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
)
57 , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE
)
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());
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(),
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
) {
91 mEvents
.PutEvent(aEvent
);
92 stackSize
= mStackSize
;
95 LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread
));
100 nsCOMPtr
<nsIThread
> thread
;
101 nsThreadManager::get()->NewThread(0,
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
);
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
));
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
);
129 thread
->Dispatch(this, NS_DISPATCH_NORMAL
);
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
);
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
;
171 listener
->OnThreadCreated();
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
187 // if too many idle threads or idle for too long, then bail.
188 if (mIdleCount
> mIdleThreadLimit
|| (now
- idleSince
) >= timeout
) {
192 // if would be too many idle threads...
193 if (mIdleCount
== mIdleThreadLimit
) {
207 shutdownThreadOnExit
= mThreads
.RemoveObject(current
);
209 PRIntervalTime delta
= timeout
- (now
- idleSince
);
210 LOG(("THRD-P(%p) waiting [%d]\n", this, delta
));
213 } else if (wasIdle
) {
219 LOG(("THRD-P(%p) running [%p]\n", this, event
.get()));
222 } while (!exitThread
);
225 listener
->OnThreadShuttingDown();
228 if (shutdownThreadOnExit
) {
229 ShutdownThread(current
);
232 LOG(("THRD-P(%p) leave\n", this));
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
);
256 while (wrapper
->IsPending()) {
257 NS_ProcessNextEvent(thread
);
260 NS_ASSERTION(aFlags
== NS_DISPATCH_NORMAL
, "unexpected dispatch flags");
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
) {
286 nsThreadPool::Shutdown()
288 nsCOMArray
<nsIThread
> threads
;
289 nsCOMPtr
<nsIThreadPoolListener
> listener
;
291 ReentrantMonitorAutoEnter
mon(mEvents
.GetReentrantMonitor());
295 threads
.AppendObjects(mThreads
);
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();
315 nsThreadPool::GetThreadLimit(uint32_t* aValue
)
317 *aValue
= mThreadLimit
;
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
337 nsThreadPool::GetIdleThreadLimit(uint32_t* aValue
)
339 *aValue
= mIdleThreadLimit
;
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
360 nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue
)
362 *aValue
= mIdleThreadTimeout
;
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
381 nsThreadPool::GetThreadStackSize(uint32_t* aValue
)
383 ReentrantMonitorAutoEnter
mon(mEvents
.GetReentrantMonitor());
384 *aValue
= mStackSize
;
389 nsThreadPool::SetThreadStackSize(uint32_t aValue
)
391 ReentrantMonitorAutoEnter
mon(mEvents
.GetReentrantMonitor());
397 nsThreadPool::GetListener(nsIThreadPoolListener
** aListener
)
399 ReentrantMonitorAutoEnter
mon(mEvents
.GetReentrantMonitor());
400 NS_IF_ADDREF(*aListener
= mListener
);
405 nsThreadPool::SetListener(nsIThreadPoolListener
* aListener
)
407 nsCOMPtr
<nsIThreadPoolListener
> swappedListener(aListener
);
409 ReentrantMonitorAutoEnter
mon(mEvents
.GetReentrantMonitor());
410 mListener
.swap(swappedListener
);
416 nsThreadPool::SetName(const nsACString
& aName
)
419 ReentrantMonitorAutoEnter
mon(mEvents
.GetReentrantMonitor());
420 if (mThreads
.Count()) {
421 return NS_ERROR_NOT_AVAILABLE
;