1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/threads/LazyIdleThread.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,556 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "LazyIdleThread.h" 1.11 + 1.12 +#include "nsIObserverService.h" 1.13 + 1.14 +#include "GeckoProfiler.h" 1.15 +#include "nsComponentManagerUtils.h" 1.16 +#include "nsServiceManagerUtils.h" 1.17 +#include "nsThreadUtils.h" 1.18 +#include "mozilla/Services.h" 1.19 + 1.20 +#ifdef DEBUG 1.21 +#define ASSERT_OWNING_THREAD() \ 1.22 + PR_BEGIN_MACRO \ 1.23 + nsIThread* currentThread = NS_GetCurrentThread(); \ 1.24 + if (currentThread) { \ 1.25 + nsCOMPtr<nsISupports> current(do_QueryInterface(currentThread)); \ 1.26 + nsCOMPtr<nsISupports> test(do_QueryInterface(mOwningThread)); \ 1.27 + MOZ_ASSERT(current == test, "Wrong thread!"); \ 1.28 + } \ 1.29 + PR_END_MACRO 1.30 +#else 1.31 +#define ASSERT_OWNING_THREAD() /* nothing */ 1.32 +#endif 1.33 + 1.34 +namespace mozilla { 1.35 + 1.36 +LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, 1.37 + const nsCSubstring& aName, 1.38 + ShutdownMethod aShutdownMethod, 1.39 + nsIObserver* aIdleObserver) 1.40 +: mMutex("LazyIdleThread::mMutex"), 1.41 + mOwningThread(NS_GetCurrentThread()), 1.42 + mIdleObserver(aIdleObserver), 1.43 + mQueuedRunnables(nullptr), 1.44 + mIdleTimeoutMS(aIdleTimeoutMS), 1.45 + mPendingEventCount(0), 1.46 + mIdleNotificationCount(0), 1.47 + mShutdownMethod(aShutdownMethod), 1.48 + mShutdown(false), 1.49 + mThreadIsShuttingDown(false), 1.50 + mIdleTimeoutEnabled(true), 1.51 + mName(aName) 1.52 +{ 1.53 + MOZ_ASSERT(mOwningThread, "Need owning thread!"); 1.54 +} 1.55 + 1.56 +LazyIdleThread::~LazyIdleThread() 1.57 +{ 1.58 + ASSERT_OWNING_THREAD(); 1.59 + 1.60 + Shutdown(); 1.61 +} 1.62 + 1.63 +void 1.64 +LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver) 1.65 +{ 1.66 + ASSERT_OWNING_THREAD(); 1.67 + 1.68 + if (mShutdown) { 1.69 + NS_WARN_IF_FALSE(!aObserver, 1.70 + "Setting an observer after Shutdown was called!"); 1.71 + return; 1.72 + } 1.73 + 1.74 + mIdleObserver = aObserver; 1.75 +} 1.76 + 1.77 +void 1.78 +LazyIdleThread::DisableIdleTimeout() 1.79 +{ 1.80 + ASSERT_OWNING_THREAD(); 1.81 + if (!mIdleTimeoutEnabled) { 1.82 + return; 1.83 + } 1.84 + mIdleTimeoutEnabled = false; 1.85 + 1.86 + if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) { 1.87 + NS_WARNING("Failed to cancel timer!"); 1.88 + } 1.89 + 1.90 + MutexAutoLock lock(mMutex); 1.91 + 1.92 + // Pretend we have a pending event to keep the idle timer from firing. 1.93 + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); 1.94 + mPendingEventCount++; 1.95 +} 1.96 + 1.97 +void 1.98 +LazyIdleThread::EnableIdleTimeout() 1.99 +{ 1.100 + ASSERT_OWNING_THREAD(); 1.101 + if (mIdleTimeoutEnabled) { 1.102 + return; 1.103 + } 1.104 + mIdleTimeoutEnabled = true; 1.105 + 1.106 + { 1.107 + MutexAutoLock lock(mMutex); 1.108 + 1.109 + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); 1.110 + --mPendingEventCount; 1.111 + } 1.112 + 1.113 + if (mThread) { 1.114 + nsCOMPtr<nsIRunnable> runnable(new nsRunnable()); 1.115 + if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) { 1.116 + NS_WARNING("Failed to dispatch!"); 1.117 + } 1.118 + } 1.119 +} 1.120 + 1.121 +void 1.122 +LazyIdleThread::PreDispatch() 1.123 +{ 1.124 + MutexAutoLock lock(mMutex); 1.125 + 1.126 + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); 1.127 + mPendingEventCount++; 1.128 +} 1.129 + 1.130 +nsresult 1.131 +LazyIdleThread::EnsureThread() 1.132 +{ 1.133 + ASSERT_OWNING_THREAD(); 1.134 + 1.135 + if (mShutdown) { 1.136 + return NS_ERROR_UNEXPECTED; 1.137 + } 1.138 + 1.139 + if (mThread) { 1.140 + return NS_OK; 1.141 + } 1.142 + 1.143 + MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!"); 1.144 + MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!"); 1.145 + MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!"); 1.146 + MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!"); 1.147 + 1.148 + nsresult rv; 1.149 + 1.150 + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { 1.151 + nsCOMPtr<nsIObserverService> obs = 1.152 + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); 1.153 + if (NS_WARN_IF(NS_FAILED(rv))) 1.154 + return rv; 1.155 + 1.156 + rv = obs->AddObserver(this, "xpcom-shutdown-threads", false); 1.157 + if (NS_WARN_IF(NS_FAILED(rv))) 1.158 + return rv; 1.159 + } 1.160 + 1.161 + mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); 1.162 + if (NS_WARN_IF(!mIdleTimer)) 1.163 + return NS_ERROR_UNEXPECTED; 1.164 + 1.165 + nsCOMPtr<nsIRunnable> runnable = 1.166 + NS_NewRunnableMethod(this, &LazyIdleThread::InitThread); 1.167 + if (NS_WARN_IF(!runnable)) 1.168 + return NS_ERROR_UNEXPECTED; 1.169 + 1.170 + rv = NS_NewThread(getter_AddRefs(mThread), runnable); 1.171 + if (NS_WARN_IF(NS_FAILED(rv))) 1.172 + return rv; 1.173 + 1.174 + return NS_OK; 1.175 +} 1.176 + 1.177 +void 1.178 +LazyIdleThread::InitThread() 1.179 +{ 1.180 + char aLocal; 1.181 + profiler_register_thread(mName.get(), &aLocal); 1.182 + 1.183 + PR_SetCurrentThreadName(mName.get()); 1.184 + 1.185 + // Happens on mThread but mThread may not be set yet... 1.186 + 1.187 + nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread())); 1.188 + MOZ_ASSERT(thread, "This should always succeed!"); 1.189 + 1.190 + if (NS_FAILED(thread->SetObserver(this))) { 1.191 + NS_WARNING("Failed to set thread observer!"); 1.192 + } 1.193 +} 1.194 + 1.195 +void 1.196 +LazyIdleThread::CleanupThread() 1.197 +{ 1.198 + nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread())); 1.199 + MOZ_ASSERT(thread, "This should always succeed!"); 1.200 + 1.201 + if (NS_FAILED(thread->SetObserver(nullptr))) { 1.202 + NS_WARNING("Failed to set thread observer!"); 1.203 + } 1.204 + 1.205 + { 1.206 + MutexAutoLock lock(mMutex); 1.207 + 1.208 + MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!"); 1.209 + mThreadIsShuttingDown = true; 1.210 + } 1.211 + 1.212 + profiler_unregister_thread(); 1.213 +} 1.214 + 1.215 +void 1.216 +LazyIdleThread::ScheduleTimer() 1.217 +{ 1.218 + ASSERT_OWNING_THREAD(); 1.219 + 1.220 + bool shouldSchedule; 1.221 + { 1.222 + MutexAutoLock lock(mMutex); 1.223 + 1.224 + MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!"); 1.225 + --mIdleNotificationCount; 1.226 + 1.227 + shouldSchedule = !mIdleNotificationCount && !mPendingEventCount; 1.228 + } 1.229 + 1.230 + if (NS_FAILED(mIdleTimer->Cancel())) { 1.231 + NS_WARNING("Failed to cancel timer!"); 1.232 + } 1.233 + 1.234 + if (shouldSchedule && 1.235 + NS_FAILED(mIdleTimer->InitWithCallback(this, mIdleTimeoutMS, 1.236 + nsITimer::TYPE_ONE_SHOT))) { 1.237 + NS_WARNING("Failed to schedule timer!"); 1.238 + } 1.239 +} 1.240 + 1.241 +nsresult 1.242 +LazyIdleThread::ShutdownThread() 1.243 +{ 1.244 + ASSERT_OWNING_THREAD(); 1.245 + 1.246 + // Before calling Shutdown() on the real thread we need to put a queue in 1.247 + // place in case a runnable is posted to the thread while it's in the 1.248 + // process of shutting down. This will be our queue. 1.249 + nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables; 1.250 + 1.251 + nsresult rv; 1.252 + 1.253 + if (mThread) { 1.254 + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { 1.255 + nsCOMPtr<nsIObserverService> obs = 1.256 + mozilla::services::GetObserverService(); 1.257 + NS_WARN_IF_FALSE(obs, "Failed to get observer service!"); 1.258 + 1.259 + if (obs && 1.260 + NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) { 1.261 + NS_WARNING("Failed to remove observer!"); 1.262 + } 1.263 + } 1.264 + 1.265 + if (mIdleObserver) { 1.266 + mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC, 1.267 + nullptr); 1.268 + } 1.269 + 1.270 +#ifdef DEBUG 1.271 + { 1.272 + MutexAutoLock lock(mMutex); 1.273 + MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!"); 1.274 + } 1.275 +#endif 1.276 + 1.277 + nsCOMPtr<nsIRunnable> runnable = 1.278 + NS_NewRunnableMethod(this, &LazyIdleThread::CleanupThread); 1.279 + if (NS_WARN_IF(!runnable)) 1.280 + return NS_ERROR_UNEXPECTED; 1.281 + 1.282 + PreDispatch(); 1.283 + 1.284 + rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 1.285 + if (NS_WARN_IF(NS_FAILED(rv))) 1.286 + return rv; 1.287 + 1.288 + // Put the temporary queue in place before calling Shutdown(). 1.289 + mQueuedRunnables = &queuedRunnables; 1.290 + 1.291 + if (NS_FAILED(mThread->Shutdown())) { 1.292 + NS_ERROR("Failed to shutdown the thread!"); 1.293 + } 1.294 + 1.295 + // Now unset the queue. 1.296 + mQueuedRunnables = nullptr; 1.297 + 1.298 + mThread = nullptr; 1.299 + 1.300 + { 1.301 + MutexAutoLock lock(mMutex); 1.302 + 1.303 + MOZ_ASSERT(!mPendingEventCount, "Huh?!"); 1.304 + MOZ_ASSERT(!mIdleNotificationCount, "Huh?!"); 1.305 + MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!"); 1.306 + mThreadIsShuttingDown = false; 1.307 + } 1.308 + } 1.309 + 1.310 + if (mIdleTimer) { 1.311 + rv = mIdleTimer->Cancel(); 1.312 + if (NS_WARN_IF(NS_FAILED(rv))) 1.313 + return rv; 1.314 + 1.315 + mIdleTimer = nullptr; 1.316 + } 1.317 + 1.318 + // If our temporary queue has any runnables then we need to dispatch them. 1.319 + if (queuedRunnables.Length()) { 1.320 + // If the thread manager has gone away then these runnables will never run. 1.321 + if (mShutdown) { 1.322 + NS_ERROR("Runnables dispatched to LazyIdleThread will never run!"); 1.323 + return NS_OK; 1.324 + } 1.325 + 1.326 + // Re-dispatch the queued runnables. 1.327 + for (uint32_t index = 0; index < queuedRunnables.Length(); index++) { 1.328 + nsCOMPtr<nsIRunnable> runnable; 1.329 + runnable.swap(queuedRunnables[index]); 1.330 + MOZ_ASSERT(runnable, "Null runnable?!"); 1.331 + 1.332 + if (NS_FAILED(Dispatch(runnable, NS_DISPATCH_NORMAL))) { 1.333 + NS_ERROR("Failed to re-dispatch queued runnable!"); 1.334 + } 1.335 + } 1.336 + } 1.337 + 1.338 + return NS_OK; 1.339 +} 1.340 + 1.341 +void 1.342 +LazyIdleThread::SelfDestruct() 1.343 +{ 1.344 + MOZ_ASSERT(mRefCnt == 1, "Bad refcount!"); 1.345 + delete this; 1.346 +} 1.347 + 1.348 +NS_IMPL_ADDREF(LazyIdleThread) 1.349 + 1.350 +NS_IMETHODIMP_(MozExternalRefCountType) 1.351 +LazyIdleThread::Release() 1.352 +{ 1.353 + nsrefcnt count = --mRefCnt; 1.354 + NS_LOG_RELEASE(this, count, "LazyIdleThread"); 1.355 + 1.356 + if (!count) { 1.357 + // Stabilize refcount. 1.358 + mRefCnt = 1; 1.359 + 1.360 + nsCOMPtr<nsIRunnable> runnable = 1.361 + NS_NewNonOwningRunnableMethod(this, &LazyIdleThread::SelfDestruct); 1.362 + NS_WARN_IF_FALSE(runnable, "Couldn't make runnable!"); 1.363 + 1.364 + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { 1.365 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.366 + // The only way this could fail is if we're in shutdown, and in that case 1.367 + // threads should have been joined already. Deleting here isn't dangerous 1.368 + // anymore because we won't spin the event loop waiting to join the 1.369 + // thread. 1.370 + SelfDestruct(); 1.371 + } 1.372 + } 1.373 + 1.374 + return count; 1.375 +} 1.376 + 1.377 +NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, 1.378 + nsIEventTarget, 1.379 + nsITimerCallback, 1.380 + nsIThreadObserver, 1.381 + nsIObserver) 1.382 + 1.383 +NS_IMETHODIMP 1.384 +LazyIdleThread::Dispatch(nsIRunnable* aEvent, 1.385 + uint32_t aFlags) 1.386 +{ 1.387 + ASSERT_OWNING_THREAD(); 1.388 + 1.389 + // LazyIdleThread can't always support synchronous dispatch currently. 1.390 + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) 1.391 + return NS_ERROR_NOT_IMPLEMENTED; 1.392 + 1.393 + // If our thread is shutting down then we can't actually dispatch right now. 1.394 + // Queue this runnable for later. 1.395 + if (UseRunnableQueue()) { 1.396 + mQueuedRunnables->AppendElement(aEvent); 1.397 + return NS_OK; 1.398 + } 1.399 + 1.400 + nsresult rv = EnsureThread(); 1.401 + if (NS_WARN_IF(NS_FAILED(rv))) 1.402 + return rv; 1.403 + 1.404 + PreDispatch(); 1.405 + 1.406 + return mThread->Dispatch(aEvent, aFlags); 1.407 +} 1.408 + 1.409 +NS_IMETHODIMP 1.410 +LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) 1.411 +{ 1.412 + if (mThread) { 1.413 + return mThread->IsOnCurrentThread(aIsOnCurrentThread); 1.414 + } 1.415 + 1.416 + *aIsOnCurrentThread = false; 1.417 + return NS_OK; 1.418 +} 1.419 + 1.420 +NS_IMETHODIMP 1.421 +LazyIdleThread::GetPRThread(PRThread** aPRThread) 1.422 +{ 1.423 + if (mThread) { 1.424 + return mThread->GetPRThread(aPRThread); 1.425 + } 1.426 + 1.427 + *aPRThread = nullptr; 1.428 + return NS_ERROR_NOT_AVAILABLE; 1.429 +} 1.430 + 1.431 +NS_IMETHODIMP 1.432 +LazyIdleThread::Shutdown() 1.433 +{ 1.434 + ASSERT_OWNING_THREAD(); 1.435 + 1.436 + mShutdown = true; 1.437 + 1.438 + nsresult rv = ShutdownThread(); 1.439 + MOZ_ASSERT(!mThread, "Should have destroyed this by now!"); 1.440 + 1.441 + mIdleObserver = nullptr; 1.442 + 1.443 + if (NS_WARN_IF(NS_FAILED(rv))) 1.444 + return rv; 1.445 + 1.446 + return NS_OK; 1.447 +} 1.448 + 1.449 +NS_IMETHODIMP 1.450 +LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents) 1.451 +{ 1.452 + // This is only supposed to be called from the thread itself so it's not 1.453 + // implemented here. 1.454 + NS_NOTREACHED("Shouldn't ever call this!"); 1.455 + return NS_ERROR_UNEXPECTED; 1.456 +} 1.457 + 1.458 +NS_IMETHODIMP 1.459 +LazyIdleThread::ProcessNextEvent(bool aMayWait, 1.460 + bool* aEventWasProcessed) 1.461 +{ 1.462 + // This is only supposed to be called from the thread itself so it's not 1.463 + // implemented here. 1.464 + NS_NOTREACHED("Shouldn't ever call this!"); 1.465 + return NS_ERROR_UNEXPECTED; 1.466 +} 1.467 + 1.468 +NS_IMETHODIMP 1.469 +LazyIdleThread::Notify(nsITimer* aTimer) 1.470 +{ 1.471 + ASSERT_OWNING_THREAD(); 1.472 + 1.473 + { 1.474 + MutexAutoLock lock(mMutex); 1.475 + 1.476 + if (mPendingEventCount || mIdleNotificationCount) { 1.477 + // Another event was scheduled since this timer was set. Don't do 1.478 + // anything and wait for the timer to fire again. 1.479 + return NS_OK; 1.480 + } 1.481 + } 1.482 + 1.483 + nsresult rv = ShutdownThread(); 1.484 + if (NS_WARN_IF(NS_FAILED(rv))) 1.485 + return rv; 1.486 + 1.487 + return NS_OK; 1.488 +} 1.489 + 1.490 +NS_IMETHODIMP 1.491 +LazyIdleThread::OnDispatchedEvent(nsIThreadInternal* /*aThread */) 1.492 +{ 1.493 + MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread, "Wrong thread!"); 1.494 + return NS_OK; 1.495 +} 1.496 + 1.497 +NS_IMETHODIMP 1.498 +LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */, 1.499 + bool /* aMayWait */, 1.500 + uint32_t /* aRecursionDepth */) 1.501 +{ 1.502 + return NS_OK; 1.503 +} 1.504 + 1.505 +NS_IMETHODIMP 1.506 +LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, 1.507 + uint32_t /* aRecursionDepth */, 1.508 + bool aEventWasProcessed) 1.509 +{ 1.510 + bool shouldNotifyIdle; 1.511 + { 1.512 + MutexAutoLock lock(mMutex); 1.513 + 1.514 + if (aEventWasProcessed) { 1.515 + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); 1.516 + --mPendingEventCount; 1.517 + } 1.518 + 1.519 + if (mThreadIsShuttingDown) { 1.520 + // We're shutting down, no need to fire any timer. 1.521 + return NS_OK; 1.522 + } 1.523 + 1.524 + shouldNotifyIdle = !mPendingEventCount; 1.525 + if (shouldNotifyIdle) { 1.526 + MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!"); 1.527 + mIdleNotificationCount++; 1.528 + } 1.529 + } 1.530 + 1.531 + if (shouldNotifyIdle) { 1.532 + nsCOMPtr<nsIRunnable> runnable = 1.533 + NS_NewRunnableMethod(this, &LazyIdleThread::ScheduleTimer); 1.534 + if (NS_WARN_IF(!runnable)) 1.535 + return NS_ERROR_UNEXPECTED; 1.536 + 1.537 + nsresult rv = mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL); 1.538 + if (NS_WARN_IF(NS_FAILED(rv))) 1.539 + return rv; 1.540 + } 1.541 + 1.542 + return NS_OK; 1.543 +} 1.544 + 1.545 +NS_IMETHODIMP 1.546 +LazyIdleThread::Observe(nsISupports* /* aSubject */, 1.547 + const char* aTopic, 1.548 + const char16_t* /* aData */) 1.549 +{ 1.550 + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 1.551 + MOZ_ASSERT(mShutdownMethod == AutomaticShutdown, 1.552 + "Should not receive notifications if not AutomaticShutdown!"); 1.553 + MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); 1.554 + 1.555 + Shutdown(); 1.556 + return NS_OK; 1.557 +} 1.558 + 1.559 +} // namespace mozilla