Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / TimeoutExecutor.cpp
blob466071bf0a91508526bce4c826a7fc9d9fea25cd
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 "TimeoutExecutor.h"
9 #include "mozilla/EventQueue.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/dom/TimeoutManager.h"
12 #include "nsComponentManagerUtils.h"
13 #include "nsIEventTarget.h"
14 #include "nsString.h"
15 #include "nsThreadUtils.h"
17 extern mozilla::LazyLogModule gTimeoutLog;
19 namespace mozilla::dom {
21 NS_IMPL_ISUPPORTS(TimeoutExecutor, nsIRunnable, nsITimerCallback, nsINamed)
23 TimeoutExecutor::~TimeoutExecutor() {
24 // The TimeoutManager should keep the Executor alive until its destroyed,
25 // and then call Shutdown() explicitly.
26 MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Shutdown);
27 MOZ_DIAGNOSTIC_ASSERT(!mOwner);
28 MOZ_DIAGNOSTIC_ASSERT(!mTimer);
31 nsresult TimeoutExecutor::ScheduleImmediate(const TimeStamp& aDeadline,
32 const TimeStamp& aNow) {
33 MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
34 MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
35 MOZ_DIAGNOSTIC_ASSERT(aDeadline <= (aNow + mAllowedEarlyFiringTime));
37 nsresult rv;
38 if (mIsIdleQueue) {
39 RefPtr<TimeoutExecutor> runnable(this);
40 MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Starting IdleDispatch runnable"));
41 rv = NS_DispatchToCurrentThreadQueue(runnable.forget(), mMaxIdleDeferMS,
42 EventQueuePriority::DeferredTimers);
43 } else {
44 rv = mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
46 NS_ENSURE_SUCCESS(rv, rv);
48 mMode = Mode::Immediate;
49 mDeadline = aDeadline;
51 return NS_OK;
54 nsresult TimeoutExecutor::ScheduleDelayed(const TimeStamp& aDeadline,
55 const TimeStamp& aNow,
56 const TimeDuration& aMinDelay) {
57 MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
58 MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
59 MOZ_DIAGNOSTIC_ASSERT(!aMinDelay.IsZero() ||
60 aDeadline > (aNow + mAllowedEarlyFiringTime));
62 nsresult rv = NS_OK;
64 if (mIsIdleQueue) {
65 // Nothing goes into the idletimeouts list if it wasn't going to
66 // fire at that time, so we can always schedule idle-execution of
67 // these immediately
68 return ScheduleImmediate(aNow, aNow);
71 if (!mTimer) {
72 mTimer = NS_NewTimer(mOwner->EventTarget());
73 NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
75 uint32_t earlyMicros = 0;
76 MOZ_ALWAYS_SUCCEEDS(
77 mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
78 mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
79 // Re-evaluate if we should have scheduled this immediately
80 if (aDeadline <= (aNow + mAllowedEarlyFiringTime)) {
81 return ScheduleImmediate(aDeadline, aNow);
83 } else {
84 // Always call Cancel() in case we are re-using a timer.
85 rv = mTimer->Cancel();
86 NS_ENSURE_SUCCESS(rv, rv);
89 // Calculate the delay based on the deadline and current time. If we have
90 // a minimum delay set then clamp to that value.
92 // Note, we don't actually adjust our mDeadline for the minimum delay, just
93 // the nsITimer value. This is necessary to avoid lots of needless
94 // rescheduling if more deadlines come in between now and the minimum delay
95 // firing time.
96 TimeDuration delay = TimeDuration::Max(aMinDelay, aDeadline - aNow);
98 // Note, we cannot use the normal nsITimer init methods that take
99 // integer milliseconds. We need higher precision. Consider this
100 // situation:
102 // 1. setTimeout(f, 1);
103 // 2. do some work for 500us
104 // 3. setTimeout(g, 1);
106 // This should fire f() and g() 500us apart.
108 // In the past worked because each setTimeout() got its own nsITimer. The 1ms
109 // was preserved and passed through to nsITimer which converted it to a
110 // TimeStamp, etc.
112 // Now, however, there is only one nsITimer. We fire f() and then try to
113 // schedule a new nsITimer for g(). Its only 500us in the future, though. We
114 // must be able to pass this fractional value to nsITimer in order to get an
115 // accurate wakeup time.
116 rv = mTimer->InitHighResolutionWithCallback(this, delay,
117 nsITimer::TYPE_ONE_SHOT);
118 NS_ENSURE_SUCCESS(rv, rv);
120 mMode = Mode::Delayed;
121 mDeadline = aDeadline;
123 return NS_OK;
126 nsresult TimeoutExecutor::Schedule(const TimeStamp& aDeadline,
127 const TimeDuration& aMinDelay) {
128 TimeStamp now(TimeStamp::Now());
130 // Schedule an immediate runnable if the desired deadline has passed
131 // or is slightly in the future. This is similar to how nsITimer will
132 // fire timers early based on the interval resolution.
133 if (aMinDelay.IsZero() && aDeadline <= (now + mAllowedEarlyFiringTime)) {
134 return ScheduleImmediate(aDeadline, now);
137 return ScheduleDelayed(aDeadline, now, aMinDelay);
140 nsresult TimeoutExecutor::MaybeReschedule(const TimeStamp& aDeadline,
141 const TimeDuration& aMinDelay) {
142 MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
143 MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Immediate || mMode == Mode::Delayed);
145 if (aDeadline >= mDeadline) {
146 return NS_OK;
149 if (mMode == Mode::Immediate) {
150 // Don't reduce the deadline here as we want to execute the
151 // timer we originally scheduled even if its a few microseconds
152 // in the future.
153 return NS_OK;
156 Cancel();
157 return Schedule(aDeadline, aMinDelay);
160 void TimeoutExecutor::MaybeExecute() {
161 MOZ_DIAGNOSTIC_ASSERT(mMode != Mode::Shutdown && mMode != Mode::None);
162 MOZ_DIAGNOSTIC_ASSERT(mOwner);
163 MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
165 TimeStamp deadline(mDeadline);
167 // Sometimes nsITimer or canceled timers will fire too early. If this
168 // happens then just cap our deadline to our maximum time in the future
169 // and proceed. If there are no timers ready we will get rescheduled
170 // by TimeoutManager.
171 TimeStamp now(TimeStamp::Now());
172 TimeStamp limit = now + mAllowedEarlyFiringTime;
173 if (deadline > limit) {
174 deadline = limit;
177 Cancel();
179 mOwner->RunTimeout(now, deadline, mIsIdleQueue);
182 TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner, bool aIsIdleQueue,
183 uint32_t aMaxIdleDeferMS)
184 : mOwner(aOwner),
185 mIsIdleQueue(aIsIdleQueue),
186 mMaxIdleDeferMS(aMaxIdleDeferMS),
187 mMode(Mode::None) {
188 MOZ_DIAGNOSTIC_ASSERT(mOwner);
191 void TimeoutExecutor::Shutdown() {
192 mOwner = nullptr;
194 if (mTimer) {
195 mTimer->Cancel();
196 mTimer = nullptr;
199 mMode = Mode::Shutdown;
200 mDeadline = TimeStamp();
203 nsresult TimeoutExecutor::MaybeSchedule(const TimeStamp& aDeadline,
204 const TimeDuration& aMinDelay) {
205 MOZ_DIAGNOSTIC_ASSERT(!aDeadline.IsNull());
207 if (mMode == Mode::Shutdown) {
208 return NS_OK;
211 if (mMode == Mode::Immediate || mMode == Mode::Delayed) {
212 return MaybeReschedule(aDeadline, aMinDelay);
215 return Schedule(aDeadline, aMinDelay);
218 void TimeoutExecutor::Cancel() {
219 if (mTimer) {
220 mTimer->Cancel();
222 mMode = Mode::None;
223 mDeadline = TimeStamp();
226 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
227 // bug 1535398.
228 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP TimeoutExecutor::Run() {
229 // If the executor is canceled and then rescheduled its possible to get
230 // spurious executions here. Ignore these unless our current mode matches.
231 MOZ_LOG(gTimeoutLog, LogLevel::Debug,
232 ("Running Immediate %stimers", mIsIdleQueue ? "Idle" : ""));
233 if (mMode == Mode::Immediate) {
234 MaybeExecute();
236 return NS_OK;
239 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until nsITimerCallback::Notify is
240 // MOZ_CAN_RUN_SCRIPT.
241 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
242 TimeoutExecutor::Notify(nsITimer* aTimer) {
243 // If the executor is canceled and then rescheduled its possible to get
244 // spurious executions here. Ignore these unless our current mode matches.
245 if (mMode == Mode::Delayed) {
246 MaybeExecute();
248 return NS_OK;
251 NS_IMETHODIMP
252 TimeoutExecutor::GetName(nsACString& aNameOut) {
253 aNameOut.AssignLiteral("TimeoutExecutor Runnable");
254 return NS_OK;
257 } // namespace mozilla::dom