michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsTimerImpl.h" michael@0: #include "TimerThread.h" michael@0: michael@0: #include "nsThreadUtils.h" michael@0: #include "pratom.h" michael@0: michael@0: #include "nsIObserverService.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/ChaosMode.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: NS_IMPL_ISUPPORTS(TimerThread, nsIRunnable, nsIObserver) michael@0: michael@0: TimerThread::TimerThread() : michael@0: mInitInProgress(false), michael@0: mInitialized(false), michael@0: mMonitor("TimerThread.mMonitor"), michael@0: mShutdown(false), michael@0: mWaiting(false), michael@0: mNotified(false), michael@0: mSleeping(false) michael@0: { michael@0: } michael@0: michael@0: TimerThread::~TimerThread() michael@0: { michael@0: mThread = nullptr; michael@0: michael@0: NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread"); michael@0: } michael@0: michael@0: nsresult michael@0: TimerThread::InitLocks() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class TimerObserverRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: TimerObserverRunnable(nsIObserver* observer) michael@0: : mObserver(observer) michael@0: { } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: private: michael@0: nsCOMPtr mObserver; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: TimerObserverRunnable::Run() michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->AddObserver(mObserver, "sleep_notification", false); michael@0: observerService->AddObserver(mObserver, "wake_notification", false); michael@0: observerService->AddObserver(mObserver, "suspend_process_notification", false); michael@0: observerService->AddObserver(mObserver, "resume_process_notification", false); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: nsresult TimerThread::Init() michael@0: { michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Init [%d]\n", mInitialized)); michael@0: michael@0: if (mInitialized) { michael@0: if (!mThread) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mInitInProgress.exchange(true) == false) { michael@0: // We hold on to mThread to keep the thread alive. michael@0: nsresult rv = NS_NewThread(getter_AddRefs(mThread), this); michael@0: if (NS_FAILED(rv)) { michael@0: mThread = nullptr; michael@0: } michael@0: else { michael@0: nsRefPtr r = new TimerObserverRunnable(this); michael@0: if (NS_IsMainThread()) { michael@0: r->Run(); michael@0: } michael@0: else { michael@0: NS_DispatchToMainThread(r); michael@0: } michael@0: } michael@0: michael@0: { michael@0: MonitorAutoLock lock(mMonitor); michael@0: mInitialized = true; michael@0: mMonitor.NotifyAll(); michael@0: } michael@0: } michael@0: else { michael@0: MonitorAutoLock lock(mMonitor); michael@0: while (!mInitialized) { michael@0: mMonitor.Wait(); michael@0: } michael@0: } michael@0: michael@0: if (!mThread) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult TimerThread::Shutdown() michael@0: { michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown begin\n")); michael@0: michael@0: if (!mThread) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsTArray timers; michael@0: { // lock scope michael@0: MonitorAutoLock lock(mMonitor); michael@0: michael@0: mShutdown = true; michael@0: michael@0: // notify the cond var so that Run() can return michael@0: if (mWaiting) { michael@0: mNotified = true; michael@0: mMonitor.Notify(); michael@0: } michael@0: michael@0: // Need to copy content of mTimers array to a local array michael@0: // because call to timers' ReleaseCallback() (and release its self) michael@0: // must not be done under the lock. Destructor of a callback michael@0: // might potentially call some code reentering the same lock michael@0: // that leads to unexpected behavior or deadlock. michael@0: // See bug 422472. michael@0: timers.AppendElements(mTimers); michael@0: mTimers.Clear(); michael@0: } michael@0: michael@0: uint32_t timersCount = timers.Length(); michael@0: for (uint32_t i = 0; i < timersCount; i++) { michael@0: nsTimerImpl *timer = timers[i]; michael@0: timer->ReleaseCallback(); michael@0: ReleaseTimerInternal(timer); michael@0: } michael@0: michael@0: mThread->Shutdown(); // wait for the thread to die michael@0: michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("TimerThread::Shutdown end\n")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: michael@0: /* void Run(); */ michael@0: NS_IMETHODIMP TimerThread::Run() michael@0: { michael@0: PR_SetCurrentThreadName("Timer"); michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: if (IsNuwaProcess()) { michael@0: NS_ASSERTION(NuwaMarkCurrentThread != nullptr, michael@0: "NuwaMarkCurrentThread is undefined!"); michael@0: NuwaMarkCurrentThread(nullptr, nullptr); michael@0: } michael@0: #endif michael@0: michael@0: MonitorAutoLock lock(mMonitor); michael@0: michael@0: // We need to know how many microseconds give a positive PRIntervalTime. This michael@0: // is platform-dependent, we calculate it at runtime now. michael@0: // First we find a value such that PR_MicrosecondsToInterval(high) = 1 michael@0: int32_t low = 0, high = 1; michael@0: while (PR_MicrosecondsToInterval(high) == 0) michael@0: high <<= 1; michael@0: // We now have michael@0: // PR_MicrosecondsToInterval(low) = 0 michael@0: // PR_MicrosecondsToInterval(high) = 1 michael@0: // and we can proceed to find the critical value using binary search michael@0: while (high-low > 1) { michael@0: int32_t mid = (high+low) >> 1; michael@0: if (PR_MicrosecondsToInterval(mid) == 0) michael@0: low = mid; michael@0: else michael@0: high = mid; michael@0: } michael@0: michael@0: // Half of the amount of microseconds needed to get positive PRIntervalTime. michael@0: // We use this to decide how to round our wait times later michael@0: int32_t halfMicrosecondsIntervalResolution = high >> 1; michael@0: bool forceRunNextTimer = false; michael@0: michael@0: while (!mShutdown) { michael@0: // Have to use PRIntervalTime here, since PR_WaitCondVar takes it michael@0: PRIntervalTime waitFor; michael@0: bool forceRunThisTimer = forceRunNextTimer; michael@0: forceRunNextTimer = false; michael@0: michael@0: if (mSleeping) { michael@0: // Sleep for 0.1 seconds while not firing timers. michael@0: uint32_t milliseconds = 100; michael@0: if (ChaosMode::isActive()) { michael@0: milliseconds = ChaosMode::randomUint32LessThan(200); michael@0: } michael@0: waitFor = PR_MillisecondsToInterval(milliseconds); michael@0: } else { michael@0: waitFor = PR_INTERVAL_NO_TIMEOUT; michael@0: TimeStamp now = TimeStamp::Now(); michael@0: nsTimerImpl *timer = nullptr; michael@0: michael@0: if (!mTimers.IsEmpty()) { michael@0: timer = mTimers[0]; michael@0: michael@0: if (now >= timer->mTimeout || forceRunThisTimer) { michael@0: next: michael@0: // NB: AddRef before the Release under RemoveTimerInternal to avoid michael@0: // mRefCnt passing through zero, in case all other refs than the one michael@0: // from mTimers have gone away (the last non-mTimers[i]-ref's Release michael@0: // must be racing with us, blocked in gThread->RemoveTimer waiting michael@0: // for TimerThread::mMonitor, under nsTimerImpl::Release. michael@0: michael@0: nsRefPtr timerRef(timer); michael@0: RemoveTimerInternal(timer); michael@0: timer = nullptr; michael@0: michael@0: { michael@0: // We release mMonitor around the Fire call to avoid deadlock. michael@0: MonitorAutoUnlock unlock(mMonitor); michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, michael@0: ("Timer thread woke up %fms from when it was supposed to\n", michael@0: fabs((now - timerRef->mTimeout).ToMilliseconds()))); michael@0: } michael@0: #endif michael@0: michael@0: // We are going to let the call to PostTimerEvent here handle the michael@0: // release of the timer so that we don't end up releasing the timer michael@0: // on the TimerThread instead of on the thread it targets. michael@0: timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget()); michael@0: michael@0: if (timerRef) { michael@0: // We got our reference back due to an error. michael@0: // Unhook the nsRefPtr, and release manually so we can get the michael@0: // refcount. michael@0: nsrefcnt rc = timerRef.forget().take()->Release(); michael@0: (void)rc; michael@0: michael@0: // The nsITimer interface requires that its users keep a reference michael@0: // to the timers they use while those timers are initialized but michael@0: // have not yet fired. If this ever happens, it is a bug in the michael@0: // code that created and used the timer. michael@0: // michael@0: // Further, note that this should never happen even with a michael@0: // misbehaving user, because nsTimerImpl::Release checks for a michael@0: // refcount of 1 with an armed timer (a timer whose only reference michael@0: // is from the timer thread) and when it hits this will remove the michael@0: // timer from the timer thread and thus destroy the last reference, michael@0: // preventing this situation from occurring. michael@0: MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!"); michael@0: } michael@0: } michael@0: michael@0: if (mShutdown) michael@0: break; michael@0: michael@0: // Update now, as PostTimerEvent plus the locking may have taken a michael@0: // tick or two, and we may goto next below. michael@0: now = TimeStamp::Now(); michael@0: } michael@0: } michael@0: michael@0: if (!mTimers.IsEmpty()) { michael@0: timer = mTimers[0]; michael@0: michael@0: TimeStamp timeout = timer->mTimeout; michael@0: michael@0: // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer michael@0: // is due now or overdue. michael@0: // michael@0: // Note that we can only sleep for integer values of a certain michael@0: // resolution. We use halfMicrosecondsIntervalResolution, calculated michael@0: // before, to do the optimal rounding (i.e., of how to decide what michael@0: // interval is so small we should not wait at all). michael@0: double microseconds = (timeout - now).ToMilliseconds()*1000; michael@0: michael@0: if (ChaosMode::isActive()) { michael@0: // The mean value of sFractions must be 1 to ensure that michael@0: // the average of a long sequence of timeouts converges to the michael@0: // actual sum of their times. michael@0: static const float sFractions[] = { michael@0: 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.75f, 2.75f michael@0: }; michael@0: microseconds *= sFractions[ChaosMode::randomUint32LessThan(ArrayLength(sFractions))]; michael@0: forceRunNextTimer = true; michael@0: } michael@0: michael@0: if (microseconds < halfMicrosecondsIntervalResolution) { michael@0: forceRunNextTimer = false; michael@0: goto next; // round down; execute event now michael@0: } michael@0: waitFor = PR_MicrosecondsToInterval(static_cast(microseconds)); // Floor is accurate enough. michael@0: if (waitFor == 0) michael@0: waitFor = 1; // round up, wait the minimum time we can wait michael@0: } michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { michael@0: if (waitFor == PR_INTERVAL_NO_TIMEOUT) michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, michael@0: ("waiting for PR_INTERVAL_NO_TIMEOUT\n")); michael@0: else michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, michael@0: ("waiting for %u\n", PR_IntervalToMilliseconds(waitFor))); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: mWaiting = true; michael@0: mNotified = false; michael@0: mMonitor.Wait(waitFor); michael@0: if (mNotified) { michael@0: forceRunNextTimer = false; michael@0: } michael@0: mWaiting = false; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult TimerThread::AddTimer(nsTimerImpl *aTimer) michael@0: { michael@0: MonitorAutoLock lock(mMonitor); michael@0: michael@0: // Add the timer to our list. michael@0: int32_t i = AddTimerInternal(aTimer); michael@0: if (i < 0) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Awaken the timer thread. michael@0: if (mWaiting && i == 0) { michael@0: mNotified = true; michael@0: mMonitor.Notify(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult TimerThread::TimerDelayChanged(nsTimerImpl *aTimer) michael@0: { michael@0: MonitorAutoLock lock(mMonitor); michael@0: michael@0: // Our caller has a strong ref to aTimer, so it can't go away here under michael@0: // ReleaseTimerInternal. michael@0: RemoveTimerInternal(aTimer); michael@0: michael@0: int32_t i = AddTimerInternal(aTimer); michael@0: if (i < 0) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Awaken the timer thread. michael@0: if (mWaiting && i == 0) { michael@0: mNotified = true; michael@0: mMonitor.Notify(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult TimerThread::RemoveTimer(nsTimerImpl *aTimer) michael@0: { michael@0: MonitorAutoLock lock(mMonitor); michael@0: michael@0: // Remove the timer from our array. Tell callers that aTimer was not found michael@0: // by returning NS_ERROR_NOT_AVAILABLE. Unlike the TimerDelayChanged case michael@0: // immediately above, our caller may be passing a (now-)weak ref in via the michael@0: // aTimer param, specifically when nsTimerImpl::Release loses a race with michael@0: // TimerThread::Run, must wait for the mMonitor auto-lock here, and during the michael@0: // wait Run drops the only remaining ref to aTimer via RemoveTimerInternal. michael@0: michael@0: if (!RemoveTimerInternal(aTimer)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // Awaken the timer thread. michael@0: if (mWaiting) { michael@0: mNotified = true; michael@0: mMonitor.Notify(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This function must be called from within a lock michael@0: int32_t TimerThread::AddTimerInternal(nsTimerImpl *aTimer) michael@0: { michael@0: if (mShutdown) michael@0: return -1; michael@0: michael@0: TimeStamp now = TimeStamp::Now(); michael@0: michael@0: TimerAdditionComparator c(now, aTimer); michael@0: nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c); michael@0: michael@0: if (!insertSlot) michael@0: return -1; michael@0: michael@0: aTimer->mArmed = true; michael@0: NS_ADDREF(aTimer); michael@0: michael@0: #ifdef MOZ_TASK_TRACER michael@0: aTimer->DispatchTracedTask(); michael@0: #endif michael@0: michael@0: return insertSlot - mTimers.Elements(); michael@0: } michael@0: michael@0: bool TimerThread::RemoveTimerInternal(nsTimerImpl *aTimer) michael@0: { michael@0: if (!mTimers.RemoveElement(aTimer)) michael@0: return false; michael@0: michael@0: ReleaseTimerInternal(aTimer); michael@0: return true; michael@0: } michael@0: michael@0: void TimerThread::ReleaseTimerInternal(nsTimerImpl *aTimer) michael@0: { michael@0: // Order is crucial here -- see nsTimerImpl::Release. michael@0: aTimer->mArmed = false; michael@0: NS_RELEASE(aTimer); michael@0: } michael@0: michael@0: void TimerThread::DoBeforeSleep() michael@0: { michael@0: mSleeping = true; michael@0: } michael@0: michael@0: void TimerThread::DoAfterSleep() michael@0: { michael@0: mSleeping = true; // wake may be notified without preceding sleep notification michael@0: for (uint32_t i = 0; i < mTimers.Length(); i ++) { michael@0: nsTimerImpl *timer = mTimers[i]; michael@0: // get and set the delay to cause its timeout to be recomputed michael@0: uint32_t delay; michael@0: timer->GetDelay(&delay); michael@0: timer->SetDelay(delay); michael@0: } michael@0: michael@0: mSleeping = false; michael@0: } michael@0: michael@0: michael@0: /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */ michael@0: NS_IMETHODIMP michael@0: TimerThread::Observe(nsISupports* /* aSubject */, const char *aTopic, const char16_t* /* aData */) michael@0: { michael@0: if (strcmp(aTopic, "sleep_notification") == 0 || michael@0: strcmp(aTopic, "suspend_process_notification") == 0) michael@0: DoBeforeSleep(); michael@0: else if (strcmp(aTopic, "wake_notification") == 0 || michael@0: strcmp(aTopic, "resume_process_notification") == 0) michael@0: DoAfterSleep(); michael@0: michael@0: return NS_OK; michael@0: }