Backed out 3 changesets (bug 1877678, bug 1849175) for causing failures on browser_op...
[gecko.git] / xpcom / threads / IdleTaskRunner.cpp
blobe9bb895dee11c0566eac04b2cf901c22b9c05dfc
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"
11 namespace mozilla {
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()) {
20 return nullptr;
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 {
31 public:
32 explicit IdleTaskRunnerTask(IdleTaskRunner* aRunner)
33 : Task(Kind::MainThreadOnly, EventQueuePriority::Idle),
34 mRunner(aRunner),
35 mRequestInterrupt(aRunner->mRequestInterrupt) {
36 SetManager(TaskController::Get()->GetIdleTaskManager());
39 TaskResult Run() override {
40 if (mRunner) {
41 // IdleTaskRunner::Run can actually trigger the destruction of the
42 // IdleTaskRunner. Make sure it doesn't get destroyed before the method
43 // finished.
44 RefPtr<IdleTaskRunner> runner(mRunner);
45 runner->Run();
47 return TaskResult::Complete;
50 void SetIdleDeadline(TimeStamp aDeadline) override {
51 if (mRunner) {
52 mRunner->SetIdleDeadline(aDeadline);
56 void Cancel() { mRunner = nullptr; }
58 bool GetName(nsACString& aName) override {
59 if (mRunner) {
60 aName.Assign(mRunner->GetName());
61 } else {
62 aName.Assign("ExpiredIdleTaskRunner");
64 return true;
67 void RequestInterrupt(uint32_t aInterruptPriority) override {
68 if (mRequestInterrupt) {
69 mRequestInterrupt(aInterruptPriority);
73 private:
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),
89 mMaxDelay(aMaxDelay),
90 mMinimumUsefulBudget(aMinimumUsefulBudget),
91 mRepeating(aRepeating),
92 mTimerActive(false),
93 mMayStopProcessing(aMayStopProcessing),
94 mRequestInterrupt(aRequestInterrupt),
95 mName(aRunnableName) {}
97 void IdleTaskRunner::Run() {
98 if (!mCallback) {
99 return;
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();
109 bool didRun = false;
110 bool allowIdleDispatch = false;
112 if (mTask) {
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.
118 mTask->Cancel();
119 mTask = nullptr;
122 if (overdueForIdle || ((now + mMinimumUsefulBudget) < mDeadline)) {
123 CancelTimer();
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
127 // period ends.
128 allowIdleDispatch = didRun;
129 } else if (now >= mDeadline) {
130 allowIdleDispatch = true;
133 if (mCallback && (mRepeating || !didRun)) {
134 Schedule(allowIdleDispatch);
135 } else {
136 mCallback = nullptr;
140 static void TimedOut(nsITimer* aTimer, void* aClosure) {
141 RefPtr<IdleTaskRunner> runner = static_cast<IdleTaskRunner*>(aClosure);
142 runner->Run();
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() {
163 CancelTimer();
164 mTimer = nullptr;
165 mScheduleTimer = nullptr;
166 mCallback = 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) {
175 if (!mCallback) {
176 return;
179 if (mMayStopProcessing && mMayStopProcessing()) {
180 Cancel();
181 return;
184 mDeadline = TimeStamp();
186 TimeStamp now = TimeStamp::Now();
188 if (aAllowIdleDispatch) {
189 SetTimerInternal(mMaxDelay);
190 if (!mTask) {
191 mTask = new IdleTaskRunnerTask(this);
192 RefPtr<Task> task(mTask);
193 TaskController::Get()->AddTask(task.forget());
195 return;
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.
202 useRefreshDriver =
203 (nsRefreshDriver::GetIdleDeadlineHint(
204 now, nsRefreshDriver::IdleCheck::OnlyThisProcessRefreshDriver) !=
205 now);
208 if (useRefreshDriver) {
209 if (!mTask) {
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);
217 } else {
218 // RefreshDriver doesn't seem to be running.
219 if (!mScheduleTimer) {
220 mScheduleTimer = NS_NewTimer();
221 if (!mScheduleTimer) {
222 return;
224 } else {
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
232 // mStartTime.
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() {
244 if (mTask) {
245 nsRefreshDriver::CancelIdleTask(mTask);
246 mTask->Cancel();
247 mTask = nullptr;
249 if (mTimer) {
250 mTimer->Cancel();
252 if (mScheduleTimer) {
253 mScheduleTimer->Cancel();
255 mTimerActive = false;
258 void IdleTaskRunner::SetTimerInternal(TimeDuration aDelay) {
259 if (mTimerActive) {
260 return;
262 ResetTimer(aDelay);
265 void IdleTaskRunner::ResetTimer(TimeDuration aDelay) {
266 mTimerActive = false;
268 if (!mTimer) {
269 mTimer = NS_NewTimer();
270 } else {
271 mTimer->Cancel();
274 if (mTimer) {
275 mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay.ToMilliseconds(),
276 nsITimer::TYPE_ONE_SHOT, mName);
277 mTimerActive = true;
281 } // end of namespace mozilla