LCOV - code coverage report
Current view: top level - xpcom/threads - ThrottledEventQueue.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 76 156 48.7 %
Date: 2017-07-14 16:53:18 Functions: 18 39 46.2 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "ThrottledEventQueue.h"
       8             : 
       9             : #include "mozilla/Atomics.h"
      10             : #include "mozilla/ClearOnShutdown.h"
      11             : #include "mozilla/Mutex.h"
      12             : #include "mozilla/Unused.h"
      13             : #include "nsEventQueue.h"
      14             : 
      15             : namespace mozilla {
      16             : 
      17             : using mozilla::services::GetObserverService;
      18             : 
      19             : namespace {
      20             : 
      21             : static const char kShutdownTopic[] = "xpcom-shutdown";
      22             : 
      23             : } // anonymous namespace
      24             : 
      25             : // The ThrottledEventQueue is designed with inner and outer objects:
      26             : //
      27             : //       XPCOM code    nsObserverService
      28             : //            |               |
      29             : //            |               |
      30             : //            v               |
      31             : //        +-------+           |
      32             : //        | Outer |           |
      33             : //        +-------+           |
      34             : //            |               |
      35             : //            |   +-------+   |
      36             : //            +-->| Inner |<--+
      37             : //                +-------+
      38             : //
      39             : // Client code references the outer nsIEventTarget which in turn references
      40             : // an inner object.  The inner object is also held alive by the observer
      41             : // service.
      42             : //
      43             : // If the outer object is dereferenced and destroyed, it will trigger a
      44             : // shutdown operation on the inner object.  Similarly if the observer
      45             : // service notifies that the browser is shutting down, then the inner
      46             : // object also starts shutting down.
      47             : //
      48             : // Once the queue has drained we unregister from the observer service.  If
      49             : // the outer object is already gone, then the inner object is free'd at this
      50             : // point.  If the outer object still exists then calls fall back to the
      51             : // ThrottledEventQueue's base target.  We just don't queue things
      52             : // any more.  The inner is then released once the outer object is released.
      53             : //
      54             : // Note, we must keep the inner object alive and attached to the observer
      55             : // service until the TaskQueue is fully shutdown and idle.  We must delay
      56             : // xpcom shutdown if the TaskQueue is in the middle of draining.
      57             : class ThrottledEventQueue::Inner final : public nsIObserver
      58             : {
      59             :   // The runnable which is dispatched to the underlying base target.  Since
      60             :   // we only execute one event at a time we just re-use a single instance
      61             :   // of this class while there are events left in the queue.
      62          39 :   class Executor final : public Runnable
      63             :   {
      64             :     RefPtr<Inner> mInner;
      65             : 
      66             :   public:
      67          14 :     explicit Executor(Inner* aInner)
      68          14 :       : Runnable("ThrottledEventQueue::Inner::Executor")
      69          14 :       , mInner(aInner)
      70          14 :     { }
      71             : 
      72             :     NS_IMETHODIMP
      73          13 :     Run() override
      74             :     {
      75          13 :       mInner->ExecuteRunnable();
      76          13 :       return NS_OK;
      77             :     }
      78             : 
      79             :     NS_IMETHODIMP
      80          14 :     GetName(nsACString& aName) override
      81             :     {
      82          14 :       return mInner->CurrentName(aName);
      83             :     }
      84             :   };
      85             : 
      86             :   mutable Mutex mMutex;
      87             :   mutable CondVar mIdleCondVar;
      88             : 
      89             :   mozilla::CondVar mEventsAvailable;
      90             : 
      91             :   // any thread, protected by mutex
      92             :   nsEventQueue mEventQueue;
      93             : 
      94             :   // written on main thread, read on any thread
      95             :   nsCOMPtr<nsISerialEventTarget> mBaseTarget;
      96             : 
      97             :   // any thread, protected by mutex
      98             :   nsCOMPtr<nsIRunnable> mExecutor;
      99             : 
     100             :   // any thread, protected by mutex
     101             :   bool mShutdownStarted;
     102             : 
     103           5 :   explicit Inner(nsISerialEventTarget* aBaseTarget)
     104           5 :     : mMutex("ThrottledEventQueue")
     105             :     , mIdleCondVar(mMutex, "ThrottledEventQueue:Idle")
     106             :     , mEventsAvailable(mMutex, "[ThrottledEventQueue::Inner.mEventsAvailable]")
     107             :     , mEventQueue(mEventsAvailable, nsEventQueue::eNormalQueue)
     108             :     , mBaseTarget(aBaseTarget)
     109           5 :     , mShutdownStarted(false)
     110             :   {
     111           5 :   }
     112             : 
     113           0 :   ~Inner()
     114           0 :   {
     115           0 :     MOZ_ASSERT(!mExecutor);
     116           0 :     MOZ_ASSERT(mShutdownStarted);
     117           0 :   }
     118             : 
     119             :   nsresult
     120          14 :   CurrentName(nsACString& aName)
     121             :   {
     122          28 :     nsCOMPtr<nsIRunnable> event;
     123             : 
     124             : #ifdef DEBUG
     125          14 :     bool currentThread = false;
     126          14 :     mBaseTarget->IsOnCurrentThread(&currentThread);
     127          14 :     MOZ_ASSERT(currentThread);
     128             : #endif
     129             : 
     130             :     {
     131          28 :       MutexAutoLock lock(mMutex);
     132             : 
     133             :       // We only check the name of an executor runnable when we know there is something
     134             :       // in the queue, so this should never fail.
     135          14 :       MOZ_ALWAYS_TRUE(mEventQueue.PeekEvent(getter_AddRefs(event), lock));
     136             :     }
     137             : 
     138          15 :     if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) {
     139          13 :       nsresult rv = named->GetName(aName);
     140          13 :       return rv;
     141             :     }
     142             : 
     143           1 :     aName.AssignLiteral("non-nsINamed ThrottledEventQueue runnable");
     144           1 :     return NS_OK;
     145             :   }
     146             : 
     147             :   void
     148          13 :   ExecuteRunnable()
     149             :   {
     150             :     // Any thread
     151          26 :     nsCOMPtr<nsIRunnable> event;
     152          13 :     bool shouldShutdown = false;
     153             : 
     154             : #ifdef DEBUG
     155          13 :     bool currentThread = false;
     156          13 :     mBaseTarget->IsOnCurrentThread(&currentThread);
     157          13 :     MOZ_ASSERT(currentThread);
     158             : #endif
     159             : 
     160             :     {
     161          26 :       MutexAutoLock lock(mMutex);
     162             : 
     163             :       // We only dispatch an executor runnable when we know there is something
     164             :       // in the queue, so this should never fail.
     165          13 :       MOZ_ALWAYS_TRUE(mEventQueue.GetPendingEvent(getter_AddRefs(event), lock));
     166             : 
     167             :       // If there are more events in the queue, then dispatch the next
     168             :       // executor.  We do this now, before running the event, because
     169             :       // the event might spin the event loop and we don't want to stall
     170             :       // the queue.
     171          13 :       if (mEventQueue.HasPendingEvent(lock)) {
     172             :         // Dispatch the next base target runnable to attempt to execute
     173             :         // the next throttled event.  We must do this before executing
     174             :         // the event in case the event spins the event loop.
     175           0 :         MOZ_ALWAYS_SUCCEEDS(
     176             :           mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL));
     177             :       }
     178             : 
     179             :       // Otherwise the queue is empty and we can stop dispatching the
     180             :       // executor.  We might also need to shutdown after running the
     181             :       // last event.
     182             :       else {
     183          13 :         shouldShutdown = mShutdownStarted;
     184             :         // Note, this breaks a ref cycle.
     185          13 :         mExecutor = nullptr;
     186          13 :         mIdleCondVar.NotifyAll();
     187             :       }
     188             :     }
     189             : 
     190             :     // Execute the event now that we have unlocked.
     191          13 :     Unused << event->Run();
     192             : 
     193             :     // If shutdown was started and the queue is now empty we can now
     194             :     // finalize the shutdown.  This is performed separately at the end
     195             :     // of the method in order to wait for the event to finish running.
     196          13 :     if (shouldShutdown) {
     197           0 :       MOZ_ASSERT(IsEmpty());
     198           0 :       NS_DispatchToMainThread(NewRunnableMethod("ThrottledEventQueue::Inner::ShutdownComplete",
     199           0 :                                                 this, &Inner::ShutdownComplete));
     200             :     }
     201          13 :   }
     202             : 
     203             :   void
     204           0 :   ShutdownComplete()
     205             :   {
     206           0 :     MOZ_ASSERT(NS_IsMainThread());
     207           0 :     MOZ_ASSERT(IsEmpty());
     208           0 :     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     209           0 :     obs->RemoveObserver(this, kShutdownTopic);
     210           0 :   }
     211             : 
     212             : public:
     213             :   static already_AddRefed<Inner>
     214           5 :   Create(nsISerialEventTarget* aBaseTarget)
     215             :   {
     216           5 :     MOZ_ASSERT(NS_IsMainThread());
     217             : 
     218           5 :     if (ClearOnShutdown_Internal::sCurrentShutdownPhase != ShutdownPhase::NotInShutdown) {
     219           0 :       return nullptr;
     220             :     }
     221             : 
     222          10 :     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     223           5 :     if (NS_WARN_IF(!obs)) {
     224           0 :       return nullptr;
     225             :     }
     226             : 
     227          10 :     RefPtr<Inner> ref = new Inner(aBaseTarget);
     228             : 
     229           5 :     nsresult rv = obs->AddObserver(ref, kShutdownTopic,
     230           5 :                                    false /* means OS will hold a strong ref */);
     231           5 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     232           0 :       ref->MaybeStartShutdown();
     233           0 :       MOZ_ASSERT(ref->IsEmpty());
     234           0 :       return nullptr;
     235             :     }
     236             : 
     237           5 :     return ref.forget();
     238             :   }
     239             : 
     240             :   NS_IMETHOD
     241           0 :   Observe(nsISupports*, const char* aTopic, const char16_t*) override
     242             :   {
     243           0 :     MOZ_ASSERT(NS_IsMainThread());
     244           0 :     MOZ_ASSERT(!strcmp(aTopic, kShutdownTopic));
     245             : 
     246           0 :     MaybeStartShutdown();
     247             : 
     248             :     // Once shutdown begins we set the Atomic<bool> mShutdownStarted flag.
     249             :     // This prevents any new runnables from being dispatched into the
     250             :     // TaskQueue.  Therefore this loop should be finite.
     251           0 :     MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() -> bool {
     252             :         return IsEmpty();
     253             :     }));
     254             : 
     255           0 :     return NS_OK;
     256             :   }
     257             : 
     258             :   void
     259           0 :   MaybeStartShutdown()
     260             :   {
     261             :     // Any thread
     262           0 :     MutexAutoLock lock(mMutex);
     263             : 
     264           0 :     if (mShutdownStarted) {
     265           0 :       return;
     266             :     }
     267           0 :     mShutdownStarted = true;
     268             : 
     269             :     // We are marked for shutdown now, but we are still processing runnables.
     270             :     // Return for now.  The shutdown will be completed once the queue is
     271             :     // drained.
     272           0 :     if (mExecutor) {
     273           0 :       return;
     274             :     }
     275             : 
     276             :     // The queue is empty, so we can complete immediately.
     277           0 :     NS_DispatchToMainThread(NewRunnableMethod("ThrottledEventQueue::Inner::ShutdownComplete",
     278           0 :                                               this, &Inner::ShutdownComplete));
     279             :   }
     280             : 
     281             :   bool
     282           0 :   IsEmpty() const
     283             :   {
     284             :     // Any thread
     285           0 :     return Length() == 0;
     286             :   }
     287             : 
     288             :   uint32_t
     289           0 :   Length() const
     290             :   {
     291             :     // Any thread
     292           0 :     MutexAutoLock lock(mMutex);
     293           0 :     return mEventQueue.Count(lock);
     294             :   }
     295             : 
     296             :   void
     297           0 :   AwaitIdle() const
     298             :   {
     299             :     // Any thread, except the main thread or our base target.  Blocking the
     300             :     // main thread is forbidden.  Blocking the base target is guaranteed to
     301             :     // produce a deadlock.
     302           0 :     MOZ_ASSERT(!NS_IsMainThread());
     303             : #ifdef DEBUG
     304           0 :     bool onBaseTarget = false;
     305           0 :     Unused << mBaseTarget->IsOnCurrentThread(&onBaseTarget);
     306           0 :     MOZ_ASSERT(!onBaseTarget);
     307             : #endif
     308             : 
     309           0 :     MutexAutoLock lock(mMutex);
     310           0 :     while (mExecutor) {
     311           0 :       mIdleCondVar.Wait();
     312             :     }
     313           0 :   }
     314             : 
     315             :   nsresult
     316           0 :   DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
     317             :   {
     318             :     // Any thread
     319           0 :     nsCOMPtr<nsIRunnable> r = aEvent;
     320           0 :     return Dispatch(r.forget(), aFlags);
     321             :   }
     322             : 
     323             :   nsresult
     324          14 :   Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
     325             :   {
     326          14 :     MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL ||
     327             :                aFlags == NS_DISPATCH_AT_END);
     328             : 
     329             :     // Any thread
     330          28 :     MutexAutoLock lock(mMutex);
     331             : 
     332             :     // If we are shutting down, just fall back to our base target
     333             :     // directly.
     334          14 :     if (mShutdownStarted) {
     335           0 :       return mBaseTarget->Dispatch(Move(aEvent), aFlags);
     336             :     }
     337             : 
     338             :     // We are not currently processing events, so we must start
     339             :     // operating on our base target.  This is fallible, so do
     340             :     // it first.  Our lock will prevent the executor from accessing
     341             :     // the event queue before we add the event below.
     342          14 :     if (!mExecutor) {
     343             :       // Note, this creates a ref cycle keeping the inner alive
     344             :       // until the queue is drained.
     345          14 :       mExecutor = new Executor(this);
     346          14 :       nsresult rv = mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL);
     347          14 :       if (NS_WARN_IF(NS_FAILED(rv))) {
     348           0 :         mExecutor = nullptr;
     349           0 :         return rv;
     350             :       }
     351             :     }
     352             : 
     353             :     // Only add the event to the underlying queue if are able to
     354             :     // dispatch to our base target.
     355          14 :     mEventQueue.PutEvent(Move(aEvent), lock);
     356          14 :     return NS_OK;
     357             :   }
     358             : 
     359             :   nsresult
     360           0 :   DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelay)
     361             :   {
     362             :     // The base target may implement this, but we don't.  Always fail
     363             :     // to provide consistent behavior.
     364           0 :     return NS_ERROR_NOT_IMPLEMENTED;
     365             :   }
     366             : 
     367             :   bool
     368           0 :   IsOnCurrentThread()
     369             :   {
     370           0 :     return mBaseTarget->IsOnCurrentThread();
     371             :   }
     372             : 
     373             :   NS_DECL_THREADSAFE_ISUPPORTS
     374             : };
     375             : 
     376          37 : NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsIObserver);
     377             : 
     378          38 : NS_IMPL_ISUPPORTS(ThrottledEventQueue,
     379             :                   ThrottledEventQueue,
     380             :                   nsIEventTarget,
     381             :                   nsISerialEventTarget);
     382             : 
     383           5 : ThrottledEventQueue::ThrottledEventQueue(already_AddRefed<Inner> aInner)
     384           5 :   : mInner(aInner)
     385             : {
     386           5 :   MOZ_ASSERT(mInner);
     387           5 : }
     388             : 
     389           0 : ThrottledEventQueue::~ThrottledEventQueue()
     390             : {
     391           0 :   mInner->MaybeStartShutdown();
     392           0 : }
     393             : 
     394             : void
     395           0 : ThrottledEventQueue::MaybeStartShutdown()
     396             : {
     397           0 :   return mInner->MaybeStartShutdown();
     398             : }
     399             : 
     400             : already_AddRefed<ThrottledEventQueue>
     401           5 : ThrottledEventQueue::Create(nsISerialEventTarget* aBaseTarget)
     402             : {
     403           5 :   MOZ_ASSERT(NS_IsMainThread());
     404           5 :   MOZ_ASSERT(aBaseTarget);
     405             : 
     406          10 :   RefPtr<Inner> inner = Inner::Create(aBaseTarget);
     407           5 :   if (NS_WARN_IF(!inner)) {
     408           0 :     return nullptr;
     409             :   }
     410             : 
     411             :   RefPtr<ThrottledEventQueue> ref =
     412          15 :     new ThrottledEventQueue(inner.forget());
     413           5 :   return ref.forget();
     414             : }
     415             : 
     416             : bool
     417           0 : ThrottledEventQueue::IsEmpty() const
     418             : {
     419           0 :   return mInner->IsEmpty();
     420             : }
     421             : 
     422             : uint32_t
     423           0 : ThrottledEventQueue::Length() const
     424             : {
     425           0 :   return mInner->Length();
     426             : }
     427             : 
     428             : void
     429           0 : ThrottledEventQueue::AwaitIdle() const
     430             : {
     431           0 :   return mInner->AwaitIdle();
     432             : }
     433             : 
     434             : NS_IMETHODIMP
     435           0 : ThrottledEventQueue::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
     436             : {
     437           0 :   return mInner->DispatchFromScript(aEvent, aFlags);
     438             : }
     439             : 
     440             : NS_IMETHODIMP
     441          14 : ThrottledEventQueue::Dispatch(already_AddRefed<nsIRunnable> aEvent,
     442             :                                      uint32_t aFlags)
     443             : {
     444          14 :   return mInner->Dispatch(Move(aEvent), aFlags);
     445             : }
     446             : 
     447             : NS_IMETHODIMP
     448           0 : ThrottledEventQueue::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
     449             :                                             uint32_t aFlags)
     450             : {
     451           0 :   return mInner->DelayedDispatch(Move(aEvent), aFlags);
     452             : }
     453             : 
     454             : NS_IMETHODIMP
     455           0 : ThrottledEventQueue::IsOnCurrentThread(bool* aResult)
     456             : {
     457           0 :   *aResult = mInner->IsOnCurrentThread();
     458           0 :   return NS_OK;
     459             : }
     460             : 
     461             : NS_IMETHODIMP_(bool)
     462           0 : ThrottledEventQueue::IsOnCurrentThreadInfallible()
     463             : {
     464           0 :   return mInner->IsOnCurrentThread();
     465             : }
     466             : 
     467             : } // namespace mozilla

Generated by: LCOV version 1.13