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: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=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 "LazyIdleThread.h"
9 #include "nsIObserverService.h"
11 #include "GeckoProfiler.h"
12 #include "nsComponentManagerUtils.h"
13 #include "nsServiceManagerUtils.h"
14 #include "nsThreadUtils.h"
15 #include "mozilla/Services.h"
17 #ifdef DEBUG
18 #define ASSERT_OWNING_THREAD() \
19 PR_BEGIN_MACRO \
20 nsIThread* currentThread = NS_GetCurrentThread(); \
21 if (currentThread) { \
22 nsCOMPtr<nsISupports> current(do_QueryInterface(currentThread)); \
23 nsCOMPtr<nsISupports> test(do_QueryInterface(mOwningThread)); \
24 MOZ_ASSERT(current == test, "Wrong thread!"); \
25 } \
26 PR_END_MACRO
27 #else
28 #define ASSERT_OWNING_THREAD() /* nothing */
29 #endif
31 namespace mozilla {
33 LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS,
34 const nsCSubstring& aName,
35 ShutdownMethod aShutdownMethod,
36 nsIObserver* aIdleObserver)
37 : mMutex("LazyIdleThread::mMutex"),
38 mOwningThread(NS_GetCurrentThread()),
39 mIdleObserver(aIdleObserver),
40 mQueuedRunnables(nullptr),
41 mIdleTimeoutMS(aIdleTimeoutMS),
42 mPendingEventCount(0),
43 mIdleNotificationCount(0),
44 mShutdownMethod(aShutdownMethod),
45 mShutdown(false),
46 mThreadIsShuttingDown(false),
47 mIdleTimeoutEnabled(true),
48 mName(aName)
49 {
50 MOZ_ASSERT(mOwningThread, "Need owning thread!");
51 }
53 LazyIdleThread::~LazyIdleThread()
54 {
55 ASSERT_OWNING_THREAD();
57 Shutdown();
58 }
60 void
61 LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver)
62 {
63 ASSERT_OWNING_THREAD();
65 if (mShutdown) {
66 NS_WARN_IF_FALSE(!aObserver,
67 "Setting an observer after Shutdown was called!");
68 return;
69 }
71 mIdleObserver = aObserver;
72 }
74 void
75 LazyIdleThread::DisableIdleTimeout()
76 {
77 ASSERT_OWNING_THREAD();
78 if (!mIdleTimeoutEnabled) {
79 return;
80 }
81 mIdleTimeoutEnabled = false;
83 if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) {
84 NS_WARNING("Failed to cancel timer!");
85 }
87 MutexAutoLock lock(mMutex);
89 // Pretend we have a pending event to keep the idle timer from firing.
90 MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
91 mPendingEventCount++;
92 }
94 void
95 LazyIdleThread::EnableIdleTimeout()
96 {
97 ASSERT_OWNING_THREAD();
98 if (mIdleTimeoutEnabled) {
99 return;
100 }
101 mIdleTimeoutEnabled = true;
103 {
104 MutexAutoLock lock(mMutex);
106 MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
107 --mPendingEventCount;
108 }
110 if (mThread) {
111 nsCOMPtr<nsIRunnable> runnable(new nsRunnable());
112 if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) {
113 NS_WARNING("Failed to dispatch!");
114 }
115 }
116 }
118 void
119 LazyIdleThread::PreDispatch()
120 {
121 MutexAutoLock lock(mMutex);
123 MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!");
124 mPendingEventCount++;
125 }
127 nsresult
128 LazyIdleThread::EnsureThread()
129 {
130 ASSERT_OWNING_THREAD();
132 if (mShutdown) {
133 return NS_ERROR_UNEXPECTED;
134 }
136 if (mThread) {
137 return NS_OK;
138 }
140 MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!");
141 MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!");
142 MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!");
143 MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!");
145 nsresult rv;
147 if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
148 nsCOMPtr<nsIObserverService> obs =
149 do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
150 if (NS_WARN_IF(NS_FAILED(rv)))
151 return rv;
153 rv = obs->AddObserver(this, "xpcom-shutdown-threads", false);
154 if (NS_WARN_IF(NS_FAILED(rv)))
155 return rv;
156 }
158 mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
159 if (NS_WARN_IF(!mIdleTimer))
160 return NS_ERROR_UNEXPECTED;
162 nsCOMPtr<nsIRunnable> runnable =
163 NS_NewRunnableMethod(this, &LazyIdleThread::InitThread);
164 if (NS_WARN_IF(!runnable))
165 return NS_ERROR_UNEXPECTED;
167 rv = NS_NewThread(getter_AddRefs(mThread), runnable);
168 if (NS_WARN_IF(NS_FAILED(rv)))
169 return rv;
171 return NS_OK;
172 }
174 void
175 LazyIdleThread::InitThread()
176 {
177 char aLocal;
178 profiler_register_thread(mName.get(), &aLocal);
180 PR_SetCurrentThreadName(mName.get());
182 // Happens on mThread but mThread may not be set yet...
184 nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
185 MOZ_ASSERT(thread, "This should always succeed!");
187 if (NS_FAILED(thread->SetObserver(this))) {
188 NS_WARNING("Failed to set thread observer!");
189 }
190 }
192 void
193 LazyIdleThread::CleanupThread()
194 {
195 nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread()));
196 MOZ_ASSERT(thread, "This should always succeed!");
198 if (NS_FAILED(thread->SetObserver(nullptr))) {
199 NS_WARNING("Failed to set thread observer!");
200 }
202 {
203 MutexAutoLock lock(mMutex);
205 MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!");
206 mThreadIsShuttingDown = true;
207 }
209 profiler_unregister_thread();
210 }
212 void
213 LazyIdleThread::ScheduleTimer()
214 {
215 ASSERT_OWNING_THREAD();
217 bool shouldSchedule;
218 {
219 MutexAutoLock lock(mMutex);
221 MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!");
222 --mIdleNotificationCount;
224 shouldSchedule = !mIdleNotificationCount && !mPendingEventCount;
225 }
227 if (NS_FAILED(mIdleTimer->Cancel())) {
228 NS_WARNING("Failed to cancel timer!");
229 }
231 if (shouldSchedule &&
232 NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS,
233 nsITimer::TYPE_ONE_SHOT))) {
234 NS_WARNING("Failed to schedule timer!");
235 }
236 }
238 nsresult
239 LazyIdleThread::ShutdownThread()
240 {
241 ASSERT_OWNING_THREAD();
243 // Before calling Shutdown() on the real thread we need to put a queue in
244 // place in case a runnable is posted to the thread while it's in the
245 // process of shutting down. This will be our queue.
246 nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables;
248 nsresult rv;
250 if (mThread) {
251 if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) {
252 nsCOMPtr<nsIObserverService> obs =
253 mozilla::services::GetObserverService();
254 NS_WARN_IF_FALSE(obs, "Failed to get observer service!");
256 if (obs &&
257 NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) {
258 NS_WARNING("Failed to remove observer!");
259 }
260 }
262 if (mIdleObserver) {
263 mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC,
264 nullptr);
265 }
267 #ifdef DEBUG
268 {
269 MutexAutoLock lock(mMutex);
270 MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!");
271 }
272 #endif
274 nsCOMPtr<nsIRunnable> runnable =
275 NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread);
276 if (NS_WARN_IF(!runnable))
277 return NS_ERROR_UNEXPECTED;
279 PreDispatch();
281 rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
282 if (NS_WARN_IF(NS_FAILED(rv)))
283 return rv;
285 // Put the temporary queue in place before calling Shutdown().
286 mQueuedRunnables = &queuedRunnables;
288 if (NS_FAILED(mThread->Shutdown())) {
289 NS_ERROR("Failed to shutdown the thread!");
290 }
292 // Now unset the queue.
293 mQueuedRunnables = nullptr;
295 mThread = nullptr;
297 {
298 MutexAutoLock lock(mMutex);
300 MOZ_ASSERT(!mPendingEventCount, "Huh?!");
301 MOZ_ASSERT(!mIdleNotificationCount, "Huh?!");
302 MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!");
303 mThreadIsShuttingDown = false;
304 }
305 }
307 if (mIdleTimer) {
308 rv = mIdleTimer->Cancel();
309 if (NS_WARN_IF(NS_FAILED(rv)))
310 return rv;
312 mIdleTimer = nullptr;
313 }
315 // If our temporary queue has any runnables then we need to dispatch them.
316 if (queuedRunnables.Length()) {
317 // If the thread manager has gone away then these runnables will never run.
318 if (mShutdown) {
319 NS_ERROR("Runnables dispatched to LazyIdleThread will never run!");
320 return NS_OK;
321 }
323 // Re-dispatch the queued runnables.
324 for (uint32_t index = 0; index < queuedRunnables.Length(); index++) {
325 nsCOMPtr<nsIRunnable> runnable;
326 runnable.swap(queuedRunnables[index]);
327 MOZ_ASSERT(runnable, "Null runnable?!");
329 if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) {
330 NS_ERROR("Failed to re-dispatch queued runnable!");
331 }
332 }
333 }
335 return NS_OK;
336 }
338 void
339 LazyIdleThread::SelfDestruct()
340 {
341 MOZ_ASSERT(mRefCnt == 1, "Bad refcount!");
342 delete this;
343 }
345 NS_IMPL_ADDREF(LazyIdleThread)
347 NS_IMETHODIMP_(MozExternalRefCountType)
348 LazyIdleThread::Release()
349 {
350 nsrefcnt count = --mRefCnt;
351 NS_LOG_RELEASE(this, count, "LazyIdleThread");
353 if (!count) {
354 // Stabilize refcount.
355 mRefCnt = 1;
357 nsCOMPtr<nsIRunnable> runnable =
358 NS_NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct);
359 NS_WARN_IF_FALSE(runnable, "Couldn't make runnable!");
361 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
362 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
363 // The only way this could fail is if we're in shutdown, and in that case
364 // threads should have been joined already. Deleting here isn't dangerous
365 // anymore because we won't spin the event loop waiting to join the
366 // thread.
367 SelfDestruct();
368 }
369 }
371 return count;
372 }
374 NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread,
375 nsIEventTarget,
376 nsITimerCallback,
377 nsIThreadObserver,
378 nsIObserver)
380 NS_IMETHODIMP
381 LazyIdleThread::Dispatch(nsIRunnable* aEvent,
382 uint32_t aFlags)
383 {
384 ASSERT_OWNING_THREAD();
386 // LazyIdleThread can't always support synchronous dispatch currently.
387 if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL))
388 return NS_ERROR_NOT_IMPLEMENTED;
390 // If our thread is shutting down then we can't actually dispatch right now.
391 // Queue this runnable for later.
392 if (UseRunnableQueue()) {
393 mQueuedRunnables->AppendElement(aEvent);
394 return NS_OK;
395 }
397 nsresult rv = EnsureThread();
398 if (NS_WARN_IF(NS_FAILED(rv)))
399 return rv;
401 PreDispatch();
403 return mThread->Dispatch(aEvent, aFlags);
404 }
406 NS_IMETHODIMP
407 LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread)
408 {
409 if (mThread) {
410 return mThread->IsOnCurrentThread(aIsOnCurrentThread);
411 }
413 *aIsOnCurrentThread = false;
414 return NS_OK;
415 }
417 NS_IMETHODIMP
418 LazyIdleThread::GetPRThread(PRThread** aPRThread)
419 {
420 if (mThread) {
421 return mThread->GetPRThread(aPRThread);
422 }
424 *aPRThread = nullptr;
425 return NS_ERROR_NOT_AVAILABLE;
426 }
428 NS_IMETHODIMP
429 LazyIdleThread::Shutdown()
430 {
431 ASSERT_OWNING_THREAD();
433 mShutdown = true;
435 nsresult rv = ShutdownThread();
436 MOZ_ASSERT(!mThread, "Should have destroyed this by now!");
438 mIdleObserver = nullptr;
440 if (NS_WARN_IF(NS_FAILED(rv)))
441 return rv;
443 return NS_OK;
444 }
446 NS_IMETHODIMP
447 LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents)
448 {
449 // This is only supposed to be called from the thread itself so it's not
450 // implemented here.
451 NS_NOTREACHED("Shouldn't ever call this!");
452 return NS_ERROR_UNEXPECTED;
453 }
455 NS_IMETHODIMP
456 LazyIdleThread::ProcessNextEvent(bool aMayWait,
457 bool* aEventWasProcessed)
458 {
459 // This is only supposed to be called from the thread itself so it's not
460 // implemented here.
461 NS_NOTREACHED("Shouldn't ever call this!");
462 return NS_ERROR_UNEXPECTED;
463 }
465 NS_IMETHODIMP
466 LazyIdleThread::Notify(nsITimer* aTimer)
467 {
468 ASSERT_OWNING_THREAD();
470 {
471 MutexAutoLock lock(mMutex);
473 if (mPendingEventCount || mIdleNotificationCount) {
474 // Another event was scheduled since this timer was set. Don't do
475 // anything and wait for the timer to fire again.
476 return NS_OK;
477 }
478 }
480 nsresult rv = ShutdownThread();
481 if (NS_WARN_IF(NS_FAILED(rv)))
482 return rv;
484 return NS_OK;
485 }
487 NS_IMETHODIMP
488 LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */)
489 {
490 MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!");
491 return NS_OK;
492 }
494 NS_IMETHODIMP
495 LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
496 bool /* aMayWait */,
497 uint32_t /* aRecursionDepth */)
498 {
499 return NS_OK;
500 }
502 NS_IMETHODIMP
503 LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
504 uint32_t /* aRecursionDepth */,
505 bool aEventWasProcessed)
506 {
507 bool shouldNotifyIdle;
508 {
509 MutexAutoLock lock(mMutex);
511 if (aEventWasProcessed) {
512 MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!");
513 --mPendingEventCount;
514 }
516 if (mThreadIsShuttingDown) {
517 // We're shutting down, no need to fire any timer.
518 return NS_OK;
519 }
521 shouldNotifyIdle = !mPendingEventCount;
522 if (shouldNotifyIdle) {
523 MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!");
524 mIdleNotificationCount++;
525 }
526 }
528 if (shouldNotifyIdle) {
529 nsCOMPtr<nsIRunnable> runnable =
530 NS_NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer);
531 if (NS_WARN_IF(!runnable))
532 return NS_ERROR_UNEXPECTED;
534 nsresult rv = mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
535 if (NS_WARN_IF(NS_FAILED(rv)))
536 return rv;
537 }
539 return NS_OK;
540 }
542 NS_IMETHODIMP
543 LazyIdleThread::Observe(nsISupports* /* aSubject */,
544 const char* aTopic,
545 const char16_t* /* aData */)
546 {
547 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
548 MOZ_ASSERT(mShutdownMethod == AutomaticShutdown,
549 "Should not receive notifications if not AutomaticShutdown!");
550 MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");
552 Shutdown();
553 return NS_OK;
554 }
556 } // namespace mozilla