diff options
Diffstat (limited to 'xpcom/threads/IdleTaskRunner.cpp')
-rw-r--r-- | xpcom/threads/IdleTaskRunner.cpp | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/xpcom/threads/IdleTaskRunner.cpp b/xpcom/threads/IdleTaskRunner.cpp new file mode 100644 index 0000000000..404410bec8 --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.cpp @@ -0,0 +1,181 @@ +/* -*- 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 "nsComponentManagerUtils.h" + +namespace mozilla { + +already_AddRefed<IdleTaskRunner> IdleTaskRunner::Create( + const CallbackType& aCallback, const char* aRunnableName, + uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing) { + if (aMayStopProcessing && aMayStopProcessing()) { + return nullptr; + } + + RefPtr<IdleTaskRunner> runner = + new IdleTaskRunner(aCallback, aRunnableName, aMaxDelay, aNonIdleBudget, + aRepeating, aMayStopProcessing); + runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch. + return runner.forget(); +} + +IdleTaskRunner::IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing) + : CancelableIdleRunnable(aRunnableName), + mCallback(aCallback), + mDelay(aMaxDelay), + mBudget(TimeDuration::FromMilliseconds(aNonIdleBudget)), + mRepeating(aRepeating), + mTimerActive(false), + mMayStopProcessing(aMayStopProcessing), + 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::SetBudget(int64_t aBudget) { + mBudget = TimeDuration::FromMilliseconds(aBudget); +} + +void IdleTaskRunner::SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTarget->IsOnCurrentThread()); + // aTarget is always the main thread event target provided from + // NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that + // CollectorRunner always run specifically 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::DispatchIdleRunnableAfterTickUnlessExists(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_DispatchToCurrentThreadQueue(runnable.forget(), + EventQueuePriority::Idle); + } else { + if (!mScheduleTimer) { + mScheduleTimer = NS_NewTimer(); + 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 /* ms */, + 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) { + mTimer = NS_NewTimer(); + } else { + mTimer->Cancel(); + } + + if (mTimer) { + mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, mName); + mTimerActive = true; + } +} + +} // end of namespace mozilla |