michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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: #include "nsAutoPtr.h" michael@0: #include "nsThreadManager.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "plarena.h" michael@0: #include "pratom.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "mozilla/Atomics.h" michael@0: michael@0: using mozilla::Atomic; michael@0: using mozilla::TimeDuration; michael@0: using mozilla::TimeStamp; michael@0: michael@0: static Atomic gGenerator; michael@0: static TimerThread* gThread = nullptr; michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: michael@0: PRLogModuleInfo* michael@0: GetTimerLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("nsTimerImpl"); michael@0: return sLog; michael@0: } michael@0: michael@0: #include michael@0: michael@0: double nsTimerImpl::sDeltaSumSquared = 0; michael@0: double nsTimerImpl::sDeltaSum = 0; michael@0: double nsTimerImpl::sDeltaNum = 0; michael@0: michael@0: static void michael@0: myNS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues, michael@0: double *meanResult, double *stdDevResult) michael@0: { michael@0: double mean = 0.0, var = 0.0, stdDev = 0.0; michael@0: if (n > 0.0 && sumOfValues >= 0) { michael@0: mean = sumOfValues / n; michael@0: double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues); michael@0: if (temp < 0.0 || n <= 1) michael@0: var = 0.0; michael@0: else michael@0: var = temp / (n * (n - 1)); michael@0: // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: michael@0: stdDev = var != 0.0 ? sqrt(var) : 0.0; michael@0: } michael@0: *meanResult = mean; michael@0: *stdDevResult = stdDev; michael@0: } michael@0: #endif michael@0: michael@0: namespace { michael@0: michael@0: // TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. michael@0: // It's needed to avoid contention over the default allocator lock when michael@0: // firing timer events (see bug 733277). The thread-safety is required because michael@0: // nsTimerEvent objects are allocated on the timer thread, and freed on another michael@0: // thread. Because TimerEventAllocator has its own lock, contention over that michael@0: // lock is limited to the allocation and deallocation of nsTimerEvent objects. michael@0: // michael@0: // Because this allocator is layered over PLArenaPool, it never shrinks -- even michael@0: // "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list michael@0: // for later recycling. So the amount of memory consumed will always be equal michael@0: // to the high-water mark consumption. But nsTimerEvents are small and it's michael@0: // unusual to have more than a few hundred of them, so this shouldn't be a michael@0: // problem in practice. michael@0: michael@0: class TimerEventAllocator michael@0: { michael@0: private: michael@0: struct FreeEntry { michael@0: FreeEntry* mNext; michael@0: }; michael@0: michael@0: PLArenaPool mPool; michael@0: FreeEntry* mFirstFree; michael@0: mozilla::Monitor mMonitor; michael@0: michael@0: public: michael@0: TimerEventAllocator() michael@0: : mFirstFree(nullptr), michael@0: mMonitor("TimerEventAllocator") michael@0: { michael@0: PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0); michael@0: } michael@0: michael@0: ~TimerEventAllocator() michael@0: { michael@0: PL_FinishArenaPool(&mPool); michael@0: } michael@0: michael@0: void* Alloc(size_t aSize); michael@0: void Free(void* aPtr); michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: class nsTimerEvent : public nsRunnable { michael@0: public: michael@0: NS_IMETHOD Run(); michael@0: michael@0: nsTimerEvent() michael@0: : mTimer() michael@0: , mGeneration(0) michael@0: { michael@0: MOZ_COUNT_CTOR(nsTimerEvent); michael@0: michael@0: MOZ_ASSERT(gThread->IsOnTimerThread(), michael@0: "nsTimer must always be allocated on the timer thread"); michael@0: michael@0: sAllocatorUsers++; michael@0: } michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: TimeStamp mInitTime; michael@0: #endif michael@0: michael@0: static void Init(); michael@0: static void Shutdown(); michael@0: static void DeleteAllocatorIfNeeded(); michael@0: michael@0: static void* operator new(size_t size) CPP_THROW_NEW { michael@0: return sAllocator->Alloc(size); michael@0: } michael@0: void operator delete(void* p) { michael@0: sAllocator->Free(p); michael@0: DeleteAllocatorIfNeeded(); michael@0: } michael@0: michael@0: already_AddRefed ForgetTimer() michael@0: { michael@0: return mTimer.forget(); michael@0: } michael@0: michael@0: void SetTimer(already_AddRefed aTimer) michael@0: { michael@0: mTimer = aTimer; michael@0: mGeneration = mTimer->GetGeneration(); michael@0: } michael@0: michael@0: private: michael@0: ~nsTimerEvent() { michael@0: MOZ_COUNT_DTOR(nsTimerEvent); michael@0: michael@0: MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, michael@0: "This will result in us attempting to deallocate the nsTimerEvent allocator twice"); michael@0: sAllocatorUsers--; michael@0: } michael@0: michael@0: nsRefPtr mTimer; michael@0: int32_t mGeneration; michael@0: michael@0: static TimerEventAllocator* sAllocator; michael@0: static Atomic sAllocatorUsers; michael@0: static bool sCanDeleteAllocator; michael@0: }; michael@0: michael@0: TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; michael@0: Atomic nsTimerEvent::sAllocatorUsers; michael@0: bool nsTimerEvent::sCanDeleteAllocator = false; michael@0: michael@0: namespace { michael@0: michael@0: void* TimerEventAllocator::Alloc(size_t aSize) michael@0: { michael@0: MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); michael@0: michael@0: mozilla::MonitorAutoLock lock(mMonitor); michael@0: michael@0: void* p; michael@0: if (mFirstFree) { michael@0: p = mFirstFree; michael@0: mFirstFree = mFirstFree->mNext; michael@0: } michael@0: else { michael@0: PL_ARENA_ALLOCATE(p, &mPool, aSize); michael@0: if (!p) michael@0: return nullptr; michael@0: } michael@0: michael@0: return p; michael@0: } michael@0: michael@0: void TimerEventAllocator::Free(void* aPtr) michael@0: { michael@0: mozilla::MonitorAutoLock lock(mMonitor); michael@0: michael@0: FreeEntry* entry = reinterpret_cast(aPtr); michael@0: michael@0: entry->mNext = mFirstFree; michael@0: mFirstFree = entry; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(nsTimerImpl, nsITimer) michael@0: NS_IMPL_ADDREF(nsTimerImpl) michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) nsTimerImpl::Release(void) michael@0: { michael@0: nsrefcnt count; michael@0: michael@0: MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "nsTimerImpl"); michael@0: if (count == 0) { michael@0: mRefCnt = 1; /* stabilize */ michael@0: michael@0: /* enable this to find non-threadsafe destructors: */ michael@0: /* NS_ASSERT_OWNINGTHREAD(nsTimerImpl); */ michael@0: delete this; michael@0: return 0; michael@0: } michael@0: michael@0: // If only one reference remains, and mArmed is set, then the ref must be michael@0: // from the TimerThread::mTimers array, so we Cancel this timer to remove michael@0: // the mTimers element, and return 0 if Cancel in fact disarmed the timer. michael@0: // michael@0: // We use an inlined version of nsTimerImpl::Cancel here to check for the michael@0: // NS_ERROR_NOT_AVAILABLE code returned by gThread->RemoveTimer when this michael@0: // timer is not found in the mTimers array -- i.e., when the timer was not michael@0: // in fact armed once we acquired TimerThread::mLock, in spite of mArmed michael@0: // being true here. That can happen if the armed timer is being fired by michael@0: // TimerThread::Run as we race and test mArmed just before it is cleared by michael@0: // the timer thread. If the RemoveTimer call below doesn't find this timer michael@0: // in the mTimers array, then the last ref to this timer is held manually michael@0: // and temporarily by the TimerThread, so we should fall through to the michael@0: // final return and return 1, not 0. michael@0: // michael@0: // The original version of this thread-based timer code kept weak refs from michael@0: // TimerThread::mTimers, removing this timer's weak ref in the destructor, michael@0: // but that leads to double-destructions in the race described above, and michael@0: // adding mArmed doesn't help, because destructors can't be deferred, once michael@0: // begun. But by combining reference-counting and a specialized Release michael@0: // method with "is this timer still in the mTimers array once we acquire michael@0: // the TimerThread's lock" testing, we defer destruction until we're sure michael@0: // that only one thread has its hot little hands on this timer. michael@0: // michael@0: // Note that both approaches preclude a timer creator, and everyone else michael@0: // except the TimerThread who might have a strong ref, from dropping all michael@0: // their strong refs without implicitly canceling the timer. Timers need michael@0: // non-mTimers-element strong refs to stay alive. michael@0: michael@0: if (count == 1 && mArmed) { michael@0: mCanceled = true; michael@0: michael@0: MOZ_ASSERT(gThread, "Armed timer exists after the thread timer stopped."); michael@0: if (NS_SUCCEEDED(gThread->RemoveTimer(this))) michael@0: return 0; michael@0: } michael@0: michael@0: return count; michael@0: } michael@0: michael@0: nsTimerImpl::nsTimerImpl() : michael@0: mClosure(nullptr), michael@0: mCallbackType(CALLBACK_TYPE_UNKNOWN), michael@0: mFiring(false), michael@0: mArmed(false), michael@0: mCanceled(false), michael@0: mGeneration(0), michael@0: mDelay(0) michael@0: { michael@0: // XXXbsmedberg: shouldn't this be in Init()? michael@0: mEventTarget = static_cast(NS_GetCurrentThread()); michael@0: michael@0: mCallback.c = nullptr; michael@0: } michael@0: michael@0: nsTimerImpl::~nsTimerImpl() michael@0: { michael@0: ReleaseCallback(); michael@0: } michael@0: michael@0: //static michael@0: nsresult michael@0: nsTimerImpl::Startup() michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsTimerEvent::Init(); michael@0: michael@0: gThread = new TimerThread(); michael@0: if (!gThread) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(gThread); michael@0: rv = gThread->InitLocks(); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(gThread); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void nsTimerImpl::Shutdown() michael@0: { michael@0: #ifdef DEBUG_TIMERS michael@0: if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { michael@0: double mean = 0, stddev = 0; michael@0: myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev); michael@0: michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", sDeltaNum, sDeltaSum, sDeltaSumSquared)); michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("mean: %fms, stddev: %fms\n", mean, stddev)); michael@0: } michael@0: #endif michael@0: michael@0: if (!gThread) michael@0: return; michael@0: michael@0: gThread->Shutdown(); michael@0: NS_RELEASE(gThread); michael@0: michael@0: nsTimerEvent::Shutdown(); michael@0: } michael@0: michael@0: michael@0: nsresult nsTimerImpl::InitCommon(uint32_t aType, uint32_t aDelay) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (NS_WARN_IF(!gThread)) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: if (!mEventTarget) { michael@0: NS_ERROR("mEventTarget is NULL"); michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: rv = gThread->Init(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: /** michael@0: * In case of re-Init, both with and without a preceding Cancel, clear the michael@0: * mCanceled flag and assign a new mGeneration. But first, remove any armed michael@0: * timer from the timer thread's list. michael@0: * michael@0: * If we are racing with the timer thread to remove this timer and we lose, michael@0: * the RemoveTimer call made here will fail to find this timer in the timer michael@0: * thread's list, and will return false harmlessly. We test mArmed here to michael@0: * avoid the small overhead in RemoveTimer of locking the timer thread and michael@0: * checking its list for this timer. It's safe to test mArmed even though michael@0: * it might be cleared on another thread in the next cycle (or even already michael@0: * be cleared by another CPU whose store hasn't reached our CPU's cache), michael@0: * because RemoveTimer is idempotent. michael@0: */ michael@0: if (mArmed) michael@0: gThread->RemoveTimer(this); michael@0: mCanceled = false; michael@0: mTimeout = TimeStamp(); michael@0: mGeneration = gGenerator++; michael@0: michael@0: mType = (uint8_t)aType; michael@0: SetDelayInternal(aDelay); michael@0: michael@0: return gThread->AddTimer(this); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, michael@0: void *aClosure, michael@0: uint32_t aDelay, michael@0: uint32_t aType) michael@0: { michael@0: if (NS_WARN_IF(!aFunc)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: ReleaseCallback(); michael@0: mCallbackType = CALLBACK_TYPE_FUNC; michael@0: mCallback.c = aFunc; michael@0: mClosure = aClosure; michael@0: michael@0: return InitCommon(aType, aDelay); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback *aCallback, michael@0: uint32_t aDelay, michael@0: uint32_t aType) michael@0: { michael@0: if (NS_WARN_IF(!aCallback)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: ReleaseCallback(); michael@0: mCallbackType = CALLBACK_TYPE_INTERFACE; michael@0: mCallback.i = aCallback; michael@0: NS_ADDREF(mCallback.i); michael@0: michael@0: return InitCommon(aType, aDelay); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::Init(nsIObserver *aObserver, michael@0: uint32_t aDelay, michael@0: uint32_t aType) michael@0: { michael@0: if (NS_WARN_IF(!aObserver)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: ReleaseCallback(); michael@0: mCallbackType = CALLBACK_TYPE_OBSERVER; michael@0: mCallback.o = aObserver; michael@0: NS_ADDREF(mCallback.o); michael@0: michael@0: return InitCommon(aType, aDelay); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::Cancel() michael@0: { michael@0: mCanceled = true; michael@0: michael@0: if (gThread) michael@0: gThread->RemoveTimer(this); michael@0: michael@0: ReleaseCallback(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::SetDelay(uint32_t aDelay) michael@0: { michael@0: if (mCallbackType == CALLBACK_TYPE_UNKNOWN && mType == TYPE_ONE_SHOT) { michael@0: // This may happen if someone tries to re-use a one-shot timer michael@0: // by re-setting delay instead of reinitializing the timer. michael@0: NS_ERROR("nsITimer->SetDelay() called when the " michael@0: "one-shot timer is not set up."); michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: // If we're already repeating precisely, update mTimeout now so that the michael@0: // new delay takes effect in the future. michael@0: if (!mTimeout.IsNull() && mType == TYPE_REPEATING_PRECISE) michael@0: mTimeout = TimeStamp::Now(); michael@0: michael@0: SetDelayInternal(aDelay); michael@0: michael@0: if (!mFiring && gThread) michael@0: gThread->TimerDelayChanged(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::GetDelay(uint32_t* aDelay) michael@0: { michael@0: *aDelay = mDelay; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::SetType(uint32_t aType) michael@0: { michael@0: mType = (uint8_t)aType; michael@0: // XXX if this is called, we should change the actual type.. this could effect michael@0: // repeating timers. we need to ensure in Fire() that if mType has changed michael@0: // during the callback that we don't end up with the timer in the queue twice. michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::GetType(uint32_t* aType) michael@0: { michael@0: *aType = mType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::GetClosure(void** aClosure) michael@0: { michael@0: *aClosure = mClosure; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::GetCallback(nsITimerCallback **aCallback) michael@0: { michael@0: if (mCallbackType == CALLBACK_TYPE_INTERFACE) michael@0: NS_IF_ADDREF(*aCallback = mCallback.i); michael@0: else if (mTimerCallbackWhileFiring) michael@0: NS_ADDREF(*aCallback = mTimerCallbackWhileFiring); michael@0: else michael@0: *aCallback = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::GetTarget(nsIEventTarget** aTarget) michael@0: { michael@0: NS_IF_ADDREF(*aTarget = mEventTarget); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsTimerImpl::SetTarget(nsIEventTarget* aTarget) michael@0: { michael@0: if (NS_WARN_IF(mCallbackType != CALLBACK_TYPE_UNKNOWN)) michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: michael@0: if (aTarget) michael@0: mEventTarget = aTarget; michael@0: else michael@0: mEventTarget = static_cast(NS_GetCurrentThread()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void nsTimerImpl::Fire() michael@0: { michael@0: if (mCanceled) michael@0: return; michael@0: michael@0: PROFILER_LABEL("Timer", "Fire"); michael@0: michael@0: #ifdef MOZ_TASK_TRACER michael@0: mozilla::tasktracer::AutoRunFakeTracedTask runTracedTask(mTracedTask); michael@0: #endif michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: TimeStamp now = TimeStamp::Now(); michael@0: if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { michael@0: TimeDuration a = now - mStart; // actual delay in intervals michael@0: TimeDuration b = TimeDuration::FromMilliseconds(mDelay); // expected delay in intervals michael@0: TimeDuration delta = (a > b) ? a - b : b - a; michael@0: uint32_t d = delta.ToMilliseconds(); // delta in ms michael@0: sDeltaSum += d; michael@0: sDeltaSumSquared += double(d) * double(d); michael@0: sDeltaNum++; michael@0: michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] expected delay time %4ums\n", this, mDelay)); michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] actual delay time %fms\n", this, a.ToMilliseconds())); michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] (mType is %d) -------\n", this, mType)); michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, ("[this=%p] delta %4dms\n", this, (a > b) ? (int32_t)d : -(int32_t)d)); michael@0: michael@0: mStart = mStart2; michael@0: mStart2 = TimeStamp(); michael@0: } michael@0: #endif michael@0: michael@0: TimeStamp timeout = mTimeout; michael@0: if (IsRepeatingPrecisely()) { michael@0: // Precise repeating timers advance mTimeout by mDelay without fail before michael@0: // calling Fire(). michael@0: timeout -= TimeDuration::FromMilliseconds(mDelay); michael@0: } michael@0: michael@0: if (mCallbackType == CALLBACK_TYPE_INTERFACE) michael@0: mTimerCallbackWhileFiring = mCallback.i; michael@0: mFiring = true; michael@0: michael@0: // Handle callbacks that re-init the timer, but avoid leaking. michael@0: // See bug 330128. michael@0: CallbackUnion callback = mCallback; michael@0: unsigned callbackType = mCallbackType; michael@0: if (callbackType == CALLBACK_TYPE_INTERFACE) michael@0: NS_ADDREF(callback.i); michael@0: else if (callbackType == CALLBACK_TYPE_OBSERVER) michael@0: NS_ADDREF(callback.o); michael@0: ReleaseCallback(); michael@0: michael@0: switch (callbackType) { michael@0: case CALLBACK_TYPE_FUNC: michael@0: callback.c(this, mClosure); michael@0: break; michael@0: case CALLBACK_TYPE_INTERFACE: michael@0: callback.i->Notify(this); michael@0: break; michael@0: case CALLBACK_TYPE_OBSERVER: michael@0: callback.o->Observe(static_cast(this), michael@0: NS_TIMER_CALLBACK_TOPIC, michael@0: nullptr); michael@0: break; michael@0: default:; michael@0: } michael@0: michael@0: // If the callback didn't re-init the timer, and it's not a one-shot timer, michael@0: // restore the callback state. michael@0: if (mCallbackType == CALLBACK_TYPE_UNKNOWN && michael@0: mType != TYPE_ONE_SHOT && !mCanceled) { michael@0: mCallback = callback; michael@0: mCallbackType = callbackType; michael@0: } else { michael@0: // The timer was a one-shot, or the callback was reinitialized. michael@0: if (callbackType == CALLBACK_TYPE_INTERFACE) michael@0: NS_RELEASE(callback.i); michael@0: else if (callbackType == CALLBACK_TYPE_OBSERVER) michael@0: NS_RELEASE(callback.o); michael@0: } michael@0: michael@0: mFiring = false; michael@0: mTimerCallbackWhileFiring = nullptr; 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: ("[this=%p] Took %fms to fire timer callback\n", michael@0: this, (TimeStamp::Now() - now).ToMilliseconds())); michael@0: } michael@0: #endif michael@0: michael@0: // Reschedule repeating timers, except REPEATING_PRECISE which already did michael@0: // that in PostTimerEvent, but make sure that we aren't armed already (which michael@0: // can happen if the callback reinitialized the timer). michael@0: if (IsRepeating() && mType != TYPE_REPEATING_PRECISE && !mArmed) { michael@0: if (mType == TYPE_REPEATING_SLACK) michael@0: SetDelayInternal(mDelay); // force mTimeout to be recomputed. For michael@0: // REPEATING_PRECISE_CAN_SKIP timers this has michael@0: // already happened. michael@0: if (gThread) michael@0: gThread->AddTimer(this); michael@0: } michael@0: } michael@0: michael@0: void nsTimerEvent::Init() michael@0: { michael@0: sAllocator = new TimerEventAllocator(); michael@0: } michael@0: michael@0: void nsTimerEvent::Shutdown() michael@0: { michael@0: sCanDeleteAllocator = true; michael@0: DeleteAllocatorIfNeeded(); michael@0: } michael@0: michael@0: void nsTimerEvent::DeleteAllocatorIfNeeded() michael@0: { michael@0: if (sCanDeleteAllocator && sAllocatorUsers == 0) { michael@0: delete sAllocator; michael@0: sAllocator = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP nsTimerEvent::Run() michael@0: { michael@0: if (mGeneration != mTimer->GetGeneration()) michael@0: return NS_OK; michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { michael@0: TimeStamp now = TimeStamp::Now(); michael@0: PR_LOG(GetTimerLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", michael@0: this, (now - mInitTime).ToMilliseconds())); michael@0: } michael@0: #endif michael@0: michael@0: mTimer->Fire(); michael@0: // Since nsTimerImpl is not thread-safe, we should release |mTimer| michael@0: // here in the target thread to avoid race condition. Otherwise, michael@0: // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the michael@0: // timer thread and result in race condition. michael@0: mTimer = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsTimerImpl::PostTimerEvent(already_AddRefed aTimerRef) michael@0: { michael@0: nsRefPtr timer(aTimerRef); michael@0: if (!timer->mEventTarget) { michael@0: NS_ERROR("Attempt to post timer event to NULL event target"); michael@0: return timer.forget(); michael@0: } michael@0: michael@0: // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. michael@0: michael@0: // Since TimerThread addref'd 'timer' for us, we don't need to addref here. michael@0: // We will release either in ~nsTimerEvent(), or pass the reference back to michael@0: // the caller. We need to copy the generation number from this timer into the michael@0: // event, so we can avoid firing a timer that was re-initialized after being michael@0: // canceled. michael@0: michael@0: // Note: We override operator new for this class, and the override is michael@0: // fallible! michael@0: nsRefPtr event = new nsTimerEvent; michael@0: if (!event) michael@0: return timer.forget(); michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { michael@0: event->mInitTime = TimeStamp::Now(); michael@0: } michael@0: #endif michael@0: michael@0: // If this is a repeating precise timer, we need to calculate the time for michael@0: // the next timer to fire before we make the callback. michael@0: if (timer->IsRepeatingPrecisely()) { michael@0: timer->SetDelayInternal(timer->mDelay); michael@0: michael@0: // But only re-arm REPEATING_PRECISE timers. michael@0: if (gThread && timer->mType == TYPE_REPEATING_PRECISE) { michael@0: nsresult rv = gThread->AddTimer(timer); michael@0: if (NS_FAILED(rv)) { michael@0: return timer.forget(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIEventTarget* target = timer->mEventTarget; michael@0: event->SetTimer(timer.forget()); michael@0: michael@0: nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: timer = event->ForgetTimer(); michael@0: if (gThread) { michael@0: gThread->RemoveTimer(timer); michael@0: } michael@0: return timer.forget(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void nsTimerImpl::SetDelayInternal(uint32_t aDelay) michael@0: { michael@0: TimeDuration delayInterval = TimeDuration::FromMilliseconds(aDelay); michael@0: michael@0: mDelay = aDelay; michael@0: michael@0: TimeStamp now = TimeStamp::Now(); michael@0: if (mTimeout.IsNull() || mType != TYPE_REPEATING_PRECISE) michael@0: mTimeout = now; michael@0: michael@0: mTimeout += delayInterval; michael@0: michael@0: #ifdef DEBUG_TIMERS michael@0: if (PR_LOG_TEST(GetTimerLog(), PR_LOG_DEBUG)) { michael@0: if (mStart.IsNull()) michael@0: mStart = now; michael@0: else michael@0: mStart2 = now; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // NOT FOR PUBLIC CONSUMPTION! michael@0: nsresult michael@0: NS_NewTimer(nsITimer* *aResult, nsTimerCallbackFunc aCallback, void *aClosure, michael@0: uint32_t aDelay, uint32_t aType) michael@0: { michael@0: nsTimerImpl* timer = new nsTimerImpl(); michael@0: if (timer == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(timer); michael@0: michael@0: nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure, michael@0: aDelay, aType); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(timer); michael@0: return rv; michael@0: } michael@0: michael@0: *aResult = timer; michael@0: return NS_OK; michael@0: }