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