tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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