/* -*- 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 namespace mozilla { LazyLogModule gMozPromiseLog("MozPromise"); LazyLogModule gStateWatchingLog("StateWatching"); StaticRefPtr 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 aRunnable, DispatchReason aReason = NormalDispatch) override { nsCOMPtr 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 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(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(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 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 mThread; const nsCOMPtr mDirectTaskDispatcher; std::unique_ptr 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 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 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 mThread; const RefPtr 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 event(aEvent); return Dispatch(event.forget(), aFlags); } NS_IMETHODIMP AbstractThread::Dispatch(already_AddRefed aEvent, uint32_t aFlags) { return Dispatch(std::move(aEvent), NormalDispatch); } NS_IMETHODIMP AbstractThread::DelayedDispatch(already_AddRefed aEvent, uint32_t aDelayMs) { nsCOMPtr event = aEvent; NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED); RefPtr 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 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 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 neverDispatched = aRunnable; } } /* static */ void AbstractThread::DispatchDirectTask( already_AddRefed 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