diff options
Diffstat (limited to 'xpcom/threads/IdleTaskRunner.cpp')
-rw-r--r-- | xpcom/threads/IdleTaskRunner.cpp | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/xpcom/threads/IdleTaskRunner.cpp b/xpcom/threads/IdleTaskRunner.cpp new file mode 100644 index 0000000000..e9bb895dee --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.cpp @@ -0,0 +1,281 @@ +/* -*- 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 "mozilla/TaskController.h" +#include "nsRefreshDriver.h" + +namespace mozilla { + +already_AddRefed<IdleTaskRunner> IdleTaskRunner::Create( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt) { + if (aMayStopProcessing && aMayStopProcessing()) { + return nullptr; + } + + RefPtr<IdleTaskRunner> runner = new IdleTaskRunner( + aCallback, aRunnableName, aStartDelay, aMaxDelay, aMinimumUsefulBudget, + aRepeating, aMayStopProcessing, aRequestInterrupt); + runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch. + return runner.forget(); +} + +class IdleTaskRunnerTask : public Task { + public: + explicit IdleTaskRunnerTask(IdleTaskRunner* aRunner) + : Task(Kind::MainThreadOnly, EventQueuePriority::Idle), + mRunner(aRunner), + mRequestInterrupt(aRunner->mRequestInterrupt) { + SetManager(TaskController::Get()->GetIdleTaskManager()); + } + + TaskResult Run() override { + if (mRunner) { + // IdleTaskRunner::Run can actually trigger the destruction of the + // IdleTaskRunner. Make sure it doesn't get destroyed before the method + // finished. + RefPtr<IdleTaskRunner> runner(mRunner); + runner->Run(); + } + return TaskResult::Complete; + } + + void SetIdleDeadline(TimeStamp aDeadline) override { + if (mRunner) { + mRunner->SetIdleDeadline(aDeadline); + } + } + + void Cancel() { mRunner = nullptr; } + + bool GetName(nsACString& aName) override { + if (mRunner) { + aName.Assign(mRunner->GetName()); + } else { + aName.Assign("ExpiredIdleTaskRunner"); + } + return true; + } + + void RequestInterrupt(uint32_t aInterruptPriority) override { + if (mRequestInterrupt) { + mRequestInterrupt(aInterruptPriority); + } + } + + private: + IdleTaskRunner* mRunner; + + // Copied here and invoked even if there is no mRunner currently, to avoid + // race conditions checking mRunner when an interrupt is requested. + IdleTaskRunner::RequestInterruptCallbackType mRequestInterrupt; +}; + +IdleTaskRunner::IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt) + : mCallback(aCallback), + mStartTime(TimeStamp::Now() + aStartDelay), + mMaxDelay(aMaxDelay), + mMinimumUsefulBudget(aMinimumUsefulBudget), + mRepeating(aRepeating), + mTimerActive(false), + mMayStopProcessing(aMayStopProcessing), + mRequestInterrupt(aRequestInterrupt), + mName(aRunnableName) {} + +void IdleTaskRunner::Run() { + if (!mCallback) { + return; + } + + // Deadline is null when called from timer or RunNextCollectorTimer rather + // than during idle time. + TimeStamp now = TimeStamp::Now(); + + // Note that if called from RunNextCollectorTimer, we may not have reached + // mStartTime yet. Pretend we are overdue for idle time. + bool overdueForIdle = mDeadline.IsNull(); + bool didRun = false; + bool allowIdleDispatch = false; + + if (mTask) { + // If we find ourselves here we should usually be running from this task, + // but there are exceptions. In any case we're doing the work now and don't + // need our task going forward unless we're re-scheduled. + nsRefreshDriver::CancelIdleTask(mTask); + // Extra safety, make sure a task can never have a dangling ptr. + mTask->Cancel(); + mTask = nullptr; + } + + if (overdueForIdle || ((now + mMinimumUsefulBudget) < 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; + } +} + +static void TimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleTaskRunner> runner = static_cast<IdleTaskRunner*>(aClosure); + runner->Run(); +} + +void IdleTaskRunner::SetIdleDeadline(mozilla::TimeStamp aDeadline) { + mDeadline = aDeadline; +} + +void IdleTaskRunner::SetMinimumUsefulBudget(int64_t aMinimumUsefulBudget) { + mMinimumUsefulBudget = TimeDuration::FromMilliseconds(aMinimumUsefulBudget); +} + +void IdleTaskRunner::SetTimer(TimeDuration 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); +} + +void IdleTaskRunner::Cancel() { + CancelTimer(); + mTimer = nullptr; + mScheduleTimer = nullptr; + mCallback = nullptr; +} + +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(); + + if (aAllowIdleDispatch) { + SetTimerInternal(mMaxDelay); + if (!mTask) { + mTask = new IdleTaskRunnerTask(this); + RefPtr<Task> task(mTask); + TaskController::Get()->AddTask(task.forget()); + } + return; + } + + bool useRefreshDriver = false; + if (now >= mStartTime) { + // Detect whether the refresh driver is ticking by checking if + // GetIdleDeadlineHint returns its input parameter. + useRefreshDriver = + (nsRefreshDriver::GetIdleDeadlineHint( + now, nsRefreshDriver::IdleCheck::OnlyThisProcessRefreshDriver) != + now); + } + + if (useRefreshDriver) { + if (!mTask) { + // If a task was already scheduled, no point rescheduling. + mTask = new IdleTaskRunnerTask(this); + // RefreshDriver is ticking, let it schedule the idle dispatch. + nsRefreshDriver::DispatchIdleTaskAfterTickUnlessExists(mTask); + } + // Ensure we get called at some point, even if RefreshDriver is stopped. + SetTimerInternal(mMaxDelay); + } else { + // RefreshDriver doesn't seem to be running. + 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. (Or wait for our start time if we haven't started yet.) + uint32_t waitToSchedule = 16; /* ms */ + if (now < mStartTime) { + // + 1 to round milliseconds up to be sure to wait until after + // mStartTime. + waitToSchedule = (mStartTime - now).ToMilliseconds() + 1; + } + mScheduleTimer->InitWithNamedFuncCallback( + ScheduleTimedOut, this, waitToSchedule, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, mName); + } +} + +IdleTaskRunner::~IdleTaskRunner() { CancelTimer(); } + +void IdleTaskRunner::CancelTimer() { + if (mTask) { + nsRefreshDriver::CancelIdleTask(mTask); + mTask->Cancel(); + mTask = nullptr; + } + if (mTimer) { + mTimer->Cancel(); + } + if (mScheduleTimer) { + mScheduleTimer->Cancel(); + } + mTimerActive = false; +} + +void IdleTaskRunner::SetTimerInternal(TimeDuration aDelay) { + if (mTimerActive) { + return; + } + ResetTimer(aDelay); +} + +void IdleTaskRunner::ResetTimer(TimeDuration aDelay) { + mTimerActive = false; + + if (!mTimer) { + mTimer = NS_NewTimer(); + } else { + mTimer->Cancel(); + } + + if (mTimer) { + mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay.ToMilliseconds(), + nsITimer::TYPE_ONE_SHOT, mName); + mTimerActive = true; + } +} + +} // end of namespace mozilla |