1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "IdleTaskRunner.h"
8 #include "mozilla/TaskController.h"
9 #include "nsRefreshDriver.h"
13 already_AddRefed
<IdleTaskRunner
> IdleTaskRunner::Create(
14 const CallbackType
& aCallback
, const char* aRunnableName
,
15 TimeDuration aStartDelay
, TimeDuration aMaxDelay
,
16 TimeDuration aMinimumUsefulBudget
, bool aRepeating
,
17 const MayStopProcessingCallbackType
& aMayStopProcessing
,
18 const RequestInterruptCallbackType
& aRequestInterrupt
) {
19 if (aMayStopProcessing
&& aMayStopProcessing()) {
23 RefPtr
<IdleTaskRunner
> runner
= new IdleTaskRunner(
24 aCallback
, aRunnableName
, aStartDelay
, aMaxDelay
, aMinimumUsefulBudget
,
25 aRepeating
, aMayStopProcessing
, aRequestInterrupt
);
26 runner
->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
27 return runner
.forget();
30 class IdleTaskRunnerTask
: public Task
{
32 explicit IdleTaskRunnerTask(IdleTaskRunner
* aRunner
)
33 : Task(Kind::MainThreadOnly
, EventQueuePriority::Idle
),
35 mRequestInterrupt(aRunner
->mRequestInterrupt
) {
36 SetManager(TaskController::Get()->GetIdleTaskManager());
39 TaskResult
Run() override
{
41 // IdleTaskRunner::Run can actually trigger the destruction of the
42 // IdleTaskRunner. Make sure it doesn't get destroyed before the method
44 RefPtr
<IdleTaskRunner
> runner(mRunner
);
47 return TaskResult::Complete
;
50 void SetIdleDeadline(TimeStamp aDeadline
) override
{
52 mRunner
->SetIdleDeadline(aDeadline
);
56 void Cancel() { mRunner
= nullptr; }
58 bool GetName(nsACString
& aName
) override
{
60 aName
.Assign(mRunner
->GetName());
62 aName
.Assign("ExpiredIdleTaskRunner");
67 void RequestInterrupt(uint32_t aInterruptPriority
) override
{
68 if (mRequestInterrupt
) {
69 mRequestInterrupt(aInterruptPriority
);
74 IdleTaskRunner
* mRunner
;
76 // Copied here and invoked even if there is no mRunner currently, to avoid
77 // race conditions checking mRunner when an interrupt is requested.
78 IdleTaskRunner::RequestInterruptCallbackType mRequestInterrupt
;
81 IdleTaskRunner::IdleTaskRunner(
82 const CallbackType
& aCallback
, const char* aRunnableName
,
83 TimeDuration aStartDelay
, TimeDuration aMaxDelay
,
84 TimeDuration aMinimumUsefulBudget
, bool aRepeating
,
85 const MayStopProcessingCallbackType
& aMayStopProcessing
,
86 const RequestInterruptCallbackType
& aRequestInterrupt
)
87 : mCallback(aCallback
),
88 mStartTime(TimeStamp::Now() + aStartDelay
),
90 mMinimumUsefulBudget(aMinimumUsefulBudget
),
91 mRepeating(aRepeating
),
93 mMayStopProcessing(aMayStopProcessing
),
94 mRequestInterrupt(aRequestInterrupt
),
95 mName(aRunnableName
) {}
97 void IdleTaskRunner::Run() {
102 // Deadline is null when called from timer or RunNextCollectorTimer rather
103 // than during idle time.
104 TimeStamp now
= TimeStamp::Now();
106 // Note that if called from RunNextCollectorTimer, we may not have reached
107 // mStartTime yet. Pretend we are overdue for idle time.
108 bool overdueForIdle
= mDeadline
.IsNull();
110 bool allowIdleDispatch
= false;
113 // If we find ourselves here we should usually be running from this task,
114 // but there are exceptions. In any case we're doing the work now and don't
115 // need our task going forward unless we're re-scheduled.
116 nsRefreshDriver::CancelIdleTask(mTask
);
117 // Extra safety, make sure a task can never have a dangling ptr.
122 if (overdueForIdle
|| ((now
+ mMinimumUsefulBudget
) < mDeadline
)) {
124 didRun
= mCallback(mDeadline
);
125 // If we didn't do meaningful work, don't schedule using immediate
126 // idle dispatch, since that could lead to a loop until the idle
128 allowIdleDispatch
= didRun
;
129 } else if (now
>= mDeadline
) {
130 allowIdleDispatch
= true;
133 if (mCallback
&& (mRepeating
|| !didRun
)) {
134 Schedule(allowIdleDispatch
);
140 static void TimedOut(nsITimer
* aTimer
, void* aClosure
) {
141 RefPtr
<IdleTaskRunner
> runner
= static_cast<IdleTaskRunner
*>(aClosure
);
145 void IdleTaskRunner::SetIdleDeadline(mozilla::TimeStamp aDeadline
) {
146 mDeadline
= aDeadline
;
149 void IdleTaskRunner::SetMinimumUsefulBudget(int64_t aMinimumUsefulBudget
) {
150 mMinimumUsefulBudget
= TimeDuration::FromMilliseconds(aMinimumUsefulBudget
);
153 void IdleTaskRunner::SetTimer(TimeDuration aDelay
, nsIEventTarget
* aTarget
) {
154 MOZ_ASSERT(NS_IsMainThread());
155 MOZ_ASSERT(aTarget
->IsOnCurrentThread());
156 // aTarget is always the main thread event target provided from
157 // NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that
158 // CollectorRunner always run specifically the main thread.
159 SetTimerInternal(aDelay
);
162 void IdleTaskRunner::Cancel() {
165 mScheduleTimer
= nullptr;
169 static void ScheduleTimedOut(nsITimer
* aTimer
, void* aClosure
) {
170 RefPtr
<IdleTaskRunner
> runnable
= static_cast<IdleTaskRunner
*>(aClosure
);
171 runnable
->Schedule(true);
174 void IdleTaskRunner::Schedule(bool aAllowIdleDispatch
) {
179 if (mMayStopProcessing
&& mMayStopProcessing()) {
184 mDeadline
= TimeStamp();
186 TimeStamp now
= TimeStamp::Now();
188 if (aAllowIdleDispatch
) {
189 SetTimerInternal(mMaxDelay
);
191 mTask
= new IdleTaskRunnerTask(this);
192 RefPtr
<Task
> task(mTask
);
193 TaskController::Get()->AddTask(task
.forget());
198 bool useRefreshDriver
= false;
199 if (now
>= mStartTime
) {
200 // Detect whether the refresh driver is ticking by checking if
201 // GetIdleDeadlineHint returns its input parameter.
203 (nsRefreshDriver::GetIdleDeadlineHint(
204 now
, nsRefreshDriver::IdleCheck::OnlyThisProcessRefreshDriver
) !=
208 if (useRefreshDriver
) {
210 // If a task was already scheduled, no point rescheduling.
211 mTask
= new IdleTaskRunnerTask(this);
212 // RefreshDriver is ticking, let it schedule the idle dispatch.
213 nsRefreshDriver::DispatchIdleTaskAfterTickUnlessExists(mTask
);
215 // Ensure we get called at some point, even if RefreshDriver is stopped.
216 SetTimerInternal(mMaxDelay
);
218 // RefreshDriver doesn't seem to be running.
219 if (!mScheduleTimer
) {
220 mScheduleTimer
= NS_NewTimer();
221 if (!mScheduleTimer
) {
225 mScheduleTimer
->Cancel();
227 // We weren't allowed to do idle dispatch immediately, do it after a
228 // short timeout. (Or wait for our start time if we haven't started yet.)
229 uint32_t waitToSchedule
= 16; /* ms */
230 if (now
< mStartTime
) {
231 // + 1 to round milliseconds up to be sure to wait until after
233 waitToSchedule
= (mStartTime
- now
).ToMilliseconds() + 1;
235 mScheduleTimer
->InitWithNamedFuncCallback(
236 ScheduleTimedOut
, this, waitToSchedule
,
237 nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY
, mName
);
241 IdleTaskRunner::~IdleTaskRunner() { CancelTimer(); }
243 void IdleTaskRunner::CancelTimer() {
245 nsRefreshDriver::CancelIdleTask(mTask
);
252 if (mScheduleTimer
) {
253 mScheduleTimer
->Cancel();
255 mTimerActive
= false;
258 void IdleTaskRunner::SetTimerInternal(TimeDuration aDelay
) {
265 void IdleTaskRunner::ResetTimer(TimeDuration aDelay
) {
266 mTimerActive
= false;
269 mTimer
= NS_NewTimer();
275 mTimer
->InitWithNamedFuncCallback(TimedOut
, this, aDelay
.ToMilliseconds(),
276 nsITimer::TYPE_ONE_SHOT
, mName
);
281 } // end of namespace mozilla