pjs/xpcom/threads/nsTimerImpl.cpp

641 строка
18 KiB
C++
Исходник Обычный вид История

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2012-05-21 15:12:37 +04:00
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsTimerImpl.h"
#include "TimerThread.h"
#include "nsAutoPtr.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "prmem.h"
#include "sampler.h"
#include NEW_H
#include "nsFixedSizeAllocator.h"
using mozilla::TimeDuration;
using mozilla::TimeStamp;
static PRInt32 gGenerator = 0;
static TimerThread* gThread = nsnull;
#ifdef DEBUG_TIMERS
#include <math.h>
double nsTimerImpl::sDeltaSumSquared = 0;
double nsTimerImpl::sDeltaSum = 0;
double nsTimerImpl::sDeltaNum = 0;
static void
myNS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues,
double *meanResult, double *stdDevResult)
{
double mean = 0.0, var = 0.0, stdDev = 0.0;
if (n > 0.0 && sumOfValues >= 0) {
mean = sumOfValues / n;
double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues);
if (temp < 0.0 || n <= 1)
var = 0.0;
else
var = temp / (n * (n - 1));
// for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
stdDev = var != 0.0 ? sqrt(var) : 0.0;
}
*meanResult = mean;
*stdDevResult = stdDev;
}
#endif
namespace {
// TimerEventAllocator is a fixed size allocator class which is used in order
// to avoid the default allocator lock contention when firing timer events.
// It is a thread-safe wrapper around nsFixedSizeAllocator. The thread-safety
// is required because nsTimerEvent objects are allocated on the timer thread,
// and freed on the main thread. Since this is a TimerEventAllocator specific
// lock, the lock contention issue is only limited to the allocation and
// deallocation of nsTimerEvent objects.
class TimerEventAllocator : public nsFixedSizeAllocator {
public:
TimerEventAllocator() :
mMonitor("TimerEventAllocator")
{
}
void* Alloc(size_t aSize)
{
mozilla::MonitorAutoLock lock(mMonitor);
return nsFixedSizeAllocator::Alloc(aSize);
}
void Free(void* aPtr, size_t aSize)
{
mozilla::MonitorAutoLock lock(mMonitor);
nsFixedSizeAllocator::Free(aPtr, aSize);
}
private:
mozilla::Monitor mMonitor;
};
}
class nsTimerEvent : public nsRunnable {
public:
NS_IMETHOD Run();
nsTimerEvent(nsTimerImpl *timer, PRInt32 generation)
: mTimer(timer), mGeneration(generation) {
// timer is already addref'd for us
MOZ_COUNT_CTOR(nsTimerEvent);
}
#ifdef DEBUG_TIMERS
TimeStamp mInitTime;
#endif
static void Init();
static void Shutdown();
static void* operator new(size_t size) CPP_THROW_NEW {
return sAllocator->Alloc(size);
}
void operator delete(void* p) {
sAllocator->Free(p, sizeof(nsTimerEvent));
}
private:
~nsTimerEvent() {
#ifdef DEBUG
if (mTimer)
NS_WARNING("leaking reference to nsTimerImpl");
#endif
MOZ_COUNT_DTOR(nsTimerEvent);
}
nsTimerImpl *mTimer;
PRInt32 mGeneration;
static TimerEventAllocator* sAllocator;
};
TimerEventAllocator* nsTimerEvent::sAllocator = nsnull;
NS_IMPL_THREADSAFE_QUERY_INTERFACE1(nsTimerImpl, nsITimer)
NS_IMPL_THREADSAFE_ADDREF(nsTimerImpl)
NS_IMETHODIMP_(nsrefcnt) nsTimerImpl::Release(void)
{
nsrefcnt count;
NS_PRECONDITION(0 != mRefCnt, "dup release");
count = NS_AtomicDecrementRefcnt(mRefCnt);
NS_LOG_RELEASE(this, count, "nsTimerImpl");
if (count == 0) {
mRefCnt = 1; /* stabilize */
/* enable this to find non-threadsafe destructors: */
/* NS_ASSERT_OWNINGTHREAD(nsTimerImpl); */
delete this;
return 0;
}
// If only one reference remains, and mArmed is set, then the ref must be
// from the TimerThread::mTimers array, so we Cancel this timer to remove
// the mTimers element, and return 0 if Cancel in fact disarmed the timer.
//
// We use an inlined version of nsTimerImpl::Cancel here to check for the
// NS_ERROR_NOT_AVAILABLE code returned by gThread->RemoveTimer when this
// timer is not found in the mTimers array -- i.e., when the timer was not
// in fact armed once we acquired TimerThread::mLock, in spite of mArmed
// being true here. That can happen if the armed timer is being fired by
// TimerThread::Run as we race and test mArmed just before it is cleared by
// the timer thread. If the RemoveTimer call below doesn't find this timer
// in the mTimers array, then the last ref to this timer is held manually
// and temporarily by the TimerThread, so we should fall through to the
// final return and return 1, not 0.
//
// The original version of this thread-based timer code kept weak refs from
// TimerThread::mTimers, removing this timer's weak ref in the destructor,
// but that leads to double-destructions in the race described above, and
// adding mArmed doesn't help, because destructors can't be deferred, once
// begun. But by combining reference-counting and a specialized Release
// method with "is this timer still in the mTimers array once we acquire
// the TimerThread's lock" testing, we defer destruction until we're sure
// that only one thread has its hot little hands on this timer.
//
// Note that both approaches preclude a timer creator, and everyone else
// except the TimerThread who might have a strong ref, from dropping all
// their strong refs without implicitly canceling the timer. Timers need
// non-mTimers-element strong refs to stay alive.
if (count == 1 && mArmed) {
mCanceled = true;
NS_ASSERTION(gThread, "An armed timer exists after the thread timer stopped.");
if (NS_SUCCEEDED(gThread->RemoveTimer(this)))
return 0;
}
return count;
}
nsTimerImpl::nsTimerImpl() :
mClosure(nsnull),
mCallbackType(CALLBACK_TYPE_UNKNOWN),
mFiring(false),
mArmed(false),
mCanceled(false),
mGeneration(0),
mDelay(0)
{
// XXXbsmedberg: shouldn't this be in Init()?
mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
mCallback.c = nsnull;
}
nsTimerImpl::~nsTimerImpl()
{
ReleaseCallback();
}
//static
nsresult
nsTimerImpl::Startup()
{
nsresult rv;
nsTimerEvent::Init();
gThread = new TimerThread();
if (!gThread) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(gThread);
rv = gThread->InitLocks();
if (NS_FAILED(rv)) {
NS_RELEASE(gThread);
}
return rv;
}
void nsTimerImpl::Shutdown()
{
#ifdef DEBUG_TIMERS
if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
double mean = 0, stddev = 0;
myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev);
PR_LOG(gTimerLog, PR_LOG_DEBUG, ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", sDeltaNum, sDeltaSum, sDeltaSumSquared));
PR_LOG(gTimerLog, PR_LOG_DEBUG, ("mean: %fms, stddev: %fms\n", mean, stddev));
}
#endif
if (!gThread)
return;
gThread->Shutdown();
NS_RELEASE(gThread);
nsTimerEvent::Shutdown();
}
nsresult nsTimerImpl::InitCommon(PRUint32 aType, PRUint32 aDelay)
{
nsresult rv;
NS_ENSURE_TRUE(gThread, NS_ERROR_NOT_INITIALIZED);
rv = gThread->Init();
NS_ENSURE_SUCCESS(rv, rv);
/**
* In case of re-Init, both with and without a preceding Cancel, clear the
* mCanceled flag and assign a new mGeneration. But first, remove any armed
* timer from the timer thread's list.
*
* If we are racing with the timer thread to remove this timer and we lose,
* the RemoveTimer call made here will fail to find this timer in the timer
* thread's list, and will return false harmlessly. We test mArmed here to
* avoid the small overhead in RemoveTimer of locking the timer thread and
* checking its list for this timer. It's safe to test mArmed even though
* it might be cleared on another thread in the next cycle (or even already
* be cleared by another CPU whose store hasn't reached our CPU's cache),
* because RemoveTimer is idempotent.
*/
if (mArmed)
gThread->RemoveTimer(this);
mCanceled = false;
mTimeout = TimeStamp();
mGeneration = PR_ATOMIC_INCREMENT(&gGenerator);
mType = (PRUint8)aType;
SetDelayInternal(aDelay);
return gThread->AddTimer(this);
}
NS_IMETHODIMP nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc,
void *aClosure,
PRUint32 aDelay,
PRUint32 aType)
{
NS_ENSURE_ARG_POINTER(aFunc);
ReleaseCallback();
mCallbackType = CALLBACK_TYPE_FUNC;
mCallback.c = aFunc;
mClosure = aClosure;
return InitCommon(aType, aDelay);
}
NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback *aCallback,
PRUint32 aDelay,
PRUint32 aType)
{
NS_ENSURE_ARG_POINTER(aCallback);
ReleaseCallback();
mCallbackType = CALLBACK_TYPE_INTERFACE;
mCallback.i = aCallback;
NS_ADDREF(mCallback.i);
return InitCommon(aType, aDelay);
}
NS_IMETHODIMP nsTimerImpl::Init(nsIObserver *aObserver,
PRUint32 aDelay,
PRUint32 aType)
{
NS_ENSURE_ARG_POINTER(aObserver);
ReleaseCallback();
mCallbackType = CALLBACK_TYPE_OBSERVER;
mCallback.o = aObserver;
NS_ADDREF(mCallback.o);
return InitCommon(aType, aDelay);
}
NS_IMETHODIMP nsTimerImpl::Cancel()
{
mCanceled = true;
if (gThread)
gThread->RemoveTimer(this);
ReleaseCallback();
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::SetDelay(PRUint32 aDelay)
{
if (mCallbackType == CALLBACK_TYPE_UNKNOWN && mType == TYPE_ONE_SHOT) {
// This may happen if someone tries to re-use a one-shot timer
// by re-setting delay instead of reinitializing the timer.
NS_ERROR("nsITimer->SetDelay() called when the "
"one-shot timer is not set up.");
return NS_ERROR_NOT_INITIALIZED;
}
// If we're already repeating precisely, update mTimeout now so that the
// new delay takes effect in the future.
if (!mTimeout.IsNull() && mType == TYPE_REPEATING_PRECISE)
mTimeout = TimeStamp::Now();
SetDelayInternal(aDelay);
if (!mFiring && gThread)
gThread->TimerDelayChanged(this);
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::GetDelay(PRUint32* aDelay)
{
*aDelay = mDelay;
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::SetType(PRUint32 aType)
{
mType = (PRUint8)aType;
// XXX if this is called, we should change the actual type.. this could effect
// repeating timers. we need to ensure in Fire() that if mType has changed
// during the callback that we don't end up with the timer in the queue twice.
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::GetType(PRUint32* aType)
{
*aType = mType;
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::GetClosure(void** aClosure)
{
*aClosure = mClosure;
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::GetCallback(nsITimerCallback **aCallback)
{
if (mCallbackType == CALLBACK_TYPE_INTERFACE)
NS_IF_ADDREF(*aCallback = mCallback.i);
else if (mTimerCallbackWhileFiring)
NS_ADDREF(*aCallback = mTimerCallbackWhileFiring);
else
*aCallback = nsnull;
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::GetTarget(nsIEventTarget** aTarget)
{
NS_IF_ADDREF(*aTarget = mEventTarget);
return NS_OK;
}
NS_IMETHODIMP nsTimerImpl::SetTarget(nsIEventTarget* aTarget)
{
NS_ENSURE_TRUE(mCallbackType == CALLBACK_TYPE_UNKNOWN,
NS_ERROR_ALREADY_INITIALIZED);
if (aTarget)
mEventTarget = aTarget;
else
mEventTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
return NS_OK;
}
void nsTimerImpl::Fire()
{
if (mCanceled)
return;
SAMPLE_LABEL("Timer", "Fire");
TimeStamp now = TimeStamp::Now();
#ifdef DEBUG_TIMERS
if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
TimeDuration a = now - mStart; // actual delay in intervals
TimeDuration b = TimeDuration::FromMilliseconds(mDelay); // expected delay in intervals
TimeDuration delta = (a > b) ? a - b : b - a;
PRUint32 d = delta.ToMilliseconds(); // delta in ms
sDeltaSum += d;
sDeltaSumSquared += double(d) * double(d);
sDeltaNum++;
PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] expected delay time %4ums\n", this, mDelay));
PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] actual delay time %fms\n", this, a.ToMilliseconds()));
PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] (mType is %d) -------\n", this, mType));
PR_LOG(gTimerLog, PR_LOG_DEBUG, ("[this=%p] delta %4dms\n", this, (a > b) ? (PRInt32)d : -(PRInt32)d));
mStart = mStart2;
mStart2 = TimeStamp();
}
#endif
TimeStamp timeout = mTimeout;
Bug 650379. Add a new XPCOM timer type that is like TYPE_REPEATING_PRECISE but does not swamp the event queue if the callback takes longer than the timer interval to run. r=cjones, sr=brendan This implements proposal 3 from bug 650379 comment 13. The main difference between TYPE_REPEATING_PRECISE and TYPE_REPEATING_PRECISE_CAN_SKIP is to not AddTimer the REPEATING_PRECISE_CAN_SKIP timer until after the callback has run; this guarantees that no more timer events will be posted until after the callback finishes executing. A secondary change is to make REPEATING_PRECISE_CAN_SKIP timers advance their firing time to mDelay from when PostTimerEvent is called, not mDelay from the old mTimeout. While this arguably makes them less precise, the alternative is that if a timer is significantly delayed for some reason (e.g. because the user puts the computer to sleep for a while) it will then fire a whole bunch of times to "catch up" to where it's supposed to be, advancing its firing time by mDelay at a time. That seems undesirable. An alternate approach would have been to readd the timer from inside PostTimerEvent, but only if we're not in the middle of firing the timer. That would allow more precise timers in the case when the callback is not taking too long, but still handle gracefully the case when the callback is slow. Unfortunately this falls down if something _else_ is hogging the main thread event loop (e.g. some other timer has a slow callback, or whatever); in that case we would post multiple events for the one precise timer while the event-loop-hogging operation is running. So I don't think we should do that.
2011-04-29 03:33:52 +04:00
if (IsRepeatingPrecisely()) {
// Precise repeating timers advance mTimeout by mDelay without fail before
// calling Fire().
timeout -= TimeDuration::FromMilliseconds(mDelay);
}
if (gThread)
gThread->UpdateFilter(mDelay, timeout, now);
if (mCallbackType == CALLBACK_TYPE_INTERFACE)
mTimerCallbackWhileFiring = mCallback.i;
mFiring = true;
// Handle callbacks that re-init the timer, but avoid leaking.
// See bug 330128.
CallbackUnion callback = mCallback;
PRUintn callbackType = mCallbackType;
if (callbackType == CALLBACK_TYPE_INTERFACE)
NS_ADDREF(callback.i);
else if (callbackType == CALLBACK_TYPE_OBSERVER)
NS_ADDREF(callback.o);
ReleaseCallback();
switch (callbackType) {
case CALLBACK_TYPE_FUNC:
callback.c(this, mClosure);
break;
case CALLBACK_TYPE_INTERFACE:
callback.i->Notify(this);
break;
case CALLBACK_TYPE_OBSERVER:
callback.o->Observe(static_cast<nsITimer*>(this),
NS_TIMER_CALLBACK_TOPIC,
nsnull);
break;
default:;
}
// If the callback didn't re-init the timer, and it's not a one-shot timer,
// restore the callback state.
if (mCallbackType == CALLBACK_TYPE_UNKNOWN &&
mType != TYPE_ONE_SHOT && !mCanceled) {
mCallback = callback;
mCallbackType = callbackType;
} else {
// The timer was a one-shot, or the callback was reinitialized.
if (callbackType == CALLBACK_TYPE_INTERFACE)
NS_RELEASE(callback.i);
else if (callbackType == CALLBACK_TYPE_OBSERVER)
NS_RELEASE(callback.o);
}
mFiring = false;
mTimerCallbackWhileFiring = nsnull;
#ifdef DEBUG_TIMERS
if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
PR_LOG(gTimerLog, PR_LOG_DEBUG,
("[this=%p] Took %fms to fire timer callback\n",
this, (TimeStamp::Now() - now).ToMilliseconds()));
}
#endif
Bug 650379. Add a new XPCOM timer type that is like TYPE_REPEATING_PRECISE but does not swamp the event queue if the callback takes longer than the timer interval to run. r=cjones, sr=brendan This implements proposal 3 from bug 650379 comment 13. The main difference between TYPE_REPEATING_PRECISE and TYPE_REPEATING_PRECISE_CAN_SKIP is to not AddTimer the REPEATING_PRECISE_CAN_SKIP timer until after the callback has run; this guarantees that no more timer events will be posted until after the callback finishes executing. A secondary change is to make REPEATING_PRECISE_CAN_SKIP timers advance their firing time to mDelay from when PostTimerEvent is called, not mDelay from the old mTimeout. While this arguably makes them less precise, the alternative is that if a timer is significantly delayed for some reason (e.g. because the user puts the computer to sleep for a while) it will then fire a whole bunch of times to "catch up" to where it's supposed to be, advancing its firing time by mDelay at a time. That seems undesirable. An alternate approach would have been to readd the timer from inside PostTimerEvent, but only if we're not in the middle of firing the timer. That would allow more precise timers in the case when the callback is not taking too long, but still handle gracefully the case when the callback is slow. Unfortunately this falls down if something _else_ is hogging the main thread event loop (e.g. some other timer has a slow callback, or whatever); in that case we would post multiple events for the one precise timer while the event-loop-hogging operation is running. So I don't think we should do that.
2011-04-29 03:33:52 +04:00
// Reschedule repeating timers, except REPEATING_PRECISE which already did
// that in PostTimerEvent, but make sure that we aren't armed already (which
// can happen if the callback reinitialized the timer).
if (IsRepeating() && mType != TYPE_REPEATING_PRECISE && !mArmed) {
if (mType == TYPE_REPEATING_SLACK)
SetDelayInternal(mDelay); // force mTimeout to be recomputed. For
// REPEATING_PRECISE_CAN_SKIP timers this has
// already happened.
if (gThread)
gThread->AddTimer(this);
}
}
void nsTimerEvent::Init()
{
sAllocator = new TimerEventAllocator();
static const size_t kBucketSizes[] = {sizeof(nsTimerEvent)};
static const PRInt32 kNumBuckets = mozilla::ArrayLength(kBucketSizes);
static const PRInt32 kInitialPoolSize = 1024 * sizeof(nsTimerEvent);
sAllocator->Init("TimerEventPool", kBucketSizes, kNumBuckets, kInitialPoolSize);
}
void nsTimerEvent::Shutdown()
{
delete sAllocator;
sAllocator = nsnull;
}
NS_IMETHODIMP nsTimerEvent::Run()
{
nsRefPtr<nsTimerImpl> timer;
timer.swap(mTimer);
if (mGeneration != timer->GetGeneration())
return NS_OK;
#ifdef DEBUG_TIMERS
if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
TimeStamp now = TimeStamp::Now();
PR_LOG(gTimerLog, PR_LOG_DEBUG,
("[this=%p] time between PostTimerEvent() and Fire(): %fms\n",
this, (now - mInitTime).ToMilliseconds()));
}
#endif
timer->Fire();
return NS_OK;
}
nsresult nsTimerImpl::PostTimerEvent()
{
// XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
// Since TimerThread addref'd 'this' for us, we don't need to addref here.
// We will release in destroyMyEvent. We need to copy the generation number
// from this timer into the event, so we can avoid firing a timer that was
// re-initialized after being canceled.
nsRefPtr<nsTimerEvent> event = new nsTimerEvent(this, mGeneration);
if (!event)
return NS_ERROR_OUT_OF_MEMORY;
#ifdef DEBUG_TIMERS
if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
event->mInitTime = TimeStamp::Now();
}
#endif
// If this is a repeating precise timer, we need to calculate the time for
// the next timer to fire before we make the callback.
Bug 650379. Add a new XPCOM timer type that is like TYPE_REPEATING_PRECISE but does not swamp the event queue if the callback takes longer than the timer interval to run. r=cjones, sr=brendan This implements proposal 3 from bug 650379 comment 13. The main difference between TYPE_REPEATING_PRECISE and TYPE_REPEATING_PRECISE_CAN_SKIP is to not AddTimer the REPEATING_PRECISE_CAN_SKIP timer until after the callback has run; this guarantees that no more timer events will be posted until after the callback finishes executing. A secondary change is to make REPEATING_PRECISE_CAN_SKIP timers advance their firing time to mDelay from when PostTimerEvent is called, not mDelay from the old mTimeout. While this arguably makes them less precise, the alternative is that if a timer is significantly delayed for some reason (e.g. because the user puts the computer to sleep for a while) it will then fire a whole bunch of times to "catch up" to where it's supposed to be, advancing its firing time by mDelay at a time. That seems undesirable. An alternate approach would have been to readd the timer from inside PostTimerEvent, but only if we're not in the middle of firing the timer. That would allow more precise timers in the case when the callback is not taking too long, but still handle gracefully the case when the callback is slow. Unfortunately this falls down if something _else_ is hogging the main thread event loop (e.g. some other timer has a slow callback, or whatever); in that case we would post multiple events for the one precise timer while the event-loop-hogging operation is running. So I don't think we should do that.
2011-04-29 03:33:52 +04:00
if (IsRepeatingPrecisely()) {
SetDelayInternal(mDelay);
Bug 650379. Add a new XPCOM timer type that is like TYPE_REPEATING_PRECISE but does not swamp the event queue if the callback takes longer than the timer interval to run. r=cjones, sr=brendan This implements proposal 3 from bug 650379 comment 13. The main difference between TYPE_REPEATING_PRECISE and TYPE_REPEATING_PRECISE_CAN_SKIP is to not AddTimer the REPEATING_PRECISE_CAN_SKIP timer until after the callback has run; this guarantees that no more timer events will be posted until after the callback finishes executing. A secondary change is to make REPEATING_PRECISE_CAN_SKIP timers advance their firing time to mDelay from when PostTimerEvent is called, not mDelay from the old mTimeout. While this arguably makes them less precise, the alternative is that if a timer is significantly delayed for some reason (e.g. because the user puts the computer to sleep for a while) it will then fire a whole bunch of times to "catch up" to where it's supposed to be, advancing its firing time by mDelay at a time. That seems undesirable. An alternate approach would have been to readd the timer from inside PostTimerEvent, but only if we're not in the middle of firing the timer. That would allow more precise timers in the case when the callback is not taking too long, but still handle gracefully the case when the callback is slow. Unfortunately this falls down if something _else_ is hogging the main thread event loop (e.g. some other timer has a slow callback, or whatever); in that case we would post multiple events for the one precise timer while the event-loop-hogging operation is running. So I don't think we should do that.
2011-04-29 03:33:52 +04:00
// But only re-arm REPEATING_PRECISE timers.
if (gThread && mType == TYPE_REPEATING_PRECISE) {
nsresult rv = gThread->AddTimer(this);
if (NS_FAILED(rv))
return rv;
}
}
nsresult rv = mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv) && gThread)
gThread->RemoveTimer(this);
return rv;
}
void nsTimerImpl::SetDelayInternal(PRUint32 aDelay)
{
TimeDuration delayInterval = TimeDuration::FromMilliseconds(aDelay);
mDelay = aDelay;
TimeStamp now = TimeStamp::Now();
if (mTimeout.IsNull() || mType != TYPE_REPEATING_PRECISE)
mTimeout = now;
mTimeout += delayInterval;
#ifdef DEBUG_TIMERS
if (PR_LOG_TEST(gTimerLog, PR_LOG_DEBUG)) {
if (mStart.IsNull())
mStart = now;
else
mStart2 = now;
}
#endif
}
// NOT FOR PUBLIC CONSUMPTION!
nsresult
NS_NewTimer(nsITimer* *aResult, nsTimerCallbackFunc aCallback, void *aClosure,
PRUint32 aDelay, PRUint32 aType)
{
nsTimerImpl* timer = new nsTimerImpl();
if (timer == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(timer);
nsresult rv = timer->InitWithFuncCallback(aCallback, aClosure,
aDelay, aType);
if (NS_FAILED(rv)) {
NS_RELEASE(timer);
return rv;
}
*aResult = timer;
return NS_OK;
}