gecko-dev/xpcom/threads/IdleTaskRunner.cpp
Nathan Froyd d81ba0426a Bug 1499725 - make IdleTaskRunner timers more efficient; r=farre
Instead of creating a timer and then setting the timer's target, we can
determine the timer's target and pass it in directly when the timer is
created.  This reordering of steps is slightly more efficient, since
SetTarget() is both a virtual call and requires locking, both of which
can be skipped if we know the target at timer creation time.  If we're
reusing the timer, we also don't need to repeatedly set the timer's
target: we can set the target once at timer creation, and then be done.

We can do this safely here because mTaskCategory doesn't change
throughout the life of the IdleTaskRunner; we make mTaskCategory `const`
to make this more explicit to the reader.
2018-10-22 09:44:50 -04:00

213 lines
5.8 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "IdleTaskRunner.h"
#include "nsRefreshDriver.h"
#include "mozilla/SystemGroup.h"
#include "nsComponentManagerUtils.h"
namespace mozilla {
already_AddRefed<IdleTaskRunner>
IdleTaskRunner::Create(const CallbackType& aCallback,
const char* aRunnableName, uint32_t aDelay,
int64_t aBudget, bool aRepeating,
const MayStopProcessingCallbackType& aMayStopProcessing,
TaskCategory aTaskCategory)
{
if (aMayStopProcessing && aMayStopProcessing()) {
return nullptr;
}
RefPtr<IdleTaskRunner> runner =
new IdleTaskRunner(aCallback, aRunnableName, aDelay,
aBudget, aRepeating, aMayStopProcessing,
aTaskCategory);
runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
return runner.forget();
}
IdleTaskRunner::IdleTaskRunner(const CallbackType& aCallback,
const char* aRunnableName,
uint32_t aDelay, int64_t aBudget,
bool aRepeating,
const MayStopProcessingCallbackType& aMayStopProcessing,
TaskCategory aTaskCategory)
: IdleRunnable(aRunnableName)
, mCallback(aCallback), mDelay(aDelay)
, mBudget(TimeDuration::FromMilliseconds(aBudget))
, mRepeating(aRepeating), mTimerActive(false)
, mMayStopProcessing(aMayStopProcessing)
, mTaskCategory(aTaskCategory)
, mName(aRunnableName)
{
}
NS_IMETHODIMP
IdleTaskRunner::Run()
{
if (!mCallback) {
return NS_OK;
}
// Deadline is null when called from timer.
TimeStamp now = TimeStamp::Now();
bool deadLineWasNull = mDeadline.IsNull();
bool didRun = false;
bool allowIdleDispatch = false;
if (deadLineWasNull || ((now + mBudget) < mDeadline)) {
CancelTimer();
didRun = mCallback(mDeadline);
// If we didn't do meaningful work, don't schedule using immediate
// idle dispatch, since that could lead to a loop until the idle
// period ends.
allowIdleDispatch = didRun;
} else if (now >= mDeadline) {
allowIdleDispatch = true;
}
if (mCallback && (mRepeating || !didRun)) {
Schedule(allowIdleDispatch);
} else {
mCallback = nullptr;
}
return NS_OK;
}
static void
TimedOut(nsITimer* aTimer, void* aClosure)
{
RefPtr<IdleTaskRunner> runnable = static_cast<IdleTaskRunner*>(aClosure);
runnable->Run();
}
void
IdleTaskRunner::SetDeadline(mozilla::TimeStamp aDeadline)
{
mDeadline = aDeadline;
};
void IdleTaskRunner::SetTimer(uint32_t aDelay, nsIEventTarget* aTarget)
{
MOZ_ASSERT(NS_IsMainThread());
// aTarget is always the main thread event target provided from
// NS_IdleDispatchToCurrentThread(). We ignore aTarget here to ensure that
// CollectorRunner always run specifically on SystemGroup::EventTargetFor(
// TaskCategory::GarbageCollection) of the main thread.
SetTimerInternal(aDelay);
}
nsresult
IdleTaskRunner::Cancel()
{
CancelTimer();
mTimer = nullptr;
mScheduleTimer = nullptr;
mCallback = nullptr;
return NS_OK;
}
static void
ScheduleTimedOut(nsITimer* aTimer, void* aClosure)
{
RefPtr<IdleTaskRunner> runnable = static_cast<IdleTaskRunner*>(aClosure);
runnable->Schedule(true);
}
void
IdleTaskRunner::Schedule(bool aAllowIdleDispatch)
{
if (!mCallback) {
return;
}
if (mMayStopProcessing && mMayStopProcessing()) {
Cancel();
return;
}
mDeadline = TimeStamp();
TimeStamp now = TimeStamp::Now();
TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now);
if (hint != now) {
// RefreshDriver is ticking, let it schedule the idle dispatch.
nsRefreshDriver::DispatchIdleRunnableAfterTick(this, mDelay);
// Ensure we get called at some point, even if RefreshDriver is stopped.
SetTimerInternal(mDelay);
} else {
// RefreshDriver doesn't seem to be running.
if (aAllowIdleDispatch) {
nsCOMPtr<nsIRunnable> runnable = this;
SetTimerInternal(mDelay);
NS_IdleDispatchToCurrentThread(runnable.forget());
} else {
if (!mScheduleTimer) {
nsIEventTarget* target = nullptr;
if (TaskCategory::Count != mTaskCategory) {
target = SystemGroup::EventTargetFor(mTaskCategory);
}
mScheduleTimer = NS_NewTimer(target);
if (!mScheduleTimer) {
return;
}
} else {
mScheduleTimer->Cancel();
}
// We weren't allowed to do idle dispatch immediately, do it after a
// short timeout.
mScheduleTimer->InitWithNamedFuncCallback(ScheduleTimedOut, this, 16,
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
mName);
}
}
}
IdleTaskRunner::~IdleTaskRunner()
{
CancelTimer();
}
void
IdleTaskRunner::CancelTimer()
{
nsRefreshDriver::CancelIdleRunnable(this);
if (mTimer) {
mTimer->Cancel();
}
if (mScheduleTimer) {
mScheduleTimer->Cancel();
}
mTimerActive = false;
}
void
IdleTaskRunner::SetTimerInternal(uint32_t aDelay)
{
if (mTimerActive) {
return;
}
if (!mTimer) {
nsIEventTarget* target = nullptr;
if (TaskCategory::Count != mTaskCategory) {
target = SystemGroup::EventTargetFor(mTaskCategory);
}
mTimer = NS_NewTimer(target);
} else {
mTimer->Cancel();
}
if (mTimer) {
mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay,
nsITimer::TYPE_ONE_SHOT,
mName);
mTimerActive = true;
}
}
} // end of namespace mozilla