TimeoutExecutor.cpp (8425B)
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/. */ 6 7 #include "TimeoutExecutor.h" 8 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" 16 17 extern mozilla::LazyLogModule gTimeoutLog; 18 19 namespace mozilla::dom { 20 21 NS_IMPL_ISUPPORTS(TimeoutExecutor, nsIRunnable, nsITimerCallback, nsINamed) 22 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); 29 } 30 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)); 36 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); 45 } 46 NS_ENSURE_SUCCESS(rv, rv); 47 48 mMode = Mode::Immediate; 49 mDeadline = aDeadline; 50 51 return NS_OK; 52 } 53 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)); 61 62 nsresult rv = NS_OK; 63 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); 69 } 70 71 if (!mTimer) { 72 mTimer = NS_NewTimer(mOwner->EventTarget()); 73 NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY); 74 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); 82 } 83 } else { 84 // Always call Cancel() in case we are re-using a timer. 85 rv = mTimer->Cancel(); 86 NS_ENSURE_SUCCESS(rv, rv); 87 } 88 89 // Calculate the delay based on the deadline and current time. If we have 90 // a minimum delay set then clamp to that value. 91 // 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); 97 98 // Note, we cannot use the normal nsITimer init methods that take 99 // integer milliseconds. We need higher precision. Consider this 100 // situation: 101 // 102 // 1. setTimeout(f, 1); 103 // 2. do some work for 500us 104 // 3. setTimeout(g, 1); 105 // 106 // This should fire f() and g() 500us apart. 107 // 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. 111 // 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); 119 120 mMode = Mode::Delayed; 121 mDeadline = aDeadline; 122 123 return NS_OK; 124 } 125 126 nsresult TimeoutExecutor::Schedule(const TimeStamp& aDeadline, 127 const TimeDuration& aMinDelay) { 128 TimeStamp now(TimeStamp::Now()); 129 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); 135 } 136 137 return ScheduleDelayed(aDeadline, now, aMinDelay); 138 } 139 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); 144 145 if (aDeadline >= mDeadline) { 146 return NS_OK; 147 } 148 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; 154 } 155 156 Cancel(); 157 return Schedule(aDeadline, aMinDelay); 158 } 159 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()); 164 165 TimeStamp deadline(mDeadline); 166 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; 175 } 176 177 Cancel(); 178 179 mOwner->RunTimeout(now, deadline, mIsIdleQueue); 180 } 181 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); 189 } 190 191 void TimeoutExecutor::Shutdown() { 192 mOwner = nullptr; 193 194 if (mTimer) { 195 mTimer->Cancel(); 196 mTimer = nullptr; 197 } 198 199 mMode = Mode::Shutdown; 200 mDeadline = TimeStamp(); 201 } 202 203 nsresult TimeoutExecutor::MaybeSchedule(const TimeStamp& aDeadline, 204 const TimeDuration& aMinDelay) { 205 MOZ_DIAGNOSTIC_ASSERT(!aDeadline.IsNull()); 206 207 if (mMode == Mode::Shutdown) { 208 return NS_OK; 209 } 210 211 if (mMode == Mode::Immediate || mMode == Mode::Delayed) { 212 return MaybeReschedule(aDeadline, aMinDelay); 213 } 214 215 return Schedule(aDeadline, aMinDelay); 216 } 217 218 void TimeoutExecutor::Cancel() { 219 if (mTimer) { 220 mTimer->Cancel(); 221 } 222 mMode = Mode::None; 223 mDeadline = TimeStamp(); 224 } 225 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(); 235 } 236 return NS_OK; 237 } 238 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(); 247 } 248 return NS_OK; 249 } 250 251 NS_IMETHODIMP 252 TimeoutExecutor::GetName(nsACString& aNameOut) { 253 aNameOut.AssignLiteral("TimeoutExecutor Runnable"); 254 return NS_OK; 255 } 256 257 } // namespace mozilla::dom