Line data Source code
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/. */
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 "mozilla/Logging.h"
15 : #include "nsThreadSyncDispatch.h"
16 :
17 : using namespace mozilla;
18 :
19 : static LazyLogModule sThreadPoolLog("nsThreadPool");
20 : #ifdef LOG
21 : #undef LOG
22 : #endif
23 : #define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args)
24 :
25 : // DESIGN:
26 : // o Allocate anonymous threads.
27 : // o Use nsThreadPool::Run as the main routine for each thread.
28 : // o Each thread waits on the event queue's monitor, checking for
29 : // pending events and rescheduling itself as an idle thread.
30 :
31 : #define DEFAULT_THREAD_LIMIT 4
32 : #define DEFAULT_IDLE_THREAD_LIMIT 1
33 : #define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60)
34 :
35 94 : NS_IMPL_ADDREF(nsThreadPool)
36 86 : NS_IMPL_RELEASE(nsThreadPool)
37 3 : NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE,
38 : NS_THREADPOOL_CID)
39 15 : NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
40 : nsIRunnable)
41 0 : NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget)
42 :
43 6 : nsThreadPool::nsThreadPool()
44 : : mMutex("[nsThreadPool.mMutex]")
45 : , mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]")
46 : , mEvents(mEventsAvailable, nsEventQueue::eNormalQueue)
47 : , mThreadLimit(DEFAULT_THREAD_LIMIT)
48 : , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
49 6 : , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
50 : , mIdleCount(0)
51 : , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
52 12 : , mShutdown(false)
53 : {
54 6 : LOG(("THRD-P(%p) constructor!!!\n", this));
55 6 : }
56 :
57 0 : nsThreadPool::~nsThreadPool()
58 : {
59 : // Threads keep a reference to the nsThreadPool until they return from Run()
60 : // after removing themselves from mThreads.
61 0 : MOZ_ASSERT(mThreads.IsEmpty());
62 0 : }
63 :
64 : nsresult
65 0 : nsThreadPool::PutEvent(nsIRunnable* aEvent)
66 : {
67 0 : nsCOMPtr<nsIRunnable> event(aEvent);
68 0 : return PutEvent(event.forget(), 0);
69 : }
70 :
71 : nsresult
72 73 : nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
73 : {
74 : // Avoid spawning a new thread while holding the event queue lock...
75 :
76 73 : bool spawnThread = false;
77 73 : uint32_t stackSize = 0;
78 : {
79 146 : MutexAutoLock lock(mMutex);
80 :
81 73 : if (NS_WARN_IF(mShutdown)) {
82 0 : return NS_ERROR_NOT_AVAILABLE;
83 : }
84 73 : LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(),
85 : mThreadLimit));
86 73 : MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops");
87 :
88 : // Make sure we have a thread to service this event.
89 219 : if (mThreads.Count() < (int32_t)mThreadLimit &&
90 146 : !(aFlags & NS_DISPATCH_AT_END) &&
91 : // Spawn a new thread if we don't have enough idle threads to serve
92 : // pending events immediately.
93 73 : mEvents.Count(lock) >= mIdleCount) {
94 2 : spawnThread = true;
95 : }
96 :
97 73 : mEvents.PutEvent(Move(aEvent), lock);
98 73 : stackSize = mStackSize;
99 : }
100 :
101 73 : LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread));
102 73 : if (!spawnThread) {
103 71 : return NS_OK;
104 : }
105 :
106 4 : nsCOMPtr<nsIThread> thread;
107 4 : nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName(mName),
108 6 : getter_AddRefs(thread), nullptr, stackSize);
109 2 : if (NS_WARN_IF(NS_FAILED(rv))) {
110 0 : return NS_ERROR_UNEXPECTED;
111 : }
112 :
113 2 : bool killThread = false;
114 : {
115 4 : MutexAutoLock lock(mMutex);
116 2 : if (mThreads.Count() < (int32_t)mThreadLimit) {
117 2 : mThreads.AppendObject(thread);
118 : } else {
119 0 : killThread = true; // okay, we don't need this thread anymore
120 : }
121 : }
122 2 : LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread));
123 2 : if (killThread) {
124 : // We never dispatched any events to the thread, so we can shut it down
125 : // asynchronously without worrying about anything.
126 0 : ShutdownThread(thread);
127 : } else {
128 2 : thread->Dispatch(this, NS_DISPATCH_NORMAL);
129 : }
130 :
131 2 : return NS_OK;
132 : }
133 :
134 : void
135 0 : nsThreadPool::ShutdownThread(nsIThread* aThread)
136 : {
137 0 : LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread));
138 :
139 : // This is either called by a threadpool thread that is out of work, or
140 : // a thread that attempted to create a threadpool thread and raced in
141 : // such a way that the newly created thread is no longer necessary.
142 : // In the first case, we must go to another thread to shut aThread down
143 : // (because it is the current thread). In the second case, we cannot
144 : // synchronously shut down the current thread (because then Dispatch() would
145 : // spin the event loop, and that could blow up the world), and asynchronous
146 : // shutdown requires this thread have an event loop (and it may not, see bug
147 : // 10204784). The simplest way to cover all cases is to asynchronously
148 : // shutdown aThread from the main thread.
149 0 : NS_DispatchToMainThread(NewRunnableMethod("nsIThread::AsyncShutdown", aThread,
150 0 : &nsIThread::AsyncShutdown));
151 0 : }
152 :
153 : NS_IMETHODIMP
154 2 : nsThreadPool::Run()
155 : {
156 2 : LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading()));
157 :
158 2 : nsCOMPtr<nsIThread> current;
159 2 : nsThreadManager::get().GetCurrentThread(getter_AddRefs(current));
160 :
161 2 : bool shutdownThreadOnExit = false;
162 2 : bool exitThread = false;
163 2 : bool wasIdle = false;
164 : PRIntervalTime idleSince;
165 :
166 2 : nsCOMPtr<nsIThreadPoolListener> listener;
167 : {
168 4 : MutexAutoLock lock(mMutex);
169 2 : listener = mListener;
170 : }
171 :
172 2 : if (listener) {
173 0 : listener->OnThreadCreated();
174 : }
175 :
176 144 : do {
177 290 : nsCOMPtr<nsIRunnable> event;
178 : {
179 290 : MutexAutoLock lock(mMutex);
180 :
181 146 : if (!mEvents.GetPendingEvent(getter_AddRefs(event), lock)) {
182 73 : PRIntervalTime now = PR_IntervalNow();
183 73 : PRIntervalTime timeout = PR_MillisecondsToInterval(mIdleThreadTimeout);
184 :
185 : // If we are shutting down, then don't keep any idle threads
186 73 : if (mShutdown) {
187 0 : exitThread = true;
188 : } else {
189 73 : if (wasIdle) {
190 : // if too many idle threads or idle for too long, then bail.
191 0 : if (mIdleCount > mIdleThreadLimit ||
192 0 : (mIdleThreadTimeout != UINT32_MAX && (now - idleSince) >= timeout)) {
193 0 : exitThread = true;
194 : }
195 : } else {
196 : // if would be too many idle threads...
197 73 : if (mIdleCount == mIdleThreadLimit) {
198 0 : exitThread = true;
199 : } else {
200 73 : ++mIdleCount;
201 73 : idleSince = now;
202 73 : wasIdle = true;
203 : }
204 : }
205 : }
206 :
207 73 : if (exitThread) {
208 0 : if (wasIdle) {
209 0 : --mIdleCount;
210 : }
211 0 : shutdownThreadOnExit = mThreads.RemoveObject(current);
212 : } else {
213 73 : PRIntervalTime delta = timeout - (now - idleSince);
214 73 : LOG(("THRD-P(%p) %s waiting [%d]\n", this, mName.BeginReading(), delta));
215 73 : mEvents.Wait(delta);
216 71 : LOG(("THRD-P(%p) done waiting\n", this));
217 : }
218 73 : } else if (wasIdle) {
219 71 : wasIdle = false;
220 71 : --mIdleCount;
221 : }
222 : }
223 144 : if (event) {
224 73 : LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(), event.get()));
225 73 : event->Run();
226 : }
227 144 : } while (!exitThread);
228 :
229 0 : if (listener) {
230 0 : listener->OnThreadShuttingDown();
231 : }
232 :
233 0 : if (shutdownThreadOnExit) {
234 0 : ShutdownThread(current);
235 : }
236 :
237 0 : LOG(("THRD-P(%p) leave\n", this));
238 0 : return NS_OK;
239 : }
240 :
241 : NS_IMETHODIMP
242 0 : nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
243 : {
244 0 : nsCOMPtr<nsIRunnable> event(aEvent);
245 0 : return Dispatch(event.forget(), aFlags);
246 : }
247 :
248 : NS_IMETHODIMP
249 73 : nsThreadPool::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
250 : {
251 73 : LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags));
252 :
253 73 : if (NS_WARN_IF(mShutdown)) {
254 0 : return NS_ERROR_NOT_AVAILABLE;
255 : }
256 :
257 73 : if (aFlags & DISPATCH_SYNC) {
258 0 : nsCOMPtr<nsIThread> thread;
259 0 : nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread));
260 0 : if (NS_WARN_IF(!thread)) {
261 0 : return NS_ERROR_NOT_AVAILABLE;
262 : }
263 :
264 : RefPtr<nsThreadSyncDispatch> wrapper =
265 0 : new nsThreadSyncDispatch(thread, Move(aEvent));
266 0 : PutEvent(wrapper);
267 :
268 0 : SpinEventLoopUntil([&, wrapper]() -> bool {
269 0 : return !wrapper->IsPending();
270 0 : }, thread);
271 : } else {
272 73 : NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
273 : aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
274 73 : PutEvent(Move(aEvent), aFlags);
275 : }
276 73 : return NS_OK;
277 : }
278 :
279 : NS_IMETHODIMP
280 0 : nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
281 : {
282 0 : return NS_ERROR_NOT_IMPLEMENTED;
283 : }
284 :
285 : NS_IMETHODIMP_(bool)
286 0 : nsThreadPool::IsOnCurrentThreadInfallible()
287 : {
288 0 : MutexAutoLock lock(mMutex);
289 :
290 0 : nsIThread* thread = NS_GetCurrentThread();
291 0 : for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
292 0 : if (mThreads[i] == thread) {
293 0 : return true;
294 : }
295 : }
296 0 : return false;
297 : }
298 :
299 : NS_IMETHODIMP
300 0 : nsThreadPool::IsOnCurrentThread(bool* aResult)
301 : {
302 0 : MutexAutoLock lock(mMutex);
303 0 : if (NS_WARN_IF(mShutdown)) {
304 0 : return NS_ERROR_NOT_AVAILABLE;
305 : }
306 :
307 0 : nsIThread* thread = NS_GetCurrentThread();
308 0 : for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
309 0 : if (mThreads[i] == thread) {
310 0 : *aResult = true;
311 0 : return NS_OK;
312 : }
313 : }
314 0 : *aResult = false;
315 0 : return NS_OK;
316 : }
317 :
318 : NS_IMETHODIMP
319 0 : nsThreadPool::Shutdown()
320 : {
321 0 : nsCOMArray<nsIThread> threads;
322 0 : nsCOMPtr<nsIThreadPoolListener> listener;
323 : {
324 0 : MutexAutoLock lock(mMutex);
325 0 : mShutdown = true;
326 0 : mEvents.NotifyAll();
327 :
328 0 : threads.AppendObjects(mThreads);
329 0 : mThreads.Clear();
330 :
331 : // Swap in a null listener so that we release the listener at the end of
332 : // this method. The listener will be kept alive as long as the other threads
333 : // that were created when it was set.
334 0 : mListener.swap(listener);
335 : }
336 :
337 : // It's important that we shutdown the threads while outside the event queue
338 : // monitor. Otherwise, we could end up dead-locking.
339 :
340 0 : for (int32_t i = 0; i < threads.Count(); ++i) {
341 0 : threads[i]->Shutdown();
342 : }
343 :
344 0 : return NS_OK;
345 : }
346 :
347 : NS_IMETHODIMP
348 0 : nsThreadPool::GetThreadLimit(uint32_t* aValue)
349 : {
350 0 : *aValue = mThreadLimit;
351 0 : return NS_OK;
352 : }
353 :
354 : NS_IMETHODIMP
355 6 : nsThreadPool::SetThreadLimit(uint32_t aValue)
356 : {
357 12 : MutexAutoLock lock(mMutex);
358 6 : LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue));
359 6 : mThreadLimit = aValue;
360 6 : if (mIdleThreadLimit > mThreadLimit) {
361 0 : mIdleThreadLimit = mThreadLimit;
362 : }
363 :
364 6 : if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) {
365 0 : mEvents.NotifyAll(); // wake up threads so they observe this change
366 : }
367 12 : return NS_OK;
368 : }
369 :
370 : NS_IMETHODIMP
371 0 : nsThreadPool::GetIdleThreadLimit(uint32_t* aValue)
372 : {
373 0 : *aValue = mIdleThreadLimit;
374 0 : return NS_OK;
375 : }
376 :
377 : NS_IMETHODIMP
378 6 : nsThreadPool::SetIdleThreadLimit(uint32_t aValue)
379 : {
380 12 : MutexAutoLock lock(mMutex);
381 6 : LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue));
382 6 : mIdleThreadLimit = aValue;
383 6 : if (mIdleThreadLimit > mThreadLimit) {
384 3 : mIdleThreadLimit = mThreadLimit;
385 : }
386 :
387 : // Do we need to kill some idle threads?
388 6 : if (mIdleCount > mIdleThreadLimit) {
389 0 : mEvents.NotifyAll(); // wake up threads so they observe this change
390 : }
391 12 : return NS_OK;
392 : }
393 :
394 : NS_IMETHODIMP
395 0 : nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue)
396 : {
397 0 : *aValue = mIdleThreadTimeout;
398 0 : return NS_OK;
399 : }
400 :
401 : NS_IMETHODIMP
402 6 : nsThreadPool::SetIdleThreadTimeout(uint32_t aValue)
403 : {
404 12 : MutexAutoLock lock(mMutex);
405 6 : uint32_t oldTimeout = mIdleThreadTimeout;
406 6 : mIdleThreadTimeout = aValue;
407 :
408 : // Do we need to notify any idle threads that their sleep time has shortened?
409 6 : if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) {
410 0 : mEvents.NotifyAll(); // wake up threads so they observe this change
411 : }
412 12 : return NS_OK;
413 : }
414 :
415 : NS_IMETHODIMP
416 0 : nsThreadPool::GetThreadStackSize(uint32_t* aValue)
417 : {
418 0 : MutexAutoLock lock(mMutex);
419 0 : *aValue = mStackSize;
420 0 : return NS_OK;
421 : }
422 :
423 : NS_IMETHODIMP
424 0 : nsThreadPool::SetThreadStackSize(uint32_t aValue)
425 : {
426 0 : MutexAutoLock lock(mMutex);
427 0 : mStackSize = aValue;
428 0 : return NS_OK;
429 : }
430 :
431 : NS_IMETHODIMP
432 0 : nsThreadPool::GetListener(nsIThreadPoolListener** aListener)
433 : {
434 0 : MutexAutoLock lock(mMutex);
435 0 : NS_IF_ADDREF(*aListener = mListener);
436 0 : return NS_OK;
437 : }
438 :
439 : NS_IMETHODIMP
440 0 : nsThreadPool::SetListener(nsIThreadPoolListener* aListener)
441 : {
442 0 : nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener);
443 : {
444 0 : MutexAutoLock lock(mMutex);
445 0 : mListener.swap(swappedListener);
446 : }
447 0 : return NS_OK;
448 : }
449 :
450 : NS_IMETHODIMP
451 6 : nsThreadPool::SetName(const nsACString& aName)
452 : {
453 : {
454 12 : MutexAutoLock lock(mMutex);
455 6 : if (mThreads.Count()) {
456 0 : return NS_ERROR_NOT_AVAILABLE;
457 : }
458 : }
459 :
460 6 : mName = aName;
461 6 : return NS_OK;
462 : }
|