Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
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/. */
6 :
7 : #include "TimeoutExecutor.h"
8 :
9 : namespace mozilla {
10 : namespace dom {
11 :
12 116 : NS_IMPL_ISUPPORTS(TimeoutExecutor, nsIRunnable, nsITimerCallback, nsINamed)
13 :
14 0 : TimeoutExecutor::~TimeoutExecutor()
15 : {
16 : // The TimeoutManager should keep the Executor alive until its destroyed,
17 : // and then call Shutdown() explicitly.
18 0 : MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Shutdown);
19 0 : MOZ_DIAGNOSTIC_ASSERT(!mOwner);
20 0 : MOZ_DIAGNOSTIC_ASSERT(!mTimer);
21 0 : }
22 :
23 : nsresult
24 6 : TimeoutExecutor::ScheduleImmediate(const TimeStamp& aDeadline,
25 : const TimeStamp& aNow)
26 : {
27 6 : MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
28 6 : MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
29 6 : MOZ_DIAGNOSTIC_ASSERT(aDeadline <= (aNow + mAllowedEarlyFiringTime));
30 :
31 : nsresult rv =
32 6 : mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
33 6 : NS_ENSURE_SUCCESS(rv, rv);
34 :
35 6 : mMode = Mode::Immediate;
36 6 : mDeadline = aDeadline;
37 :
38 6 : return NS_OK;
39 : }
40 :
41 : nsresult
42 10 : TimeoutExecutor::ScheduleDelayed(const TimeStamp& aDeadline,
43 : const TimeStamp& aNow,
44 : const TimeDuration& aMinDelay)
45 : {
46 10 : MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
47 10 : MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
48 10 : MOZ_DIAGNOSTIC_ASSERT(!aMinDelay.IsZero() ||
49 : aDeadline > (aNow + mAllowedEarlyFiringTime));
50 :
51 10 : nsresult rv = NS_OK;
52 :
53 10 : if (!mTimer) {
54 2 : mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
55 2 : NS_ENSURE_SUCCESS(rv, rv);
56 :
57 2 : uint32_t earlyMicros = 0;
58 2 : MOZ_ALWAYS_SUCCEEDS(mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
59 2 : mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
60 : }
61 :
62 : // Always call Cancel() in case we are re-using a timer. Otherwise
63 : // the subsequent SetTarget() may fail.
64 10 : rv = mTimer->Cancel();
65 10 : NS_ENSURE_SUCCESS(rv, rv);
66 :
67 10 : rv = mTimer->SetTarget(mOwner->EventTarget());
68 10 : NS_ENSURE_SUCCESS(rv, rv);
69 :
70 : // Calculate the delay based on the deadline and current time. If we have
71 : // a minimum delay set then clamp to that value.
72 : //
73 : // Note, we don't actually adjust our mDeadline for the minimum delay, just
74 : // the nsITimer value. This is necessary to avoid lots of needless
75 : // rescheduling if more deadlines come in between now and the minimum delay
76 : // firing time.
77 10 : TimeDuration delay = TimeDuration::Max(aMinDelay, aDeadline - aNow);
78 :
79 : // Note, we cannot use the normal nsITimer init methods that take
80 : // integer milliseconds. We need higher precision. Consider this
81 : // situation:
82 : //
83 : // 1. setTimeout(f, 1);
84 : // 2. do some work for 500us
85 : // 3. setTimeout(g, 1);
86 : //
87 : // This should fire f() and g() 500us apart.
88 : //
89 : // In the past worked because each setTimeout() got its own nsITimer. The 1ms
90 : // was preserved and passed through to nsITimer which converted it to a
91 : // TimeStamp, etc.
92 : //
93 : // Now, however, there is only one nsITimer. We fire f() and then try to
94 : // schedule a new nsITimer for g(). Its only 500us in the future, though. We
95 : // must be able to pass this fractional value to nsITimer in order to get an
96 : // accurate wakeup time.
97 20 : rv = mTimer->InitHighResolutionWithCallback(this, delay,
98 10 : nsITimer::TYPE_ONE_SHOT);
99 10 : NS_ENSURE_SUCCESS(rv, rv);
100 :
101 10 : mMode = Mode::Delayed;
102 10 : mDeadline = aDeadline;
103 :
104 10 : return NS_OK;
105 : }
106 :
107 : nsresult
108 16 : TimeoutExecutor::Schedule(const TimeStamp& aDeadline,
109 : const TimeDuration& aMinDelay)
110 : {
111 16 : TimeStamp now(TimeStamp::Now());
112 :
113 : // Schedule an immediate runnable if the desired deadline has passed
114 : // or is slightly in the future. This is similar to how nsITimer will
115 : // fire timers early based on the interval resolution.
116 16 : if (aMinDelay.IsZero() && aDeadline <= (now + mAllowedEarlyFiringTime)) {
117 6 : return ScheduleImmediate(aDeadline, now);
118 : }
119 :
120 10 : return ScheduleDelayed(aDeadline, now, aMinDelay);
121 : }
122 :
123 : nsresult
124 16 : TimeoutExecutor::MaybeReschedule(const TimeStamp& aDeadline,
125 : const TimeDuration& aMinDelay)
126 : {
127 16 : MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
128 16 : MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Immediate ||
129 : mMode == Mode::Delayed);
130 :
131 16 : if (aDeadline >= mDeadline) {
132 10 : return NS_OK;
133 : }
134 :
135 6 : if (mMode == Mode::Immediate) {
136 : // Don't reduce the deadline here as we want to execute the
137 : // timer we originally scheduled even if its a few microseconds
138 : // in the future.
139 0 : return NS_OK;
140 : }
141 :
142 6 : Cancel();
143 6 : return Schedule(aDeadline, aMinDelay);
144 : }
145 :
146 : void
147 8 : TimeoutExecutor::MaybeExecute()
148 : {
149 8 : MOZ_DIAGNOSTIC_ASSERT(mMode != Mode::Shutdown && mMode != Mode::None);
150 8 : MOZ_DIAGNOSTIC_ASSERT(mOwner);
151 8 : MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
152 :
153 8 : TimeStamp deadline(mDeadline);
154 :
155 : // Sometimes nsITimer or canceled timers will fire too early. If this
156 : // happens then just cap our deadline to our maximum time in the future
157 : // and proceed. If there are no timers ready we will get rescheduled
158 : // by TimeoutManager.
159 8 : TimeStamp now(TimeStamp::Now());
160 8 : TimeStamp limit = now + mAllowedEarlyFiringTime;
161 8 : if (deadline > limit) {
162 0 : deadline = limit;
163 : }
164 :
165 8 : Cancel();
166 :
167 8 : mOwner->RunTimeout(now, deadline);
168 8 : }
169 :
170 7 : TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner)
171 : : mOwner(aOwner)
172 7 : , mMode(Mode::None)
173 : {
174 7 : MOZ_DIAGNOSTIC_ASSERT(mOwner);
175 7 : }
176 :
177 : void
178 0 : TimeoutExecutor::Shutdown()
179 : {
180 0 : mOwner = nullptr;
181 :
182 0 : if (mTimer) {
183 0 : mTimer->Cancel();
184 0 : mTimer = nullptr;
185 : }
186 :
187 0 : mMode = Mode::Shutdown;
188 0 : mDeadline = TimeStamp();
189 0 : }
190 :
191 : nsresult
192 26 : TimeoutExecutor::MaybeSchedule(const TimeStamp& aDeadline,
193 : const TimeDuration& aMinDelay)
194 : {
195 26 : MOZ_DIAGNOSTIC_ASSERT(!aDeadline.IsNull());
196 :
197 26 : if (mMode == Mode::Shutdown) {
198 0 : return NS_OK;
199 : }
200 :
201 26 : if (mMode == Mode::Immediate || mMode == Mode::Delayed) {
202 16 : return MaybeReschedule(aDeadline, aMinDelay);
203 : }
204 :
205 10 : return Schedule(aDeadline, aMinDelay);
206 : }
207 :
208 : void
209 17 : TimeoutExecutor::Cancel()
210 : {
211 17 : if (mTimer) {
212 14 : mTimer->Cancel();
213 : }
214 17 : mMode = Mode::None;
215 17 : mDeadline = TimeStamp();
216 17 : }
217 :
218 : NS_IMETHODIMP
219 5 : TimeoutExecutor::Run()
220 : {
221 : // If the executor is canceled and then rescheduled its possible to get
222 : // spurious executions here. Ignore these unless our current mode matches.
223 5 : if (mMode == Mode::Immediate) {
224 5 : MaybeExecute();
225 : }
226 5 : return NS_OK;
227 : }
228 :
229 : NS_IMETHODIMP
230 3 : TimeoutExecutor::Notify(nsITimer* aTimer)
231 : {
232 : // If the executor is canceled and then rescheduled its possible to get
233 : // spurious executions here. Ignore these unless our current mode matches.
234 3 : if (mMode == Mode::Delayed) {
235 3 : MaybeExecute();
236 : }
237 3 : return NS_OK;
238 : }
239 :
240 : NS_IMETHODIMP
241 9 : TimeoutExecutor::GetName(nsACString& aNameOut)
242 : {
243 9 : aNameOut.AssignLiteral("TimeoutExecutor Runnable");
244 9 : return NS_OK;
245 : }
246 :
247 : NS_IMETHODIMP
248 0 : TimeoutExecutor::SetName(const char* aName)
249 : {
250 0 : return NS_ERROR_NOT_IMPLEMENTED;
251 : }
252 :
253 : } // namespace dom
254 : } // namespace mozilla
|