forked from mirrors/gecko-dev
		
	 d5c301d0bc
			
		
	
	
		d5c301d0bc
		
	
	
	
	
		
			
			This will be used to allow the BackgroundEventTarget to safely hold weak references to active TaskQueues managed by it. Differential Revision: https://phabricator.services.mozilla.com/D142605
		
			
				
	
	
		
			359 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
	
		
			12 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 "mozilla/AbstractThread.h"
 | |
| 
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "mozilla/DelayedRunnable.h"
 | |
| #include "mozilla/Maybe.h"
 | |
| #include "mozilla/MozPromise.h"  // We initialize the MozPromise logging in this file.
 | |
| #include "mozilla/ProfilerRunnable.h"
 | |
| #include "mozilla/StateWatching.h"  // We initialize the StateWatching logging in this file.
 | |
| #include "mozilla/StaticPtr.h"
 | |
| #include "mozilla/TaskDispatcher.h"
 | |
| #include "mozilla/TaskQueue.h"
 | |
| #include "mozilla/Unused.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsIDirectTaskDispatcher.h"
 | |
| #include "nsIThreadInternal.h"
 | |
| #include "nsServiceManagerUtils.h"
 | |
| #include "nsThreadManager.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include <memory>
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| LazyLogModule gMozPromiseLog("MozPromise");
 | |
| LazyLogModule gStateWatchingLog("StateWatching");
 | |
| 
 | |
| StaticRefPtr<AbstractThread> sMainThread;
 | |
| MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;
 | |
| 
 | |
| class XPCOMThreadWrapper final : public AbstractThread,
 | |
|                                  public nsIThreadObserver,
 | |
|                                  public nsIDirectTaskDispatcher {
 | |
|  public:
 | |
|   XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch,
 | |
|                      bool aOnThread)
 | |
|       : AbstractThread(aRequireTailDispatch),
 | |
|         mThread(aThread),
 | |
|         mDirectTaskDispatcher(do_QueryInterface(aThread)),
 | |
|         mOnThread(aOnThread) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!aOnThread || IsCurrentThreadIn());
 | |
|     if (aOnThread) {
 | |
|       MOZ_ASSERT(!sCurrentThreadTLS.get(),
 | |
|                  "There can only be a single XPCOMThreadWrapper available on a "
 | |
|                  "thread");
 | |
|       // Set the default current thread so that GetCurrent() never returns
 | |
|       // nullptr.
 | |
|       sCurrentThreadTLS.set(this);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NS_DECL_THREADSAFE_ISUPPORTS
 | |
| 
 | |
|   nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
 | |
|                     DispatchReason aReason = NormalDispatch) override {
 | |
|     nsCOMPtr<nsIRunnable> r = aRunnable;
 | |
|     AbstractThread* currentThread;
 | |
|     if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
 | |
|         RequiresTailDispatch(currentThread) &&
 | |
|         currentThread->IsTailDispatcherAvailable()) {
 | |
|       return currentThread->TailDispatcher().AddTask(this, r.forget());
 | |
|     }
 | |
| 
 | |
|     // At a certain point during shutdown, we stop processing events from the
 | |
|     // main thread event queue (this happens long after all _other_ XPCOM
 | |
|     // threads have been shut down). However, various bits of subsequent
 | |
|     // teardown logic (the media shutdown blocker and the final shutdown cycle
 | |
|     // collection) can trigger state watching and state mirroring notifications
 | |
|     // that result in dispatch to the main thread. This causes shutdown leaks,
 | |
|     // because the |Runner| wrapper below creates a guaranteed cycle
 | |
|     // (Thread->EventQueue->Runnable->Thread) until the event is processed. So
 | |
|     // if we put the event into a queue that will never be processed, we'll wind
 | |
|     // up with a leak.
 | |
|     //
 | |
|     // We opt to just release the runnable in that case. Ordinarily, this
 | |
|     // approach could cause problems for runnables that are only safe to be
 | |
|     // released on the target thread (and not the dispatching thread). This is
 | |
|     // why XPCOM thread dispatch explicitly leaks the runnable when dispatch
 | |
|     // fails, rather than releasing it. But given that this condition only
 | |
|     // applies very late in shutdown when only one thread remains operational,
 | |
|     // that concern is unlikely to apply.
 | |
|     if (gXPCOMMainThreadEventsAreDoomed) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     RefPtr<nsIRunnable> runner = new Runner(this, r.forget());
 | |
|     return mThread->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
 | |
|   }
 | |
| 
 | |
|   // Prevent a GCC warning about the other overload of Dispatch being hidden.
 | |
|   using AbstractThread::Dispatch;
 | |
| 
 | |
|   NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override {
 | |
|     return mThread->RegisterShutdownTask(aTask);
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override {
 | |
|     return mThread->UnregisterShutdownTask(aTask);
 | |
|   }
 | |
| 
 | |
|   bool IsCurrentThreadIn() const override {
 | |
|     return mThread->IsOnCurrentThread();
 | |
|   }
 | |
| 
 | |
|   TaskDispatcher& TailDispatcher() override {
 | |
|     MOZ_ASSERT(IsCurrentThreadIn());
 | |
|     MOZ_ASSERT(IsTailDispatcherAvailable());
 | |
|     if (!mTailDispatcher) {
 | |
|       mTailDispatcher =
 | |
|           std::make_unique<AutoTaskDispatcher>(mDirectTaskDispatcher,
 | |
|                                                /* aIsTailDispatcher = */ true);
 | |
|       mThread->AddObserver(this);
 | |
|     }
 | |
| 
 | |
|     return *mTailDispatcher;
 | |
|   }
 | |
| 
 | |
|   bool IsTailDispatcherAvailable() override {
 | |
|     // Our tail dispatching implementation relies on nsIThreadObserver
 | |
|     // callbacks. If we're not doing event processing, it won't work.
 | |
|     bool inEventLoop =
 | |
|         static_cast<nsThread*>(mThread.get())->RecursionDepth() > 0;
 | |
|     return inEventLoop;
 | |
|   }
 | |
| 
 | |
|   bool MightHaveTailTasks() override { return !!mTailDispatcher; }
 | |
| 
 | |
|   nsIEventTarget* AsEventTarget() override { return mThread; }
 | |
| 
 | |
|   //-----------------------------------------------------------------------------
 | |
|   // nsIThreadObserver
 | |
|   //-----------------------------------------------------------------------------
 | |
|   NS_IMETHOD OnDispatchedEvent() override { return NS_OK; }
 | |
| 
 | |
|   NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread,
 | |
|                                    bool eventWasProcessed) override {
 | |
|     // This is the primary case.
 | |
|     MaybeFireTailDispatcher();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD OnProcessNextEvent(nsIThreadInternal* thread,
 | |
|                                 bool mayWait) override {
 | |
|     // In general, the tail dispatcher is handled at the end of the current in
 | |
|     // AfterProcessNextEvent() above. However, if start spinning a nested event
 | |
|     // loop, it's generally better to fire the tail dispatcher before the first
 | |
|     // nested event, rather than after it. This check handles that case.
 | |
|     MaybeFireTailDispatcher();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   //-----------------------------------------------------------------------------
 | |
|   // nsIDirectTaskDispatcher
 | |
|   //-----------------------------------------------------------------------------
 | |
|   // Forward calls to nsIDirectTaskDispatcher to the underlying nsThread object.
 | |
|   // We can't use the generated NS_FORWARD_NSIDIRECTTASKDISPATCHER macro
 | |
|   // as already_AddRefed type must be moved.
 | |
|   NS_IMETHOD DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) override {
 | |
|     return mDirectTaskDispatcher->DispatchDirectTask(std::move(aEvent));
 | |
|   }
 | |
|   NS_IMETHOD DrainDirectTasks() override {
 | |
|     return mDirectTaskDispatcher->DrainDirectTasks();
 | |
|   }
 | |
|   NS_IMETHOD HaveDirectTasks(bool* aResult) override {
 | |
|     return mDirectTaskDispatcher->HaveDirectTasks(aResult);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const RefPtr<nsIThreadInternal> mThread;
 | |
|   const nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher;
 | |
|   std::unique_ptr<AutoTaskDispatcher> mTailDispatcher;
 | |
|   const bool mOnThread;
 | |
| 
 | |
|   ~XPCOMThreadWrapper() {
 | |
|     if (mOnThread) {
 | |
|       MOZ_DIAGNOSTIC_ASSERT(IsCurrentThreadIn(),
 | |
|                             "Must be destroyed on the thread it was created");
 | |
|       sCurrentThreadTLS.set(nullptr);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void MaybeFireTailDispatcher() {
 | |
|     if (mTailDispatcher) {
 | |
|       mTailDispatcher->DrainDirectTasks();
 | |
|       mThread->RemoveObserver(this);
 | |
|       mTailDispatcher.reset();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   class Runner : public Runnable {
 | |
|    public:
 | |
|     explicit Runner(XPCOMThreadWrapper* aThread,
 | |
|                     already_AddRefed<nsIRunnable> aRunnable)
 | |
|         : Runnable("XPCOMThreadWrapper::Runner"),
 | |
|           mThread(aThread),
 | |
|           mRunnable(aRunnable) {}
 | |
| 
 | |
|     NS_IMETHOD Run() override {
 | |
|       MOZ_ASSERT(mThread == AbstractThread::GetCurrent());
 | |
|       MOZ_ASSERT(mThread->IsCurrentThreadIn());
 | |
|       SerialEventTargetGuard guard(mThread);
 | |
|       AUTO_PROFILE_FOLLOWING_RUNNABLE(mRunnable);
 | |
|       return mRunnable->Run();
 | |
|     }
 | |
| 
 | |
| #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
 | |
|     NS_IMETHOD GetName(nsACString& aName) override {
 | |
|       aName.AssignLiteral("AbstractThread::Runner");
 | |
|       if (nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable)) {
 | |
|         nsAutoCString name;
 | |
|         named->GetName(name);
 | |
|         if (!name.IsEmpty()) {
 | |
|           aName.AppendLiteral(" for ");
 | |
|           aName.Append(name);
 | |
|         }
 | |
|       }
 | |
|       return NS_OK;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|    private:
 | |
|     const RefPtr<XPCOMThreadWrapper> mThread;
 | |
|     const RefPtr<nsIRunnable> mRunnable;
 | |
|   };
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(XPCOMThreadWrapper, nsIThreadObserver,
 | |
|                   nsIDirectTaskDispatcher, nsISerialEventTarget, nsIEventTarget)
 | |
| 
 | |
| NS_IMETHODIMP_(bool)
 | |
| AbstractThread::IsOnCurrentThreadInfallible() { return IsCurrentThreadIn(); }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AbstractThread::IsOnCurrentThread(bool* aResult) {
 | |
|   *aResult = IsCurrentThreadIn();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AbstractThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
 | |
|   nsCOMPtr<nsIRunnable> event(aEvent);
 | |
|   return Dispatch(event.forget(), aFlags);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AbstractThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
 | |
|                          uint32_t aFlags) {
 | |
|   return Dispatch(std::move(aEvent), NormalDispatch);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AbstractThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
 | |
|                                 uint32_t aDelayMs) {
 | |
|   nsCOMPtr<nsIRunnable> event = aEvent;
 | |
|   NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|   RefPtr<DelayedRunnable> r =
 | |
|       new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs);
 | |
|   nsresult rv = r->Init();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return Dispatch(r.forget(), NS_DISPATCH_NORMAL);
 | |
| }
 | |
| 
 | |
| nsresult AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) {
 | |
|   if (MightHaveTailTasks()) {
 | |
|     return TailDispatcher().DispatchTasksFor(aThread);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool AbstractThread::HasTailTasksFor(AbstractThread* aThread) {
 | |
|   if (!MightHaveTailTasks()) {
 | |
|     return false;
 | |
|   }
 | |
|   return TailDispatcher().HasTasksFor(aThread);
 | |
| }
 | |
| 
 | |
| bool AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const {
 | |
|   MOZ_ASSERT(aThread);
 | |
|   // We require tail dispatch if both the source and destination
 | |
|   // threads support it.
 | |
|   return SupportsTailDispatch() && aThread->SupportsTailDispatch();
 | |
| }
 | |
| 
 | |
| bool AbstractThread::RequiresTailDispatchFromCurrentThread() const {
 | |
|   AbstractThread* current = GetCurrent();
 | |
|   return current && RequiresTailDispatch(current);
 | |
| }
 | |
| 
 | |
| AbstractThread* AbstractThread::MainThread() {
 | |
|   MOZ_ASSERT(sMainThread);
 | |
|   return sMainThread;
 | |
| }
 | |
| 
 | |
| void AbstractThread::InitTLS() {
 | |
|   if (!sCurrentThreadTLS.init()) {
 | |
|     MOZ_CRASH();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void AbstractThread::InitMainThread() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(!sMainThread);
 | |
|   nsCOMPtr<nsIThreadInternal> mainThread =
 | |
|       do_QueryInterface(nsThreadManager::get().GetMainThreadWeak());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mainThread);
 | |
| 
 | |
|   if (!sCurrentThreadTLS.init()) {
 | |
|     MOZ_CRASH();
 | |
|   }
 | |
|   sMainThread = new XPCOMThreadWrapper(mainThread.get(),
 | |
|                                        /* aRequireTailDispatch = */ true,
 | |
|                                        true /* onThread */);
 | |
| }
 | |
| 
 | |
| void AbstractThread::ShutdownMainThread() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   sMainThread = nullptr;
 | |
| }
 | |
| 
 | |
| void AbstractThread::DispatchStateChange(
 | |
|     already_AddRefed<nsIRunnable> aRunnable) {
 | |
|   AbstractThread* currentThread = GetCurrent();
 | |
|   MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
 | |
|   if (currentThread->IsTailDispatcherAvailable()) {
 | |
|     currentThread->TailDispatcher().AddStateChangeTask(this,
 | |
|                                                        std::move(aRunnable));
 | |
|   } else {
 | |
|     // If the tail dispatcher isn't available, we just avoid sending state
 | |
|     // updates.
 | |
|     //
 | |
|     // This happens, specifically (1) During async shutdown (via the media
 | |
|     // shutdown blocker), and (2) During the final shutdown cycle collection.
 | |
|     // Both of these trigger changes to various watched and mirrored state.
 | |
|     nsCOMPtr<nsIRunnable> neverDispatched = aRunnable;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void AbstractThread::DispatchDirectTask(
 | |
|     already_AddRefed<nsIRunnable> aRunnable) {
 | |
|   AbstractThread* currentThread = GetCurrent();
 | |
|   MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
 | |
|   if (currentThread->IsTailDispatcherAvailable()) {
 | |
|     currentThread->TailDispatcher().AddDirectTask(std::move(aRunnable));
 | |
|   } else {
 | |
|     // If the tail dispatcher isn't available, we post as a regular task.
 | |
|     currentThread->Dispatch(std::move(aRunnable));
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 |