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"
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
));
39 RefPtr
<TimeoutExecutor
> runnable(this);
40 MOZ_LOG(gTimeoutLog
, LogLevel::Debug
, ("Starting IdleDispatch runnable"));
41 rv
= NS_DispatchToCurrentThreadQueue(runnable
.forget(), mMaxIdleDeferMS
,
42 EventQueuePriority::DeferredTimers
);
44 rv
= mOwner
->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL
);
46 NS_ENSURE_SUCCESS(rv
, rv
);
48 mMode
= Mode::Immediate
;
49 mDeadline
= aDeadline
;
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
));
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
68 return ScheduleImmediate(aNow
, aNow
);
72 mTimer
= NS_NewTimer(mOwner
->EventTarget());
73 NS_ENSURE_TRUE(mTimer
, NS_ERROR_OUT_OF_MEMORY
);
75 uint32_t earlyMicros
= 0;
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
);
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
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
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
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
;
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
) {
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
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
) {
179 mOwner
->RunTimeout(now
, deadline
, mIsIdleQueue
);
182 TimeoutExecutor::TimeoutExecutor(TimeoutManager
* aOwner
, bool aIsIdleQueue
,
183 uint32_t aMaxIdleDeferMS
)
185 mIsIdleQueue(aIsIdleQueue
),
186 mMaxIdleDeferMS(aMaxIdleDeferMS
),
188 MOZ_DIAGNOSTIC_ASSERT(mOwner
);
191 void TimeoutExecutor::Shutdown() {
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
) {
211 if (mMode
== Mode::Immediate
|| mMode
== Mode::Delayed
) {
212 return MaybeReschedule(aDeadline
, aMinDelay
);
215 return Schedule(aDeadline
, aMinDelay
);
218 void TimeoutExecutor::Cancel() {
223 mDeadline
= TimeStamp();
226 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
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
) {
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
) {
252 TimeoutExecutor::GetName(nsACString
& aNameOut
) {
253 aNameOut
.AssignLiteral("TimeoutExecutor Runnable");
257 } // namespace mozilla::dom