forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			257 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "TimeoutExecutor.h"
 | |
| 
 | |
| #include "mozilla/EventQueue.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "mozilla/dom/TimeoutManager.h"
 | |
| #include "nsComponentManagerUtils.h"
 | |
| #include "nsIEventTarget.h"
 | |
| #include "nsString.h"
 | |
| #include "nsThreadUtils.h"
 | |
| 
 | |
| extern mozilla::LazyLogModule gTimeoutLog;
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(TimeoutExecutor, nsIRunnable, nsITimerCallback, nsINamed)
 | |
| 
 | |
| TimeoutExecutor::~TimeoutExecutor() {
 | |
|   // The TimeoutManager should keep the Executor alive until its destroyed,
 | |
|   // and then call Shutdown() explicitly.
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Shutdown);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mOwner);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mTimer);
 | |
| }
 | |
| 
 | |
| nsresult TimeoutExecutor::ScheduleImmediate(const TimeStamp& aDeadline,
 | |
|                                             const TimeStamp& aNow) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aDeadline <= (aNow + mAllowedEarlyFiringTime));
 | |
| 
 | |
|   nsresult rv;
 | |
|   if (mIsIdleQueue) {
 | |
|     RefPtr<TimeoutExecutor> runnable(this);
 | |
|     MOZ_LOG(gTimeoutLog, LogLevel::Debug, ("Starting IdleDispatch runnable"));
 | |
|     rv = NS_DispatchToCurrentThreadQueue(runnable.forget(), mMaxIdleDeferMS,
 | |
|                                          EventQueuePriority::DeferredTimers);
 | |
|   } else {
 | |
|     rv = mOwner->EventTarget()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   mMode = Mode::Immediate;
 | |
|   mDeadline = aDeadline;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TimeoutExecutor::ScheduleDelayed(const TimeStamp& aDeadline,
 | |
|                                           const TimeStamp& aNow,
 | |
|                                           const TimeDuration& aMinDelay) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mDeadline.IsNull());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::None);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!aMinDelay.IsZero() ||
 | |
|                         aDeadline > (aNow + mAllowedEarlyFiringTime));
 | |
| 
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   if (mIsIdleQueue) {
 | |
|     // Nothing goes into the idletimeouts list if it wasn't going to
 | |
|     // fire at that time, so we can always schedule idle-execution of
 | |
|     // these immediately
 | |
|     return ScheduleImmediate(aNow, aNow);
 | |
|   }
 | |
| 
 | |
|   if (!mTimer) {
 | |
|     mTimer = NS_NewTimer(mOwner->EventTarget());
 | |
|     NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
 | |
| 
 | |
|     uint32_t earlyMicros = 0;
 | |
|     MOZ_ALWAYS_SUCCEEDS(
 | |
|         mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
 | |
|     mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
 | |
|     // Re-evaluate if we should have scheduled this immediately
 | |
|     if (aDeadline <= (aNow + mAllowedEarlyFiringTime)) {
 | |
|       return ScheduleImmediate(aDeadline, aNow);
 | |
|     }
 | |
|   } else {
 | |
|     // Always call Cancel() in case we are re-using a timer.
 | |
|     rv = mTimer->Cancel();
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // Calculate the delay based on the deadline and current time.  If we have
 | |
|   // a minimum delay set then clamp to that value.
 | |
|   //
 | |
|   // Note, we don't actually adjust our mDeadline for the minimum delay, just
 | |
|   // the nsITimer value.  This is necessary to avoid lots of needless
 | |
|   // rescheduling if more deadlines come in between now and the minimum delay
 | |
|   // firing time.
 | |
|   TimeDuration delay = TimeDuration::Max(aMinDelay, aDeadline - aNow);
 | |
| 
 | |
|   // Note, we cannot use the normal nsITimer init methods that take
 | |
|   // integer milliseconds.  We need higher precision.  Consider this
 | |
|   // situation:
 | |
|   //
 | |
|   // 1. setTimeout(f, 1);
 | |
|   // 2. do some work for 500us
 | |
|   // 3. setTimeout(g, 1);
 | |
|   //
 | |
|   // This should fire f() and g() 500us apart.
 | |
|   //
 | |
|   // In the past worked because each setTimeout() got its own nsITimer.  The 1ms
 | |
|   // was preserved and passed through to nsITimer which converted it to a
 | |
|   // TimeStamp, etc.
 | |
|   //
 | |
|   // Now, however, there is only one nsITimer.  We fire f() and then try to
 | |
|   // schedule a new nsITimer for g().  Its only 500us in the future, though.  We
 | |
|   // must be able to pass this fractional value to nsITimer in order to get an
 | |
|   // accurate wakeup time.
 | |
|   rv = mTimer->InitHighResolutionWithCallback(this, delay,
 | |
|                                               nsITimer::TYPE_ONE_SHOT);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   mMode = Mode::Delayed;
 | |
|   mDeadline = aDeadline;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult TimeoutExecutor::Schedule(const TimeStamp& aDeadline,
 | |
|                                    const TimeDuration& aMinDelay) {
 | |
|   TimeStamp now(TimeStamp::Now());
 | |
| 
 | |
|   // Schedule an immediate runnable if the desired deadline has passed
 | |
|   // or is slightly in the future.  This is similar to how nsITimer will
 | |
|   // fire timers early based on the interval resolution.
 | |
|   if (aMinDelay.IsZero() && aDeadline <= (now + mAllowedEarlyFiringTime)) {
 | |
|     return ScheduleImmediate(aDeadline, now);
 | |
|   }
 | |
| 
 | |
|   return ScheduleDelayed(aDeadline, now, aMinDelay);
 | |
| }
 | |
| 
 | |
| nsresult TimeoutExecutor::MaybeReschedule(const TimeStamp& aDeadline,
 | |
|                                           const TimeDuration& aMinDelay) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mMode == Mode::Immediate || mMode == Mode::Delayed);
 | |
| 
 | |
|   if (aDeadline >= mDeadline) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (mMode == Mode::Immediate) {
 | |
|     // Don't reduce the deadline here as we want to execute the
 | |
|     // timer we originally scheduled even if its a few microseconds
 | |
|     // in the future.
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   Cancel();
 | |
|   return Schedule(aDeadline, aMinDelay);
 | |
| }
 | |
| 
 | |
| void TimeoutExecutor::MaybeExecute() {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mMode != Mode::Shutdown && mMode != Mode::None);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mOwner);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mDeadline.IsNull());
 | |
| 
 | |
|   TimeStamp deadline(mDeadline);
 | |
| 
 | |
|   // Sometimes nsITimer or canceled timers will fire too early.  If this
 | |
|   // happens then just cap our deadline to our maximum time in the future
 | |
|   // and proceed.  If there are no timers ready we will get rescheduled
 | |
|   // by TimeoutManager.
 | |
|   TimeStamp now(TimeStamp::Now());
 | |
|   TimeStamp limit = now + mAllowedEarlyFiringTime;
 | |
|   if (deadline > limit) {
 | |
|     deadline = limit;
 | |
|   }
 | |
| 
 | |
|   Cancel();
 | |
| 
 | |
|   mOwner->RunTimeout(now, deadline, mIsIdleQueue);
 | |
| }
 | |
| 
 | |
| TimeoutExecutor::TimeoutExecutor(TimeoutManager* aOwner, bool aIsIdleQueue,
 | |
|                                  uint32_t aMaxIdleDeferMS)
 | |
|     : mOwner(aOwner),
 | |
|       mIsIdleQueue(aIsIdleQueue),
 | |
|       mMaxIdleDeferMS(aMaxIdleDeferMS),
 | |
|       mMode(Mode::None) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mOwner);
 | |
| }
 | |
| 
 | |
| void TimeoutExecutor::Shutdown() {
 | |
|   mOwner = nullptr;
 | |
| 
 | |
|   if (mTimer) {
 | |
|     mTimer->Cancel();
 | |
|     mTimer = nullptr;
 | |
|   }
 | |
| 
 | |
|   mMode = Mode::Shutdown;
 | |
|   mDeadline = TimeStamp();
 | |
| }
 | |
| 
 | |
| nsresult TimeoutExecutor::MaybeSchedule(const TimeStamp& aDeadline,
 | |
|                                         const TimeDuration& aMinDelay) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!aDeadline.IsNull());
 | |
| 
 | |
|   if (mMode == Mode::Shutdown) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (mMode == Mode::Immediate || mMode == Mode::Delayed) {
 | |
|     return MaybeReschedule(aDeadline, aMinDelay);
 | |
|   }
 | |
| 
 | |
|   return Schedule(aDeadline, aMinDelay);
 | |
| }
 | |
| 
 | |
| void TimeoutExecutor::Cancel() {
 | |
|   if (mTimer) {
 | |
|     mTimer->Cancel();
 | |
|   }
 | |
|   mMode = Mode::None;
 | |
|   mDeadline = TimeStamp();
 | |
| }
 | |
| 
 | |
| // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.  See
 | |
| // bug 1535398.
 | |
| MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP TimeoutExecutor::Run() {
 | |
|   // If the executor is canceled and then rescheduled its possible to get
 | |
|   // spurious executions here.  Ignore these unless our current mode matches.
 | |
|   MOZ_LOG(gTimeoutLog, LogLevel::Debug,
 | |
|           ("Running Immediate %stimers", mIsIdleQueue ? "Idle" : ""));
 | |
|   if (mMode == Mode::Immediate) {
 | |
|     MaybeExecute();
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // MOZ_CAN_RUN_SCRIPT_BOUNDARY until nsITimerCallback::Notify is
 | |
| // MOZ_CAN_RUN_SCRIPT.
 | |
| MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
 | |
| TimeoutExecutor::Notify(nsITimer* aTimer) {
 | |
|   // If the executor is canceled and then rescheduled its possible to get
 | |
|   // spurious executions here.  Ignore these unless our current mode matches.
 | |
|   if (mMode == Mode::Delayed) {
 | |
|     MaybeExecute();
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| TimeoutExecutor::GetName(nsACString& aNameOut) {
 | |
|   aNameOut.AssignLiteral("TimeoutExecutor Runnable");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom
 | 
