tor-browser

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

MessagePump.cpp (9935B)


      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 "MessagePump.h"
      8 
      9 #include "nsIThread.h"
     10 #include "nsITimer.h"
     11 #include "nsICancelableRunnable.h"
     12 
     13 #include "base/basictypes.h"
     14 #include "base/logging.h"
     15 #include "base/scoped_nsautorelease_pool.h"
     16 #include "mozilla/Assertions.h"
     17 #include "mozilla/DebugOnly.h"
     18 #include "nsComponentManagerUtils.h"
     19 #include "nsDebug.h"
     20 #include "nsServiceManagerUtils.h"
     21 #include "nsString.h"
     22 #include "nsThreadUtils.h"
     23 #include "nsTimerImpl.h"
     24 #include "nsXULAppAPI.h"
     25 #include "prthread.h"
     26 
     27 using base::TimeTicks;
     28 using namespace mozilla::ipc;
     29 
     30 #ifdef DEBUG
     31 static MessagePump::Delegate* gFirstDelegate;
     32 #endif
     33 
     34 namespace mozilla {
     35 namespace ipc {
     36 
     37 class DoWorkRunnable final : public CancelableRunnable,
     38                             public nsITimerCallback {
     39 public:
     40  explicit DoWorkRunnable(MessagePump* aPump)
     41      : CancelableRunnable("ipc::DoWorkRunnable"), mPump(aPump) {
     42    MOZ_ASSERT(aPump);
     43  }
     44 
     45  NS_DECL_ISUPPORTS_INHERITED
     46  NS_DECL_NSIRUNNABLE
     47  NS_DECL_NSITIMERCALLBACK
     48  nsresult Cancel() override;
     49 
     50 private:
     51  ~DoWorkRunnable() = default;
     52 
     53  MessagePump* mPump;
     54  // DoWorkRunnable is designed as a stateless singleton.  Do not add stateful
     55  // members here!
     56 };
     57 
     58 } /* namespace ipc */
     59 } /* namespace mozilla */
     60 
     61 MessagePump::MessagePump(nsISerialEventTarget* aEventTarget)
     62    : mEventTarget(aEventTarget) {
     63  mDoWorkEvent = new DoWorkRunnable(this);
     64 }
     65 
     66 MessagePump::~MessagePump() = default;
     67 
     68 void MessagePump::Run(MessagePump::Delegate* aDelegate) {
     69  MOZ_ASSERT(keep_running_);
     70  MOZ_RELEASE_ASSERT(NS_IsMainThread(),
     71                     "Use mozilla::ipc::MessagePumpForNonMainThreads instead!");
     72  MOZ_RELEASE_ASSERT(!mEventTarget);
     73 
     74  nsIThread* thisThread = NS_GetCurrentThread();
     75  MOZ_ASSERT(thisThread);
     76 
     77  mDelayedWorkTimer = NS_NewTimer();
     78  MOZ_ASSERT(mDelayedWorkTimer);
     79 
     80  base::ScopedNSAutoreleasePool autoReleasePool;
     81 
     82  for (;;) {
     83    autoReleasePool.Recycle();
     84 
     85    bool did_work = NS_ProcessNextEvent(thisThread, false) ? true : false;
     86    if (!keep_running_) break;
     87 
     88    // NB: it is crucial *not* to directly call |aDelegate->DoWork()|
     89    // here.  To ensure that MessageLoop tasks and XPCOM events have
     90    // equal priority, we sensitively rely on processing exactly one
     91    // Task per DoWorkRunnable XPCOM event.
     92 
     93    did_work |= aDelegate->DoDelayedWork(&delayed_work_time_);
     94 
     95    if (did_work && delayed_work_time_.is_null()) mDelayedWorkTimer->Cancel();
     96 
     97    if (!keep_running_) break;
     98 
     99    if (did_work) continue;
    100 
    101    did_work = aDelegate->DoIdleWork();
    102    if (!keep_running_) break;
    103 
    104    if (did_work) continue;
    105 
    106    // This will either sleep or process an event.
    107    NS_ProcessNextEvent(thisThread, true);
    108  }
    109 
    110  mDelayedWorkTimer->Cancel();
    111 
    112  keep_running_ = true;
    113 }
    114 
    115 void MessagePump::ScheduleWork() {
    116  // Make sure the event loop wakes up.
    117  if (mEventTarget) {
    118    mEventTarget->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL);
    119  } else {
    120    // Some things (like xpcshell) don't use the app shell and so Run hasn't
    121    // been called. We still need to wake up the main thread.
    122    NS_DispatchToMainThread(mDoWorkEvent);
    123  }
    124  event_.Signal();
    125 }
    126 
    127 void MessagePump::ScheduleWorkForNestedLoop() {
    128  // This method is called when our MessageLoop has just allowed
    129  // nested tasks.  In our setup, whenever that happens we know that
    130  // DoWork() will be called "soon", so there's no need to pay the
    131  // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent).
    132 }
    133 
    134 void MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) {
    135  // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as
    136  // ::Run().
    137  MOZ_RELEASE_ASSERT((!mEventTarget && NS_IsMainThread()) ||
    138                     mEventTarget->IsOnCurrentThread());
    139 
    140  if (!mDelayedWorkTimer) {
    141    mDelayedWorkTimer = NS_NewTimer();
    142    if (!mDelayedWorkTimer) {
    143      // Called before XPCOM has started up? We can't do this correctly.
    144      NS_WARNING("Delayed task might not run!");
    145      delayed_work_time_ = aDelayedTime;
    146      return;
    147    }
    148  }
    149 
    150  if (!delayed_work_time_.is_null()) {
    151    mDelayedWorkTimer->Cancel();
    152  }
    153 
    154  delayed_work_time_ = aDelayedTime;
    155 
    156  // TimeDelta's constructor initializes to 0
    157  base::TimeDelta delay;
    158  if (aDelayedTime > base::TimeTicks::Now())
    159    delay = aDelayedTime - base::TimeTicks::Now();
    160 
    161  uint32_t delayMS = uint32_t(delay.InMilliseconds());
    162  mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS,
    163                                      nsITimer::TYPE_ONE_SHOT);
    164 }
    165 
    166 nsISerialEventTarget* MessagePump::GetXPCOMThread() {
    167  if (mEventTarget) {
    168    return mEventTarget;
    169  }
    170 
    171  // Main thread
    172  return GetMainThreadSerialEventTarget();
    173 }
    174 
    175 void MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) {
    176  aDelegate->DoDelayedWork(&delayed_work_time_);
    177  if (!delayed_work_time_.is_null()) {
    178    ScheduleDelayedWork(delayed_work_time_);
    179  }
    180 }
    181 
    182 NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable,
    183                            nsITimerCallback)
    184 
    185 NS_IMETHODIMP
    186 DoWorkRunnable::Run() {
    187  MessageLoop* loop = MessageLoop::current();
    188  MOZ_ASSERT(loop);
    189 
    190  bool nestableTasksAllowed = loop->NestableTasksAllowed();
    191 
    192  // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
    193  // always dispatch DoWork() below from what looks to MessageLoop like a nested
    194  // context.  So we unconditionally allow nesting here.
    195  loop->SetNestableTasksAllowed(true);
    196  loop->DoWork();
    197  loop->SetNestableTasksAllowed(nestableTasksAllowed);
    198 
    199  return NS_OK;
    200 }
    201 
    202 NS_IMETHODIMP
    203 DoWorkRunnable::Notify(nsITimer* aTimer) {
    204  MessageLoop* loop = MessageLoop::current();
    205  MOZ_ASSERT(loop);
    206 
    207  bool nestableTasksAllowed = loop->NestableTasksAllowed();
    208  loop->SetNestableTasksAllowed(true);
    209  mPump->DoDelayedWork(loop);
    210  loop->SetNestableTasksAllowed(nestableTasksAllowed);
    211 
    212  return NS_OK;
    213 }
    214 
    215 nsresult DoWorkRunnable::Cancel() {
    216  // Workers require cancelable runnables, but we can't really cancel cleanly
    217  // here.  If we don't process this runnable then we will leave something
    218  // unprocessed in the message_loop.  Therefore, eagerly complete our work
    219  // instead by immediately calling Run().  Run() should be called separately
    220  // after this.  Unfortunately we cannot use flags to verify this because
    221  // DoWorkRunnable is a stateless singleton that can be in the event queue
    222  // multiple times simultaneously.
    223  MOZ_ALWAYS_SUCCEEDS(Run());
    224  return NS_OK;
    225 }
    226 
    227 void MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate) {
    228  if (mFirstRun) {
    229    MOZ_ASSERT(aDelegate && !gFirstDelegate);
    230 #ifdef DEBUG
    231    gFirstDelegate = aDelegate;
    232 #endif
    233 
    234    mFirstRun = false;
    235    if (NS_FAILED(XRE_RunAppShell())) {
    236      NS_WARNING("Failed to run app shell?!");
    237    }
    238 
    239    MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
    240 #ifdef DEBUG
    241    gFirstDelegate = nullptr;
    242 #endif
    243 
    244    return;
    245  }
    246 
    247  MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
    248 
    249  // We can get to this point in startup with Tasks in our loop's
    250  // incoming_queue_ or pending_queue_, but without a matching
    251  // DoWorkRunnable().  In MessagePump::Run() above, we sensitively
    252  // depend on *not* directly calling delegate->DoWork(), because that
    253  // prioritizes Tasks above XPCOM events.  However, from this point
    254  // forward, any Task posted to our loop is guaranteed to have a
    255  // DoWorkRunnable enqueued for it.
    256  //
    257  // So we just flush the pending work here and move on.
    258  MessageLoop* loop = MessageLoop::current();
    259  bool nestableTasksAllowed = loop->NestableTasksAllowed();
    260  loop->SetNestableTasksAllowed(true);
    261 
    262  while (aDelegate->DoWork());
    263 
    264  loop->SetNestableTasksAllowed(nestableTasksAllowed);
    265 
    266  // Really run.
    267  mozilla::ipc::MessagePump::Run(aDelegate);
    268 }
    269 
    270 void MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate) {
    271  MOZ_ASSERT(keep_running_);
    272  MOZ_RELEASE_ASSERT(!NS_IsMainThread(),
    273                     "Use mozilla::ipc::MessagePump instead!");
    274 
    275  nsIThread* thread = NS_GetCurrentThread();
    276  MOZ_RELEASE_ASSERT(mEventTarget->IsOnCurrentThread());
    277 
    278  mDelayedWorkTimer = NS_NewTimer(mEventTarget);
    279  MOZ_ASSERT(mDelayedWorkTimer);
    280 
    281  // Chromium event notifications to be processed will be received by this
    282  // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that
    283  // were received before our thread is valid, however, will not generate
    284  // runnable wrappers. We must process any of these before we enter this
    285  // loop, or we will forever have unprocessed chromium messages in our queue.
    286  //
    287  // Note we would like to request a flush of the chromium event queue
    288  // using a runnable on the xpcom side, but some thread implementations
    289  // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork
    290  // calls dispatch on mEventTarget) before the thread processes an event. As
    291  // such, clear the queue manually.
    292  while (aDelegate->DoWork()) {
    293  }
    294 
    295  base::ScopedNSAutoreleasePool autoReleasePool;
    296  for (;;) {
    297    autoReleasePool.Recycle();
    298 
    299    bool didWork = NS_ProcessNextEvent(thread, false) ? true : false;
    300    if (!keep_running_) {
    301      break;
    302    }
    303 
    304    didWork |= aDelegate->DoDelayedWork(&delayed_work_time_);
    305 
    306    if (didWork && delayed_work_time_.is_null()) {
    307      mDelayedWorkTimer->Cancel();
    308    }
    309 
    310    if (!keep_running_) {
    311      break;
    312    }
    313 
    314    if (didWork) {
    315      continue;
    316    }
    317 
    318    DebugOnly<bool> didIdleWork = aDelegate->DoIdleWork();
    319    MOZ_ASSERT(!didIdleWork);
    320    if (!keep_running_) {
    321      break;
    322    }
    323 
    324    if (didWork) {
    325      continue;
    326    }
    327 
    328    // This will either sleep or process an event.
    329    NS_ProcessNextEvent(thread, true);
    330  }
    331 
    332  mDelayedWorkTimer->Cancel();
    333 
    334  keep_running_ = true;
    335 }