diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /xpcom/threads | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/threads')
103 files changed, 24904 insertions, 0 deletions
diff --git a/xpcom/threads/AbstractThread.cpp b/xpcom/threads/AbstractThread.cpp new file mode 100644 index 0000000000..796b1539e9 --- /dev/null +++ b/xpcom/threads/AbstractThread.cpp @@ -0,0 +1,380 @@ +/* -*- 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/Maybe.h" +#include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file. +#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" +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_ISUPPORTS_INHERITED + + 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; + + bool IsCurrentThreadIn() const override { + return mThread->IsOnCurrentThread(); + } + + TaskDispatcher& TailDispatcher() override { + MOZ_ASSERT(IsCurrentThreadIn()); + MOZ_ASSERT(IsTailDispatcherAvailable()); + if (!mTailDispatcher.isSome()) { + mTailDispatcher.emplace(mDirectTaskDispatcher, + /* aIsTailDispatcher = */ true); + mThread->AddObserver(this); + } + + return mTailDispatcher.ref(); + } + + 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.isSome(); } + + 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; + Maybe<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.isSome()) { + mTailDispatcher.ref().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); + 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_INHERITED(XPCOMThreadWrapper, AbstractThread, + nsIThreadObserver, nsIDirectTaskDispatcher); + +NS_IMPL_ISUPPORTS(AbstractThread, nsIEventTarget, nsISerialEventTarget) + +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)); + } +} + +/* static */ +already_AddRefed<AbstractThread> AbstractThread::CreateXPCOMThreadWrapper( + nsIThread* aThread, bool aRequireTailDispatch, bool aOnThread) { + nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(aThread); + MOZ_ASSERT(internalThread, "Need an nsThread for AbstractThread"); + RefPtr<XPCOMThreadWrapper> wrapper = + new XPCOMThreadWrapper(internalThread, aRequireTailDispatch, aOnThread); + + bool onCurrentThread = false; + Unused << aThread->IsOnCurrentThread(&onCurrentThread); + + if (onCurrentThread) { + if (!aOnThread) { + MOZ_ASSERT(!sCurrentThreadTLS.get(), + "There can only be a single XPCOMThreadWrapper available on a " + "thread"); + sCurrentThreadTLS.set(wrapper); + } + return wrapper.forget(); + } + + // Set the thread-local sCurrentThreadTLS to point to the wrapper on the + // target thread. This ensures that sCurrentThreadTLS is as expected by + // AbstractThread::GetCurrent() on the target thread. + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "AbstractThread::CreateXPCOMThreadWrapper", [wrapper]() { + MOZ_ASSERT(!sCurrentThreadTLS.get(), + "There can only be a single XPCOMThreadWrapper available on " + "a thread"); + sCurrentThreadTLS.set(wrapper); + }); + aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + return wrapper.forget(); +} +} // namespace mozilla diff --git a/xpcom/threads/AbstractThread.h b/xpcom/threads/AbstractThread.h new file mode 100644 index 0000000000..ef339986dd --- /dev/null +++ b/xpcom/threads/AbstractThread.h @@ -0,0 +1,134 @@ +/* -*- 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/. */ + +#if !defined(AbstractThread_h_) +# define AbstractThread_h_ + +# include "mozilla/AlreadyAddRefed.h" +# include "mozilla/ThreadLocal.h" +# include "nscore.h" +# include "nsISerialEventTarget.h" +# include "nsISupports.h" + +class nsIEventTarget; +class nsIRunnable; +class nsIThread; + +namespace mozilla { + +class TaskDispatcher; + +/* + * We often want to run tasks on a target that guarantees that events will never + * run in parallel. There are various target types that achieve this - namely + * nsIThread and TaskQueue. Note that nsIThreadPool (which implements + * nsIEventTarget) does not have this property, so we do not want to use + * nsIEventTarget for this purpose. This class encapsulates the specifics of + * the structures we might use here and provides a consistent interface. + * + * At present, the supported AbstractThread implementations are TaskQueue, + * AbstractThread::MainThread() and XPCOMThreadWrapper which can wrap any + * nsThread. + * + * The primary use of XPCOMThreadWrapper is to allow any threads to provide + * Direct Task dispatching which is similar (but not identical to) the microtask + * semantics of JS promises. Instantiating a XPCOMThreadWrapper on the current + * nsThread is sufficient to enable direct task dispatching. + * + * You shouldn't use pointers when comparing AbstractThread or nsIThread to + * determine if you are currently on the thread, but instead use the + * nsISerialEventTarget::IsOnCurrentThread() method. + */ +class AbstractThread : public nsISerialEventTarget { + public: + // Returns the AbstractThread that the caller is currently running in, or null + // if the caller is not running in an AbstractThread. + static AbstractThread* GetCurrent() { return sCurrentThreadTLS.get(); } + + AbstractThread(bool aSupportsTailDispatch) + : mSupportsTailDispatch(aSupportsTailDispatch) {} + + // Returns an AbstractThread wrapper of a nsIThread. + static already_AddRefed<AbstractThread> CreateXPCOMThreadWrapper( + nsIThread* aThread, bool aRequireTailDispatch, bool aOnThread = false); + + NS_DECL_THREADSAFE_ISUPPORTS + + // We don't use NS_DECL_NSIEVENTTARGET so that we can remove the default + // |flags| parameter from Dispatch. Otherwise, a single-argument Dispatch call + // would be ambiguous. + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override; + NS_IMETHOD IsOnCurrentThread(bool* _retval) override; + NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event, + uint32_t flags) override; + NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override; + NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable> event, + uint32_t delay) override; + + enum DispatchReason { NormalDispatch, TailDispatch }; + virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable, + DispatchReason aReason = NormalDispatch) = 0; + + virtual bool IsCurrentThreadIn() const = 0; + + // Returns a TaskDispatcher that will dispatch its tasks when the currently- + // running tasks pops off the stack. + // + // May only be called when running within the it is invoked up, and only on + // threads which support it. + virtual TaskDispatcher& TailDispatcher() = 0; + + // Returns true if we have tail tasks scheduled, or if this isn't known. + // Returns false if we definitely don't have any tail tasks. + virtual bool MightHaveTailTasks() { return true; } + + // Returns true if the tail dispatcher is available. In certain edge cases + // like shutdown, it might not be. + virtual bool IsTailDispatcherAvailable() { return true; } + + // Helper functions for methods on the tail TasklDispatcher. These check + // HasTailTasks to avoid allocating a TailDispatcher if it isn't + // needed. + nsresult TailDispatchTasksFor(AbstractThread* aThread); + bool HasTailTasksFor(AbstractThread* aThread); + + // Returns true if this supports the tail dispatcher. + bool SupportsTailDispatch() const { return mSupportsTailDispatch; } + + // Returns true if this thread requires all dispatches originating from + // aThread go through the tail dispatcher. + bool RequiresTailDispatch(AbstractThread* aThread) const; + bool RequiresTailDispatchFromCurrentThread() const; + + virtual nsIEventTarget* AsEventTarget() { MOZ_CRASH("Not an event target!"); } + + // Returns the non-DocGroup version of AbstractThread on the main thread. + // A DocGroup-versioned one is available in + // DispatcherTrait::AbstractThreadFor(). Note: + // DispatcherTrait::AbstractThreadFor() SHALL be used when possible. + static AbstractThread* MainThread(); + + // Must be called exactly once during startup. + static void InitTLS(); + static void InitMainThread(); + static void ShutdownMainThread(); + + void DispatchStateChange(already_AddRefed<nsIRunnable> aRunnable); + + static void DispatchDirectTask(already_AddRefed<nsIRunnable> aRunnable); + + protected: + virtual ~AbstractThread() = default; + static MOZ_THREAD_LOCAL(AbstractThread*) sCurrentThreadTLS; + + // True if we want to require that every task dispatched from tasks running in + // this queue go through our queue's tail dispatcher. + const bool mSupportsTailDispatch; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/BlockingResourceBase.cpp b/xpcom/threads/BlockingResourceBase.cpp new file mode 100644 index 0000000000..218f0cf4b1 --- /dev/null +++ b/xpcom/threads/BlockingResourceBase.cpp @@ -0,0 +1,542 @@ +/* -*- 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/BlockingResourceBase.h" + +#ifdef DEBUG +# include "prthread.h" + +# ifndef MOZ_CALLSTACK_DISABLED +# include "CodeAddressService.h" +# include "nsHashKeys.h" +# include "mozilla/StackWalk.h" +# include "nsTHashtable.h" +# endif + +# include "mozilla/CondVar.h" +# include "mozilla/DeadlockDetector.h" +# include "mozilla/RecursiveMutex.h" +# include "mozilla/ReentrantMonitor.h" +# include "mozilla/Mutex.h" +# include "mozilla/RWLock.h" +# include "mozilla/UniquePtr.h" + +# if defined(MOZILLA_INTERNAL_API) +# include "GeckoProfiler.h" +# endif // MOZILLA_INTERNAL_API + +#endif // ifdef DEBUG + +namespace mozilla { +// +// BlockingResourceBase implementation +// + +// static members +const char* const BlockingResourceBase::kResourceTypeName[] = { + // needs to be kept in sync with BlockingResourceType + "Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"}; + +#ifdef DEBUG + +PRCallOnceType BlockingResourceBase::sCallOnce; +MOZ_THREAD_LOCAL(BlockingResourceBase*) +BlockingResourceBase::sResourceAcqnChainFront; +BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector; + +void BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc, + void* aSp, void* aClosure) { +# ifndef MOZ_CALLSTACK_DISABLED + AcquisitionState* state = (AcquisitionState*)aClosure; + state->ref().AppendElement(aPc); +# endif +} + +void BlockingResourceBase::GetStackTrace(AcquisitionState& aState) { +# ifndef MOZ_CALLSTACK_DISABLED + // Skip this function and the calling function. + const uint32_t kSkipFrames = 2; + + // Clear the array... + aState.reset(); + // ...and create a new one; this also puts the state to 'acquired' status + // regardless of whether we obtain a stack trace or not. + aState.emplace(); + + // NB: Ignore the return value, there's nothing useful we can do if this + // this fails. + MozStackWalk(StackWalkCallback, kSkipFrames, kAcquisitionStateStackSize, + aState.ptr()); +# endif +} + +/** + * PrintCycle + * Append to |aOut| detailed information about the circular + * dependency in |aCycle|. Returns true if it *appears* that this + * cycle may represent an imminent deadlock, but this is merely a + * heuristic; the value returned may be a false positive or false + * negative. + * + * *NOT* thread safe. Calls |Print()|. + * + * FIXME bug 456272 hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but only + * some info is written into |aOut| + */ +static bool PrintCycle( + const BlockingResourceBase::DDT::ResourceAcquisitionArray& aCycle, + nsACString& aOut) { + NS_ASSERTION(aCycle.Length() > 1, "need > 1 element for cycle!"); + + bool maybeImminent = true; + + fputs("=== Cyclical dependency starts at\n", stderr); + aOut += "Cyclical dependency starts at\n"; + + const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type res = + aCycle.ElementAt(0); + maybeImminent &= res->Print(aOut); + + BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i; + BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len = + aCycle.Length(); + const BlockingResourceBase::DDT::ResourceAcquisitionArray::elem_type* it = + 1 + aCycle.Elements(); + for (i = 1; i < len - 1; ++i, ++it) { + fputs("\n--- Next dependency:\n", stderr); + aOut += "\nNext dependency:\n"; + + maybeImminent &= (*it)->Print(aOut); + } + + fputs("\n=== Cycle completed at\n", stderr); + aOut += "Cycle completed at\n"; + (*it)->Print(aOut); + + return maybeImminent; +} + +bool BlockingResourceBase::Print(nsACString& aOut) const { + fprintf(stderr, "--- %s : %s", kResourceTypeName[mType], mName); + aOut += BlockingResourceBase::kResourceTypeName[mType]; + aOut += " : "; + aOut += mName; + + bool acquired = IsAcquired(); + + if (acquired) { + fputs(" (currently acquired)\n", stderr); + aOut += " (currently acquired)\n"; + } + + fputs(" calling context\n", stderr); +# ifdef MOZ_CALLSTACK_DISABLED + fputs(" [stack trace unavailable]\n", stderr); +# else + const AcquisitionState& state = acquired ? mAcquired : mFirstSeen; + + CodeAddressService<> addressService; + + for (uint32_t i = 0; i < state.ref().Length(); i++) { + const size_t kMaxLength = 1024; + char buffer[kMaxLength]; + addressService.GetLocation(i + 1, state.ref()[i], buffer, kMaxLength); + const char* fmt = " %s\n"; + aOut.AppendLiteral(" "); + aOut.Append(buffer); + aOut.AppendLiteral("\n"); + fprintf(stderr, fmt, buffer); + } + +# endif + + return acquired; +} + +BlockingResourceBase::BlockingResourceBase( + const char* aName, BlockingResourceBase::BlockingResourceType aType) + : mName(aName), + mType(aType) +# ifdef MOZ_CALLSTACK_DISABLED + , + mAcquired(false) +# else + , + mAcquired() +# endif +{ + MOZ_ASSERT(mName, "Name must be nonnull"); + // PR_CallOnce guaranatees that InitStatics is called in a + // thread-safe way + if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) { + MOZ_CRASH("can't initialize blocking resource static members"); + } + + mChainPrev = 0; + sDeadlockDetector->Add(this); +} + +BlockingResourceBase::~BlockingResourceBase() { + // we don't check for really obviously bad things like freeing + // Mutexes while they're still locked. it is assumed that the + // base class, or its underlying primitive, will check for such + // stupid mistakes. + mChainPrev = 0; // racy only for stupidly buggy client code + if (sDeadlockDetector) { + sDeadlockDetector->Remove(this); + } +} + +size_t BlockingResourceBase::SizeOfDeadlockDetector( + MallocSizeOf aMallocSizeOf) { + return sDeadlockDetector + ? sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf) + : 0; +} + +PRStatus BlockingResourceBase::InitStatics() { + MOZ_ASSERT(sResourceAcqnChainFront.init()); + sDeadlockDetector = new DDT(); + if (!sDeadlockDetector) { + MOZ_CRASH("can't allocate deadlock detector"); + } + return PR_SUCCESS; +} + +void BlockingResourceBase::Shutdown() { + delete sDeadlockDetector; + sDeadlockDetector = 0; +} + +void BlockingResourceBase::CheckAcquire() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + mozilla::UniquePtr<DDT::ResourceAcquisitionArray> cycle( + sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this)); + if (!cycle) { + return; + } + +# ifndef MOZ_CALLSTACK_DISABLED + // Update the current stack before printing. + GetStackTrace(mAcquired); +# endif + + fputs("###!!! ERROR: Potential deadlock detected:\n", stderr); + nsAutoCString out("Potential deadlock detected:\n"); + bool maybeImminent = PrintCycle(*cycle, out); + + if (maybeImminent) { + fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr); + out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n"); + } else { + fputs("\nDeadlock may happen for some other execution\n\n", stderr); + out.AppendLiteral("\nDeadlock may happen for some other execution\n\n"); + } + + // Only error out if we think a deadlock is imminent. + if (maybeImminent) { + NS_ERROR(out.get()); + } else { + NS_WARNING(out.get()); + } +} + +void BlockingResourceBase::Acquire() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow Acquire()ing condvars"); + return; + } + NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource"); + + ResourceChainAppend(ResourceChainFront()); + +# ifdef MOZ_CALLSTACK_DISABLED + mAcquired = true; +# else + // Take a stack snapshot. + GetStackTrace(mAcquired); + MOZ_ASSERT(IsAcquired()); + + if (!mFirstSeen) { + mFirstSeen = mAcquired; + } +# endif +} + +void BlockingResourceBase::Release() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow Release()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + NS_ASSERTION(chainFront && IsAcquired(), + "Release()ing something that hasn't been Acquire()ed"); + + if (chainFront == this) { + ResourceChainRemove(); + } else { + // not an error, but makes code hard to reason about. + NS_WARNING("Resource acquired is being released in non-LIFO order; why?\n"); + nsCString tmp; + Print(tmp); + + // remove this resource from wherever it lives in the chain + // we walk backwards in order of acquisition: + // (1) ...node<-prev<-curr... + // / / + // (2) ...prev<-curr... + BlockingResourceBase* curr = chainFront; + BlockingResourceBase* prev = nullptr; + while (curr && (prev = curr->mChainPrev) && (prev != this)) { + curr = prev; + } + if (prev == this) { + curr->mChainPrev = prev->mChainPrev; + } + } + + ClearAcquisitionState(); +} + +// +// Debug implementation of (OffTheBooks)Mutex +void OffTheBooksMutex::Lock() { + CheckAcquire(); + this->lock(); + mOwningThread = PR_GetCurrentThread(); + Acquire(); +} + +bool OffTheBooksMutex::TryLock() { + CheckAcquire(); + bool locked = this->tryLock(); + if (locked) { + mOwningThread = PR_GetCurrentThread(); + Acquire(); + } + return locked; +} + +void OffTheBooksMutex::Unlock() { + Release(); + mOwningThread = nullptr; + this->unlock(); +} + +void OffTheBooksMutex::AssertCurrentThreadOwns() const { + MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread()); +} + +// +// Debug implementation of RWLock +// + +void RWLock::ReadLock() { + // All we want to ensure here is that we're not attempting to acquire the + // read lock while this thread is holding the write lock. + CheckAcquire(); + this->ReadLockInternal(); + MOZ_ASSERT(mOwningThread == nullptr); +} + +void RWLock::ReadUnlock() { + MOZ_ASSERT(mOwningThread == nullptr); + this->ReadUnlockInternal(); +} + +void RWLock::WriteLock() { + CheckAcquire(); + this->WriteLockInternal(); + mOwningThread = PR_GetCurrentThread(); + Acquire(); +} + +void RWLock::WriteUnlock() { + Release(); + mOwningThread = nullptr; + this->WriteUnlockInternal(); +} + +// +// Debug implementation of ReentrantMonitor +void ReentrantMonitor::Enter() { + BlockingResourceBase* chainFront = ResourceChainFront(); + + // the code below implements monitor reentrancy semantics + + if (this == chainFront) { + // immediately re-entered the monitor: acceptable + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + + // this is sort of a hack around not recording the thread that + // owns this monitor + if (chainFront) { + for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br; + br = ResourceChainPrev(br)) { + if (br == this) { + NS_WARNING( + "Re-entering ReentrantMonitor after acquiring other resources."); + + // show the caller why this is potentially bad + CheckAcquire(); + + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(); + PR_EnterMonitor(mReentrantMonitor); + NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!"); + Acquire(); // protected by mReentrantMonitor + mEntryCount = 1; +} + +void ReentrantMonitor::Exit() { + if (--mEntryCount == 0) { + Release(); // protected by mReentrantMonitor + } + PRStatus status = PR_ExitMonitor(mReentrantMonitor); + NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()"); +} + +nsresult ReentrantMonitor::Wait(PRIntervalTime aInterval) { + AssertCurrentThreadIn(); + + // save monitor state and reset it to empty + int32_t savedEntryCount = mEntryCount; + AcquisitionState savedAcquisitionState = GetAcquisitionState(); + BlockingResourceBase* savedChainPrev = mChainPrev; + mEntryCount = 0; + ClearAcquisitionState(); + mChainPrev = 0; + + nsresult rv; + { +# if defined(MOZILLA_INTERNAL_API) + AUTO_PROFILER_THREAD_SLEEP; +# endif + // give up the monitor until we're back from Wait() + rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + + // restore saved state + mEntryCount = savedEntryCount; + SetAcquisitionState(savedAcquisitionState); + mChainPrev = savedChainPrev; + + return rv; +} + +// +// Debug implementation of RecursiveMutex +void RecursiveMutex::Lock() { + BlockingResourceBase* chainFront = ResourceChainFront(); + + // the code below implements mutex reentrancy semantics + + if (this == chainFront) { + // immediately re-entered the mutex: acceptable + LockInternal(); + ++mEntryCount; + return; + } + + // this is sort of a hack around not recording the thread that + // owns this monitor + if (chainFront) { + for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br; + br = ResourceChainPrev(br)) { + if (br == this) { + NS_WARNING( + "Re-entering RecursiveMutex after acquiring other resources."); + + // show the caller why this is potentially bad + CheckAcquire(); + + LockInternal(); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(); + LockInternal(); + NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!"); + Acquire(); // protected by us + mOwningThread = PR_GetCurrentThread(); + mEntryCount = 1; +} + +void RecursiveMutex::Unlock() { + if (--mEntryCount == 0) { + Release(); // protected by us + mOwningThread = nullptr; + } + UnlockInternal(); +} + +void RecursiveMutex::AssertCurrentThreadIn() { + MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread()); +} + +// +// Debug implementation of CondVar +void OffTheBooksCondVar::Wait() { + // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code + // duplication. + CVStatus status = Wait(TimeDuration::Forever()); + MOZ_ASSERT(status == CVStatus::NoTimeout); +} + +CVStatus OffTheBooksCondVar::Wait(TimeDuration aDuration) { + AssertCurrentThreadOwnsMutex(); + + // save mutex state and reset to empty + AcquisitionState savedAcquisitionState = mLock->GetAcquisitionState(); + BlockingResourceBase* savedChainPrev = mLock->mChainPrev; + PRThread* savedOwningThread = mLock->mOwningThread; + mLock->ClearAcquisitionState(); + mLock->mChainPrev = 0; + mLock->mOwningThread = nullptr; + + // give up mutex until we're back from Wait() + CVStatus status; + { +# if defined(MOZILLA_INTERNAL_API) + AUTO_PROFILER_THREAD_SLEEP; +# endif + status = mImpl.wait_for(*mLock, aDuration); + } + + // restore saved state + mLock->SetAcquisitionState(savedAcquisitionState); + mLock->mChainPrev = savedChainPrev; + mLock->mOwningThread = savedOwningThread; + + return status; +} + +#endif // ifdef DEBUG + +} // namespace mozilla diff --git a/xpcom/threads/BlockingResourceBase.h b/xpcom/threads/BlockingResourceBase.h new file mode 100644 index 0000000000..b5501599f1 --- /dev/null +++ b/xpcom/threads/BlockingResourceBase.h @@ -0,0 +1,328 @@ +/* -*- 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/. */ + +#ifndef mozilla_BlockingResourceBase_h +#define mozilla_BlockingResourceBase_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/ThreadLocal.h" + +#include "nscore.h" +#include "nsDebug.h" + +#include "prtypes.h" + +#ifdef DEBUG + +// NB: Comment this out to enable callstack tracking. +# define MOZ_CALLSTACK_DISABLED + +# include "prinit.h" + +# ifndef MOZ_CALLSTACK_DISABLED +# include "mozilla/Maybe.h" +# include "nsTArray.h" +# endif + +#endif + +// +// This header is not meant to be included by client code. +// + +namespace mozilla { + +#ifdef DEBUG +template <class T> +class DeadlockDetector; +#endif + +/** + * BlockingResourceBase + * Base class of resources that might block clients trying to acquire them. + * Does debugging and deadlock detection in DEBUG builds. + **/ +class BlockingResourceBase { + public: + // Needs to be kept in sync with kResourceTypeNames. + enum BlockingResourceType { + eMutex, + eReentrantMonitor, + eCondVar, + eRecursiveMutex + }; + + /** + * kResourceTypeName + * Human-readable version of BlockingResourceType enum. + */ + static const char* const kResourceTypeName[]; + +#ifdef DEBUG + + static size_t SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf); + + /** + * Print + * Write a description of this blocking resource to |aOut|. If + * the resource appears to be currently acquired, the current + * acquisition context is printed and true is returned. + * Otherwise, we print the context from |aFirstSeen|, the + * first acquisition from which the code calling |Print()| + * became interested in us, and return false. + * + * *NOT* thread safe. Reads |mAcquisitionContext| without + * synchronization, but this will not cause correctness + * problems. + * + * FIXME bug 456272: hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but + * only some info is written into |aOut| + */ + bool Print(nsACString& aOut) const; + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + // NB: |mName| is not reported as it's expected to be a static string. + // If we switch to a nsString it should be added to the tally. + // |mChainPrev| is not reported because its memory is not owned. + size_t n = aMallocSizeOf(this); + return n; + } + + // ``DDT'' = ``Deadlock Detector Type'' + typedef DeadlockDetector<BlockingResourceBase> DDT; + + protected: +# ifdef MOZ_CALLSTACK_DISABLED + typedef bool AcquisitionState; +# else + // Using maybe to use emplacement as the acquisition state flag; we may not + // always get a stack trace because of possible stack walk suppression or + // errors, hence can't use !IsEmpty() on the array itself as indication. + static size_t const kAcquisitionStateStackSize = 24; + typedef Maybe<AutoTArray<void*, kAcquisitionStateStackSize> > + AcquisitionState; +# endif + + /** + * BlockingResourceBase + * Initialize this blocking resource. Also hooks the resource into + * instrumentation code. + * + * Thread safe. + * + * @param aName A meaningful, unique name that can be used in + * error messages, et al. + * @param aType The specific type of |this|, if any. + **/ + BlockingResourceBase(const char* aName, BlockingResourceType aType); + + ~BlockingResourceBase(); + + /** + * CheckAcquire + * + * Thread safe. + **/ + void CheckAcquire(); + + /** + * Acquire + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Acquire(); // NS_NEEDS_RESOURCE(this) + + /** + * Release + * Remove this resource from the current thread's acquisition chain. + * The resource does not have to be at the front of the chain, although + * it is confusing to release resources in a different order than they + * are acquired. This generates a warning. + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Release(); // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainFront + * + * Thread safe. + * + * @return the front of the resource acquisition chain, i.e., the last + * resource acquired. + */ + static BlockingResourceBase* ResourceChainFront() { + return sResourceAcqnChainFront.get(); + } + + /** + * ResourceChainPrev + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + static BlockingResourceBase* ResourceChainPrev( + const BlockingResourceBase* aResource) { + return aResource->mChainPrev; + } // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainAppend + * Set |this| to the front of the resource acquisition chain, and link + * |this| to |aPrev|. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainAppend(BlockingResourceBase* aPrev) { + mChainPrev = aPrev; + sResourceAcqnChainFront.set(this); + } // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainRemove + * Remove |this| from the front of the resource acquisition chain. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainRemove() { + NS_ASSERTION(this == ResourceChainFront(), "not at chain front"); + sResourceAcqnChainFront.set(mChainPrev); + } // NS_NEEDS_RESOURCE(this) + + /** + * GetAcquisitionState + * Return whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + AcquisitionState GetAcquisitionState() { return mAcquired; } + + /** + * SetAcquisitionState + * Set whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void SetAcquisitionState(const AcquisitionState& aAcquisitionState) { + mAcquired = aAcquisitionState; + } + + /** + * ClearAcquisitionState + * Indicate this resource is not acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ClearAcquisitionState() { +# ifdef MOZ_CALLSTACK_DISABLED + mAcquired = false; +# else + mAcquired.reset(); +# endif + } + + /** + * IsAcquired + * Indicates if this resource is acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + bool IsAcquired() const { return (bool)mAcquired; } + + /** + * mChainPrev + * A series of resource acquisitions creates a chain of orders. This + * chain is implemented as a linked list; |mChainPrev| points to the + * resource most recently Acquire()'d before this one. + **/ + BlockingResourceBase* mChainPrev; + + private: + /** + * mName + * A descriptive name for this resource. Used in error + * messages etc. + */ + const char* mName; + + /** + * mType + * The more specific type of this resource. Used to implement + * special semantics (e.g., reentrancy of monitors). + **/ + BlockingResourceType mType; + + /** + * mAcquired + * Indicates if this resource is currently acquired. + */ + AcquisitionState mAcquired; + +# ifndef MOZ_CALLSTACK_DISABLED + /** + * mFirstSeen + * Inidicates where this resource was first acquired. + */ + AcquisitionState mFirstSeen; +# endif + + /** + * sCallOnce + * Ensures static members are initialized only once, and in a + * thread-safe way. + */ + static PRCallOnceType sCallOnce; + + /** + * Thread-private pointer to the front of each thread's resource + * acquisition chain. + */ + static MOZ_THREAD_LOCAL(BlockingResourceBase*) sResourceAcqnChainFront; + + /** + * sDeadlockDetector + * Does as named. + */ + static DDT* sDeadlockDetector; + + /** + * InitStatics + * Inititialize static members of BlockingResourceBase that can't + * be statically initialized. + * + * *NOT* thread safe. + */ + static PRStatus InitStatics(); + + /** + * Shutdown + * Free static members. + * + * *NOT* thread safe. + */ + static void Shutdown(); + + static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp, + void* aClosure); + static void GetStackTrace(AcquisitionState& aState); + +# ifdef MOZILLA_INTERNAL_API + // so it can call BlockingResourceBase::Shutdown() + friend void LogTerm(); +# endif // ifdef MOZILLA_INTERNAL_API + +#else // non-DEBUG implementation + + BlockingResourceBase(const char* aName, BlockingResourceType aType) {} + + ~BlockingResourceBase() {} + +#endif +}; + +} // namespace mozilla + +#endif // mozilla_BlockingResourceBase_h diff --git a/xpcom/threads/CPUUsageWatcher.cpp b/xpcom/threads/CPUUsageWatcher.cpp new file mode 100644 index 0000000000..03d213ebc9 --- /dev/null +++ b/xpcom/threads/CPUUsageWatcher.cpp @@ -0,0 +1,256 @@ +/* -*- 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/CPUUsageWatcher.h" + +#include "prsystem.h" + +#ifdef XP_MACOSX +# include <sys/resource.h> +# include <mach/clock.h> +# include <mach/mach_host.h> +#endif + +#ifdef CPU_USAGE_WATCHER_ACTIVE +# include "mozilla/BackgroundHangMonitor.h" +#endif + +namespace mozilla { + +#ifdef CPU_USAGE_WATCHER_ACTIVE + +// Even if the machine only has one processor, tolerate up to 50% +// external CPU usage. +static const float kTolerableExternalCPUUsageFloor = 0.5f; + +struct CPUStats { + // The average CPU usage time, which can be summed across all cores in the + // system, or averaged between them. Whichever it is, it needs to be in the + // same units as updateTime. + uint64_t usageTime; + // A monotonically increasing value in the same units as usageTime, which can + // be used to determine the percentage of active vs idle time + uint64_t updateTime; +}; + +# ifdef XP_MACOSX + +static const uint64_t kMicrosecondsPerSecond = 1000000LL; +static const uint64_t kNanosecondsPerMicrosecond = 1000LL; +static const uint64_t kCPUCheckInterval = kMicrosecondsPerSecond / 2LL; + +static uint64_t GetMicroseconds(timeval time) { + return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + + (uint64_t)time.tv_usec; +} + +static uint64_t GetMicroseconds(mach_timespec_t time) { + return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + + ((uint64_t)time.tv_nsec) / kNanosecondsPerMicrosecond; +} + +static Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats( + int32_t numCPUs) { + CPUStats result = {}; + rusage usage; + int32_t rusageResult = getrusage(RUSAGE_SELF, &usage); + if (rusageResult == -1) { + return Err(GetProcessTimesError); + } + result.usageTime = + GetMicroseconds(usage.ru_utime) + GetMicroseconds(usage.ru_stime); + + clock_serv_t realtimeClock; + kern_return_t errorResult = + host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &realtimeClock); + if (errorResult != KERN_SUCCESS) { + return Err(GetProcessTimesError); + } + mach_timespec_t time; + errorResult = clock_get_time(realtimeClock, &time); + if (errorResult != KERN_SUCCESS) { + return Err(GetProcessTimesError); + } + result.updateTime = GetMicroseconds(time); + + // getrusage will give us the sum of the values across all + // of our cores. Divide by the number of CPUs to get an average. + result.usageTime /= numCPUs; + return result; +} + +static Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() { + CPUStats result = {}; + host_cpu_load_info_data_t loadInfo; + mach_msg_type_number_t loadInfoCount = HOST_CPU_LOAD_INFO_COUNT; + kern_return_t statsResult = + host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, + (host_info_t)&loadInfo, &loadInfoCount); + if (statsResult != KERN_SUCCESS) { + return Err(HostStatisticsError); + } + + result.usageTime = loadInfo.cpu_ticks[CPU_STATE_USER] + + loadInfo.cpu_ticks[CPU_STATE_NICE] + + loadInfo.cpu_ticks[CPU_STATE_SYSTEM]; + result.updateTime = result.usageTime + loadInfo.cpu_ticks[CPU_STATE_IDLE]; + return result; +} + +# endif // XP_MACOSX + +# ifdef XP_WIN + +// A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC +static const uint64_t kFILETIMETicksPerSecond = 10000000; +static const uint64_t kCPUCheckInterval = kFILETIMETicksPerSecond / 2; + +uint64_t FiletimeToInteger(FILETIME filetime) { + return ((uint64_t)filetime.dwLowDateTime) | (uint64_t)filetime.dwHighDateTime + << 32; +} + +Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats(int32_t numCPUs) { + CPUStats result = {}; + FILETIME creationFiletime; + FILETIME exitFiletime; + FILETIME kernelFiletime; + FILETIME userFiletime; + bool success = GetProcessTimes(GetCurrentProcess(), &creationFiletime, + &exitFiletime, &kernelFiletime, &userFiletime); + if (!success) { + return Err(GetProcessTimesError); + } + + result.usageTime = + FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime); + + FILETIME nowFiletime; + GetSystemTimeAsFileTime(&nowFiletime); + result.updateTime = FiletimeToInteger(nowFiletime); + + result.usageTime /= numCPUs; + + return result; +} + +Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() { + CPUStats result = {}; + FILETIME idleFiletime; + FILETIME kernelFiletime; + FILETIME userFiletime; + bool success = GetSystemTimes(&idleFiletime, &kernelFiletime, &userFiletime); + + if (!success) { + return Err(GetSystemTimesError); + } + + result.usageTime = + FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime); + result.updateTime = result.usageTime + FiletimeToInteger(idleFiletime); + + return result; +} + +# endif // XP_WIN + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() { + mNumCPUs = PR_GetNumberOfProcessors(); + if (mNumCPUs <= 0) { + mExternalUsageThreshold = 1.0f; + return Err(GetNumberOfProcessorsError); + } + mExternalUsageThreshold = + std::max(1.0f - 1.0f / (float)mNumCPUs, kTolerableExternalCPUUsageFloor); + + CPUStats processTimes; + MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); + mProcessUpdateTime = processTimes.updateTime; + mProcessUsageTime = processTimes.usageTime; + + CPUStats globalTimes; + MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); + mGlobalUpdateTime = globalTimes.updateTime; + mGlobalUsageTime = globalTimes.usageTime; + + mInitialized = true; + + CPUUsageWatcher* self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "CPUUsageWatcher::Init", + [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); })); + + return Ok(); +} + +void CPUUsageWatcher::Uninit() { + if (mInitialized) { + BackgroundHangMonitor::UnregisterAnnotator(*this); + } + mInitialized = false; +} + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() { + if (!mInitialized) { + return Ok(); + } + + mExternalUsageRatio = 0.0f; + + CPUStats processTimes; + MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); + CPUStats globalTimes; + MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); + + uint64_t processUsageDelta = processTimes.usageTime - mProcessUsageTime; + uint64_t processUpdateDelta = processTimes.updateTime - mProcessUpdateTime; + float processUsageNormalized = + processUsageDelta > 0 + ? (float)processUsageDelta / (float)processUpdateDelta + : 0.0f; + + uint64_t globalUsageDelta = globalTimes.usageTime - mGlobalUsageTime; + uint64_t globalUpdateDelta = globalTimes.updateTime - mGlobalUpdateTime; + float globalUsageNormalized = + globalUsageDelta > 0 ? (float)globalUsageDelta / (float)globalUpdateDelta + : 0.0f; + + mProcessUsageTime = processTimes.usageTime; + mProcessUpdateTime = processTimes.updateTime; + mGlobalUsageTime = globalTimes.usageTime; + mGlobalUpdateTime = globalTimes.updateTime; + + mExternalUsageRatio = + std::max(0.0f, globalUsageNormalized - processUsageNormalized); + + return Ok(); +} + +void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) { + if (!mInitialized) { + return; + } + + if (mExternalUsageRatio > mExternalUsageThreshold) { + aAnnotations.AddAnnotation(u"ExternalCPUHigh"_ns, true); + } +} + +#else // !CPU_USAGE_WATCHER_ACTIVE + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() { return Ok(); } + +void CPUUsageWatcher::Uninit() {} + +Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() { + return Ok(); +} + +void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {} + +#endif // CPU_USAGE_WATCHER_ACTIVE + +} // namespace mozilla diff --git a/xpcom/threads/CPUUsageWatcher.h b/xpcom/threads/CPUUsageWatcher.h new file mode 100644 index 0000000000..c3a643378a --- /dev/null +++ b/xpcom/threads/CPUUsageWatcher.h @@ -0,0 +1,100 @@ +/* -*- 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/. */ + +#ifndef mozilla_CPUUsageWatcher_h +#define mozilla_CPUUsageWatcher_h + +#include <stdint.h> + +#include "mozilla/HangAnnotations.h" +#include "mozilla/Result.h" + +// We only support OSX and Windows, because on Linux we're forced to read +// from /proc/stat in order to get global CPU values. We would prefer to not +// eat that cost for this. +#if defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX)) +# define CPU_USAGE_WATCHER_ACTIVE +#endif + +namespace mozilla { + +// Start error values at 1 to allow using the UnusedZero Result +// optimization. +enum CPUUsageWatcherError : uint8_t { + ClockGetTimeError = 1, + GetNumberOfProcessorsError, + GetProcessTimesError, + GetSystemTimesError, + HostStatisticsError, + ProcStatError, +}; + +namespace detail { + +template <> +struct UnusedZero<CPUUsageWatcherError> : UnusedZeroEnum<CPUUsageWatcherError> { +}; + +} // namespace detail + +class CPUUsageHangAnnotator : public BackgroundHangAnnotator { + public: +}; + +class CPUUsageWatcher : public BackgroundHangAnnotator { + public: +#ifdef CPU_USAGE_WATCHER_ACTIVE + CPUUsageWatcher() + : mInitialized(false), + mExternalUsageThreshold(0), + mExternalUsageRatio(0), + mProcessUsageTime(0), + mProcessUpdateTime(0), + mGlobalUsageTime(0), + mGlobalUpdateTime(0), + mNumCPUs(0) {} +#endif + + Result<Ok, CPUUsageWatcherError> Init(); + + void Uninit(); + + // Updates necessary values to allow AnnotateHang to function. This must be + // called on some semi-regular basis, as it will calculate the mean CPU + // usage values between now and the last time it was called. + Result<Ok, CPUUsageWatcherError> CollectCPUUsage(); + + void AnnotateHang(BackgroundHangAnnotations& aAnnotations) final; + + private: +#ifdef CPU_USAGE_WATCHER_ACTIVE + bool mInitialized; + // The threshold above which we will mark a hang as occurring under high + // external CPU usage conditions + float mExternalUsageThreshold; + // The CPU usage (0-1) external to our process, averaged between the two + // most recent monitor thread runs + float mExternalUsageRatio; + // The total cumulative CPU usage time by our process as of the last + // CollectCPUUsage or Startup + uint64_t mProcessUsageTime; + // A time value in the same units as mProcessUsageTime used to + // determine the ratio of CPU usage time to idle time + uint64_t mProcessUpdateTime; + // The total cumulative CPU usage time by all processes as of the last + // CollectCPUUsage or Startup + uint64_t mGlobalUsageTime; + // A time value in the same units as mGlobalUsageTime used to + // determine the ratio of CPU usage time to idle time + uint64_t mGlobalUpdateTime; + // The number of virtual cores on our machine + uint64_t mNumCPUs; +#endif +}; + +} // namespace mozilla + +#endif // mozilla_CPUUsageWatcher_h diff --git a/xpcom/threads/CondVar.h b/xpcom/threads/CondVar.h new file mode 100644 index 0000000000..890e1c21a0 --- /dev/null +++ b/xpcom/threads/CondVar.h @@ -0,0 +1,137 @@ +/* -*- 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/. */ + +#ifndef mozilla_CondVar_h +#define mozilla_CondVar_h + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PlatformConditionVariable.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" + +#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG) +# include "GeckoProfiler.h" +#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG) + +namespace mozilla { + +/** + * Similarly to OffTheBooksMutex, OffTheBooksCondvar is identical to CondVar, + * except that OffTheBooksCondVar doesn't include leak checking. Sometimes + * you want to intentionally "leak" a CondVar until shutdown; in these cases, + * OffTheBooksCondVar is for you. + */ +class OffTheBooksCondVar : BlockingResourceBase { + public: + /** + * OffTheBooksCondVar + * + * The CALLER owns |aLock|. + * + * @param aLock A Mutex to associate with this condition variable. + * @param aName A name which can reference this monitor + * @returns If failure, nullptr. + * If success, a valid Monitor* which must be destroyed + * by Monitor::DestroyMonitor() + **/ + OffTheBooksCondVar(OffTheBooksMutex& aLock, const char* aName) + : BlockingResourceBase(aName, eCondVar), mLock(&aLock) {} + + /** + * ~OffTheBooksCondVar + * Clean up after this OffTheBooksCondVar, but NOT its associated Mutex. + **/ + ~OffTheBooksCondVar() = default; + + /** + * Wait + * @see prcvar.h + **/ +#ifndef DEBUG + void Wait() { +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + mImpl.wait(*mLock); + } + + CVStatus Wait(TimeDuration aDuration) { +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + return mImpl.wait_for(*mLock, aDuration); + } +#else + // NOTE: debug impl is in BlockingResourceBase.cpp + void Wait(); + CVStatus Wait(TimeDuration aDuration); +#endif + + /** + * Notify + * @see prcvar.h + **/ + void Notify() { mImpl.notify_one(); } + + /** + * NotifyAll + * @see prcvar.h + **/ + void NotifyAll() { mImpl.notify_all(); } + +#ifdef DEBUG + /** + * AssertCurrentThreadOwnsMutex + * @see Mutex::AssertCurrentThreadOwns + **/ + void AssertCurrentThreadOwnsMutex() { mLock->AssertCurrentThreadOwns(); } + + /** + * AssertNotCurrentThreadOwnsMutex + * @see Mutex::AssertNotCurrentThreadOwns + **/ + void AssertNotCurrentThreadOwnsMutex() { + mLock->AssertNotCurrentThreadOwns(); + } + +#else + void AssertCurrentThreadOwnsMutex() {} + void AssertNotCurrentThreadOwnsMutex() {} + +#endif // ifdef DEBUG + + private: + OffTheBooksCondVar(); + OffTheBooksCondVar(const OffTheBooksCondVar&) = delete; + OffTheBooksCondVar& operator=(const OffTheBooksCondVar&) = delete; + + OffTheBooksMutex* mLock; + detail::ConditionVariableImpl mImpl; +}; + +/** + * CondVar + * Vanilla condition variable. Please don't use this unless you have a + * compelling reason --- Monitor provides a simpler API. + */ +class CondVar : public OffTheBooksCondVar { + public: + CondVar(OffTheBooksMutex& aLock, const char* aName) + : OffTheBooksCondVar(aLock, aName) { + MOZ_COUNT_CTOR(CondVar); + } + + MOZ_COUNTED_DTOR(CondVar) + + private: + CondVar(); + CondVar(const CondVar&); + CondVar& operator=(const CondVar&); +}; + +} // namespace mozilla + +#endif // ifndef mozilla_CondVar_h diff --git a/xpcom/threads/DataMutex.h b/xpcom/threads/DataMutex.h new file mode 100644 index 0000000000..0a768f925a --- /dev/null +++ b/xpcom/threads/DataMutex.h @@ -0,0 +1,125 @@ +/* -*- 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/. */ + +#ifndef DataMutex_h__ +#define DataMutex_h__ + +#include <utility> +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" + +namespace mozilla { + +// A template to wrap a type with a mutex so that accesses to the type's +// data are required to take the lock before accessing it. This ensures +// that a mutex is explicitly associated with the data that it protects, +// and makes it impossible to access the data without first taking the +// associated mutex. +// +// This is based on Rust's std::sync::Mutex, which operates under the +// strategy of locking data, rather than code. +// +// Examples: +// +// DataMutex<uint32_t> u32DataMutex(1, "u32DataMutex"); +// auto x = u32DataMutex.Lock(); +// *x = 4; +// assert(*x, 4u); +// +// DataMutex<nsTArray<uint32_t>> arrayDataMutex("arrayDataMutex"); +// auto a = arrayDataMutex.Lock(); +// auto& x = a.ref(); +// x.AppendElement(1u); +// assert(x[0], 1u); +// +template <typename T, typename MutexType> +class DataMutexBase { + public: + class MOZ_STACK_CLASS AutoLock { + public: + T* operator->() const& { return &ref(); } + T* operator->() const&& = delete; + + T& operator*() const& { return ref(); } + T& operator*() const&& = delete; + + // Like RefPtr, make this act like its underlying raw pointer type + // whenever it is used in a context where a raw pointer is expected. + operator T*() const& { return &ref(); } + + // Like RefPtr, don't allow implicit conversion of temporary to raw pointer. + operator T*() const&& = delete; + + T& ref() const& { + MOZ_ASSERT(mOwner); + return mOwner->mValue; + } + T& ref() const&& = delete; + + AutoLock(AutoLock&& aOther) : mOwner(aOther.mOwner) { + aOther.mOwner = nullptr; + } + + ~AutoLock() { + if (mOwner) { + mOwner->mMutex.Unlock(); + mOwner = nullptr; + } + } + + private: + friend class DataMutexBase; + + AutoLock(const AutoLock& aOther) = delete; + + explicit AutoLock(DataMutexBase<T, MutexType>* aDataMutex) + : mOwner(aDataMutex) { + MOZ_ASSERT(!!mOwner); + mOwner->mMutex.Lock(); + } + + DataMutexBase<T, MutexType>* mOwner; + }; + + explicit DataMutexBase(const char* aName) : mMutex(aName) {} + + DataMutexBase(T&& aValue, const char* aName) + : mMutex(aName), mValue(std::move(aValue)) {} + + AutoLock Lock() { return AutoLock(this); } + + const MutexType& Mutex() const { return mMutex; } + + private: + MutexType mMutex; + T mValue; +}; + +// Craft a version of StaticMutex that takes a const char* in its ctor. +// We need this so it works interchangeably with Mutex which requires a const +// char* aName in its ctor. +class StaticMutexNameless : public StaticMutex { + public: + explicit StaticMutexNameless(const char* aName) : StaticMutex() {} + + private: + // Disallow copy construction, `=`, `new`, and `delete` like BaseStaticMutex. +#ifdef DEBUG + StaticMutexNameless(StaticMutexNameless& aOther); +#endif // DEBUG + StaticMutexNameless& operator=(StaticMutexNameless* aRhs); + static void* operator new(size_t) noexcept(true); + static void operator delete(void*); +}; + +template <typename T> +using DataMutex = DataMutexBase<T, Mutex>; +template <typename T> +using StaticDataMutex = DataMutexBase<T, StaticMutexNameless>; + +} // namespace mozilla + +#endif // DataMutex_h__ diff --git a/xpcom/threads/DeadlockDetector.h b/xpcom/threads/DeadlockDetector.h new file mode 100644 index 0000000000..d8b9c79c02 --- /dev/null +++ b/xpcom/threads/DeadlockDetector.h @@ -0,0 +1,359 @@ +/* -*- 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/. */ +#ifndef mozilla_DeadlockDetector_h +#define mozilla_DeadlockDetector_h + +#include "mozilla/Attributes.h" + +#include <stdlib.h> + +#include "prlock.h" + +#include "nsClassHashtable.h" +#include "nsTArray.h" + +namespace mozilla { + +/** + * DeadlockDetector + * + * The following is an approximate description of how the deadlock detector + * works. + * + * The deadlock detector ensures that all blocking resources are + * acquired according to a partial order P. One type of blocking + * resource is a lock. If a lock l1 is acquired (locked) before l2, + * then we say that |l1 <_P l2|. The detector flags an error if two + * locks l1 and l2 have an inconsistent ordering in P; that is, if + * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error + * because a thread acquiring l1,l2 according to the first order might + * race with a thread acquiring them according to the second order. + * If this happens under the right conditions, then the acquisitions + * will deadlock. + * + * This deadlock detector doesn't know at compile-time what P is. So, + * it tries to discover the order at run time. More precisely, it + * finds <i>some</i> order P, then tries to find chains of resource + * acquisitions that violate P. An example acquisition sequence, and + * the orders they impose, is + * l1.lock() // current chain: [ l1 ] + * // order: { } + * + * l2.lock() // current chain: [ l1, l2 ] + * // order: { l1 <_P l2 } + * + * l3.lock() // current chain: [ l1, l2, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: <_P is transitive, so also |l1 <_P l3|) + * + * l2.unlock() // current chain: [ l1, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: it's OK, but weird, that l2 was unlocked out + * // of order. we still have l1 <_P l3). + * + * l2.lock() // current chain: [ l1, l3, l2 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3, + * l3 <_P l2 (!!!) } + * BEEP BEEP! Here the detector will flag a potential error, since + * l2 and l3 were used inconsistently (and potentially in ways that + * would deadlock). + */ +template <typename T> +class DeadlockDetector { + public: + typedef nsTArray<const T*> ResourceAcquisitionArray; + + private: + struct OrderingEntry; + typedef nsTArray<OrderingEntry*> HashEntryArray; + typedef typename HashEntryArray::index_type index_type; + typedef typename HashEntryArray::size_type size_type; + static const index_type NoIndex = HashEntryArray::NoIndex; + + /** + * Value type for the ordering table. Contains the other + * resources on which an ordering constraint |key < other| + * exists. The catch is that we also store the calling context at + * which the other resource was acquired; this improves the + * quality of error messages when potential deadlock is detected. + */ + struct OrderingEntry { + explicit OrderingEntry(const T* aResource) + : mOrderedLT() // FIXME bug 456272: set to empirical dep size? + , + mExternalRefs(), + mResource(aResource) {} + ~OrderingEntry() {} + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + n += mOrderedLT.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mExternalRefs.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; + } + + HashEntryArray mOrderedLT; // this <_o Other + HashEntryArray mExternalRefs; // hash entries that reference this + const T* mResource; + }; + + // Throwaway RAII lock to make the following code safer. + struct PRAutoLock { + explicit PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); } + ~PRAutoLock() { PR_Unlock(mLock); } + PRLock* mLock; + }; + + public: + static const uint32_t kDefaultNumBuckets; + + /** + * DeadlockDetector + * Create a new deadlock detector. + * + * @param aNumResourcesGuess Guess at approximate number of resources + * that will be checked. + */ + explicit DeadlockDetector(uint32_t aNumResourcesGuess = kDefaultNumBuckets) + : mOrdering(aNumResourcesGuess) { + mLock = PR_NewLock(); + if (!mLock) { + MOZ_CRASH("couldn't allocate deadlock detector lock"); + } + } + + /** + * ~DeadlockDetector + * + * *NOT* thread safe. + */ + ~DeadlockDetector() { PR_DestroyLock(mLock); } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + + { + PRAutoLock _(mLock); + n += mOrdering.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mOrdering.ConstIter(); !iter.Done(); iter.Next()) { + // NB: Key is accounted for in the entry. + n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return n; + } + + /** + * Add + * Make the deadlock detector aware of |aResource|. + * + * WARNING: The deadlock detector owns |aResource|. + * + * Thread safe. + * + * @param aResource Resource to make deadlock detector aware of. + */ + void Add(const T* aResource) { + PRAutoLock _(mLock); + mOrdering.Put(aResource, new OrderingEntry(aResource)); + } + + void Remove(const T* aResource) { + PRAutoLock _(mLock); + + OrderingEntry* entry = mOrdering.Get(aResource); + + // Iterate the external refs and remove the entry from them. + HashEntryArray& refs = entry->mExternalRefs; + for (index_type i = 0; i < refs.Length(); i++) { + refs[i]->mOrderedLT.RemoveElementSorted(entry); + } + + // Iterate orders and remove this entry from their refs. + HashEntryArray& orders = entry->mOrderedLT; + for (index_type i = 0; i < orders.Length(); i++) { + orders[i]->mExternalRefs.RemoveElementSorted(entry); + } + + // Now the entry can be safely removed. + mOrdering.Remove(aResource); + } + + /** + * CheckAcquisition This method is called after acquiring |aLast|, + * but before trying to acquire |aProposed|. + * It determines whether actually trying to acquire |aProposed| + * will create problems. It is OK if |aLast| is nullptr; this is + * interpreted as |aProposed| being the thread's first acquisition + * of its current chain. + * + * Iff acquiring |aProposed| may lead to deadlock for some thread + * interleaving (including the current one!), the cyclical + * dependency from which this was deduced is returned. Otherwise, + * 0 is returned. + * + * If a potential deadlock is detected and a resource cycle is + * returned, it is the *caller's* responsibility to free it. + * + * Thread safe. + * + * @param aLast Last resource acquired by calling thread (or 0). + * @param aProposed Resource calling thread proposes to acquire. + */ + ResourceAcquisitionArray* CheckAcquisition(const T* aLast, + const T* aProposed) { + if (!aLast) { + // don't check if |0 < aProposed|; just vamoose + return 0; + } + + NS_ASSERTION(aProposed, "null resource"); + PRAutoLock _(mLock); + + OrderingEntry* proposed = mOrdering.Get(aProposed); + NS_ASSERTION(proposed, "missing ordering entry"); + + OrderingEntry* current = mOrdering.Get(aLast); + NS_ASSERTION(current, "missing ordering entry"); + + // this is the crux of the deadlock detector algorithm + + if (current == proposed) { + // reflexive deadlock. fastpath b/c InTransitiveClosure is + // not applicable here. + ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray(); + if (!cycle) { + MOZ_CRASH("can't allocate dep. cycle array"); + } + cycle->AppendElement(current->mResource); + cycle->AppendElement(aProposed); + return cycle; + } + if (InTransitiveClosure(current, proposed)) { + // we've already established |aLast < aProposed|. all is well. + return 0; + } + if (InTransitiveClosure(proposed, current)) { + // the order |aProposed < aLast| has been deduced, perhaps + // transitively. we're attempting to violate that + // constraint by acquiring resources in the order + // |aLast < aProposed|, and thus we may deadlock under the + // right conditions. + ResourceAcquisitionArray* cycle = GetDeductionChain(proposed, current); + // show how acquiring |aProposed| would complete the cycle + cycle->AppendElement(aProposed); + return cycle; + } + // |aLast|, |aProposed| are unordered according to our + // poset. this is fine, but we now need to add this + // ordering constraint. + current->mOrderedLT.InsertElementSorted(proposed); + proposed->mExternalRefs.InsertElementSorted(current); + return 0; + } + + /** + * Return true iff |aTarget| is in the transitive closure of |aStart| + * over the ordering relation `<_this'. + * + * @precondition |aStart != aTarget| + */ + bool InTransitiveClosure(const OrderingEntry* aStart, + const OrderingEntry* aTarget) const { + // NB: Using a static comparator rather than default constructing one shows + // a 9% improvement in scalability tests on some systems. + static nsDefaultComparator<const OrderingEntry*, const OrderingEntry*> comp; + if (aStart->mOrderedLT.BinaryIndexOf(aTarget, comp) != NoIndex) { + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + if (InTransitiveClosure(*it, aTarget)) { + return true; + } + } + return false; + } + + /** + * Return an array of all resource acquisitions + * aStart <_this r1 <_this r2 <_ ... <_ aTarget + * from which |aStart <_this aTarget| was deduced, including + * |aStart| and |aTarget|. + * + * Nb: there may be multiple deductions of |aStart <_this + * aTarget|. This function returns the first ordering found by + * depth-first search. + * + * Nb: |InTransitiveClosure| could be replaced by this function. + * However, this one is more expensive because we record the DFS + * search stack on the heap whereas the other doesn't. + * + * @precondition |aStart != aTarget| + */ + ResourceAcquisitionArray* GetDeductionChain(const OrderingEntry* aStart, + const OrderingEntry* aTarget) { + ResourceAcquisitionArray* chain = new ResourceAcquisitionArray(); + if (!chain) { + MOZ_CRASH("can't allocate dep. cycle array"); + } + chain->AppendElement(aStart->mResource); + + NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain), + "GetDeductionChain called when there's no deadlock"); + return chain; + } + + // precondition: |aStart != aTarget| + // invariant: |aStart| is the last element in |aChain| + bool GetDeductionChain_Helper(const OrderingEntry* aStart, + const OrderingEntry* aTarget, + ResourceAcquisitionArray* aChain) { + if (aStart->mOrderedLT.BinaryIndexOf(aTarget) != NoIndex) { + aChain->AppendElement(aTarget->mResource); + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + aChain->AppendElement((*it)->mResource); + if (GetDeductionChain_Helper(*it, aTarget, aChain)) { + return true; + } + aChain->RemoveLastElement(); + } + return false; + } + + /** + * The partial order on resource acquisitions used by the deadlock + * detector. + */ + nsClassHashtable<nsPtrHashKey<const T>, OrderingEntry> mOrdering; + + /** + * Protects contentious methods. + * Nb: can't use mozilla::Mutex since we are used as its deadlock + * detector. + */ + PRLock* mLock; + + private: + DeadlockDetector(const DeadlockDetector& aDD) = delete; + DeadlockDetector& operator=(const DeadlockDetector& aDD) = delete; +}; + +template <typename T> +// FIXME bug 456272: tune based on average workload +const uint32_t DeadlockDetector<T>::kDefaultNumBuckets = 32; + +} // namespace mozilla + +#endif // ifndef mozilla_DeadlockDetector_h diff --git a/xpcom/threads/EventQueue.cpp b/xpcom/threads/EventQueue.cpp new file mode 100644 index 0000000000..fc263032b6 --- /dev/null +++ b/xpcom/threads/EventQueue.cpp @@ -0,0 +1,133 @@ +/* -*- 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/EventQueue.h" + +#include "GeckoProfiler.h" +#include "InputTaskManager.h" +#include "nsIRunnable.h" +#include "TaskController.h" + +using namespace mozilla; +using namespace mozilla::detail; + +template <size_t ItemsPerPage> +void EventQueueInternal<ItemsPerPage>::PutEvent( + already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aPriority, + const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aDelay) { + nsCOMPtr<nsIRunnable> event(aEvent); + + static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_IDLE) == + static_cast<uint32_t>(EventQueuePriority::Idle)); + static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_NORMAL) == + static_cast<uint32_t>(EventQueuePriority::Normal)); + static_assert( + static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_MEDIUMHIGH) == + static_cast<uint32_t>(EventQueuePriority::MediumHigh)); + static_assert( + static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_INPUT_HIGH) == + static_cast<uint32_t>(EventQueuePriority::InputHigh)); + static_assert(static_cast<uint32_t>(nsIRunnablePriority::PRIORITY_HIGH) == + static_cast<uint32_t>(EventQueuePriority::High)); + + if (mForwardToTC) { + TaskController* tc = TaskController::Get(); + + TaskManager* manager = nullptr; + if (aPriority == EventQueuePriority::InputHigh) { + if (InputTaskManager::Get()->State() == + InputTaskManager::STATE_DISABLED) { + aPriority = EventQueuePriority::Normal; + } else { + manager = InputTaskManager::Get(); + } + } else if (aPriority == EventQueuePriority::DeferredTimers || + aPriority == EventQueuePriority::Idle) { + manager = TaskController::Get()->GetIdleTaskManager(); + } + + tc->DispatchRunnable(event.forget(), static_cast<uint32_t>(aPriority), + manager); + return; + } + +#ifdef MOZ_GECKO_PROFILER + // Sigh, this doesn't check if this thread is being profiled + if (profiler_is_active()) { + // check to see if the profiler has been enabled since the last PutEvent + while (mDispatchTimes.Count() < mQueue.Count()) { + mDispatchTimes.Push(TimeStamp()); + } + mDispatchTimes.Push(aDelay ? TimeStamp::Now() - *aDelay : TimeStamp::Now()); + } +#endif + + mQueue.Push(std::move(event)); +} + +template <size_t ItemsPerPage> +already_AddRefed<nsIRunnable> EventQueueInternal<ItemsPerPage>::GetEvent( + const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aLastEventDelay) { + if (mQueue.IsEmpty()) { + if (aLastEventDelay) { + *aLastEventDelay = TimeDuration(); + } + return nullptr; + } + +#ifdef MOZ_GECKO_PROFILER + // We always want to clear the dispatch times, even if the profiler is turned + // off, because we want to empty the (previously-collected) dispatch times, if + // any, from when the profiler was turned on. We only want to do something + // interesting with the dispatch times if the profiler is turned on, though. + if (!mDispatchTimes.IsEmpty()) { + TimeStamp dispatch_time = mDispatchTimes.Pop(); + if (profiler_is_active()) { + if (!dispatch_time.IsNull()) { + if (aLastEventDelay) { + *aLastEventDelay = TimeStamp::Now() - dispatch_time; + } + } + } + } else if (profiler_is_active()) { + if (aLastEventDelay) { + // if we just turned on the profiler, we don't have dispatch + // times for events already in the queue. + *aLastEventDelay = TimeDuration(); + } + } +#endif + + nsCOMPtr<nsIRunnable> result = mQueue.Pop(); + return result.forget(); +} + +template <size_t ItemsPerPage> +bool EventQueueInternal<ItemsPerPage>::IsEmpty( + const MutexAutoLock& aProofOfLock) { + return mQueue.IsEmpty(); +} + +template <size_t ItemsPerPage> +bool EventQueueInternal<ItemsPerPage>::HasReadyEvent( + const MutexAutoLock& aProofOfLock) { + return !IsEmpty(aProofOfLock); +} + +template <size_t ItemsPerPage> +size_t EventQueueInternal<ItemsPerPage>::Count( + const MutexAutoLock& aProofOfLock) const { + return mQueue.Count(); +} + +namespace mozilla { +template class EventQueueSized<16>; // Used by ThreadEventQueue +template class EventQueueSized<64>; // Used by ThrottledEventQueue +namespace detail { +template class EventQueueInternal<16>; // Used by ThreadEventQueue +template class EventQueueInternal<64>; // Used by ThrottledEventQueue +} // namespace detail +} // namespace mozilla diff --git a/xpcom/threads/EventQueue.h b/xpcom/threads/EventQueue.h new file mode 100644 index 0000000000..6301a0f6fd --- /dev/null +++ b/xpcom/threads/EventQueue.h @@ -0,0 +1,132 @@ +/* -*- 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/. */ + +#ifndef mozilla_EventQueue_h +#define mozilla_EventQueue_h + +#include "mozilla/Mutex.h" +#include "mozilla/Queue.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" + +class nsIRunnable; + +namespace mozilla { + +enum class EventQueuePriority { + Idle, + DeferredTimers, + InputLow, + Normal, + MediumHigh, + InputHigh, + High, + + Count +}; + +class IdlePeriodState; + +namespace detail { + +// EventQueue is our unsynchronized event queue implementation. It is a queue +// of runnables used for non-main thread, as well as optionally providing +// forwarding to TaskController. +// +// Since EventQueue is unsynchronized, it should be wrapped in an outer +// SynchronizedEventQueue implementation (like ThreadEventQueue). +template <size_t ItemsPerPage> +class EventQueueInternal { + public: + explicit EventQueueInternal(bool aForwardToTC) : mForwardToTC(aForwardToTC) {} + + // Add an event to the end of the queue. Implementors are free to use + // aPriority however they wish. If the runnable supports + // nsIRunnablePriority and the implementing class supports + // prioritization, aPriority represents the result of calling + // nsIRunnablePriority::GetPriority(). *aDelay is time the event has + // already been delayed (used when moving an event from one queue to + // another) + void PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority, const MutexAutoLock& aProofOfLock, + mozilla::TimeDuration* aDelay = nullptr); + + // Get an event from the front of the queue. This should return null if the + // queue is non-empty but the event in front is not ready to run. + // *aLastEventDelay is the time the event spent in queues before being + // retrieved. + already_AddRefed<nsIRunnable> GetEvent( + const MutexAutoLock& aProofOfLock, + mozilla::TimeDuration* aLastEventDelay = nullptr); + + // Returns true if the queue is empty. Implies !HasReadyEvent(). + bool IsEmpty(const MutexAutoLock& aProofOfLock); + + // Returns true if the queue is non-empty and if the event in front is ready + // to run. Implies !IsEmpty(). This should return true iff GetEvent returns a + // non-null value. + bool HasReadyEvent(const MutexAutoLock& aProofOfLock); + + // Returns the number of events in the queue. + size_t Count(const MutexAutoLock& aProofOfLock) const; + // For some reason, if we put this in the .cpp file the linker can't find it + already_AddRefed<nsIRunnable> PeekEvent(const MutexAutoLock& aProofOfLock) { + if (mQueue.IsEmpty()) { + return nullptr; + } + + nsCOMPtr<nsIRunnable> result = mQueue.FirstElement(); + return result.forget(); + } + + void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t size = mQueue.ShallowSizeOfExcludingThis(aMallocSizeOf); +#ifdef MOZ_GECKO_PROFILER + size += mDispatchTimes.ShallowSizeOfExcludingThis(aMallocSizeOf); +#endif + return size; + } + + private: + mozilla::Queue<nsCOMPtr<nsIRunnable>, ItemsPerPage> mQueue; +#ifdef MOZ_GECKO_PROFILER + // This queue is only populated when the profiler is turned on. + mozilla::Queue<mozilla::TimeStamp, ItemsPerPage> mDispatchTimes; + TimeDuration mLastEventDelay; +#endif + // This indicates PutEvent forwards runnables to the TaskController. This + // should be true for the top level event queue on the main thread. + bool mForwardToTC; +}; + +} // namespace detail + +class EventQueue final : public mozilla::detail::EventQueueInternal<16> { + public: + explicit EventQueue(bool aForwardToTC = false) + : mozilla::detail::EventQueueInternal<16>(aForwardToTC) {} +}; + +template <size_t ItemsPerPage = 16> +class EventQueueSized final + : public mozilla::detail::EventQueueInternal<ItemsPerPage> { + public: + explicit EventQueueSized(bool aForwardToTC = false) + : mozilla::detail::EventQueueInternal<ItemsPerPage>(aForwardToTC) {} +}; + +} // namespace mozilla + +#endif // mozilla_EventQueue_h diff --git a/xpcom/threads/IdlePeriodState.cpp b/xpcom/threads/IdlePeriodState.cpp new file mode 100644 index 0000000000..95f155e339 --- /dev/null +++ b/xpcom/threads/IdlePeriodState.cpp @@ -0,0 +1,254 @@ +/* -*- 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/IdlePeriodState.h" +#include "mozilla/StaticPrefs_idle_period.h" +#include "mozilla/ipc/IdleSchedulerChild.h" +#include "nsIIdlePeriod.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "nsXULAppAPI.h" + +static uint64_t sIdleRequestCounter = 0; + +namespace mozilla { + +IdlePeriodState::IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod) + : mIdlePeriod(aIdlePeriod) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); +} + +IdlePeriodState::~IdlePeriodState() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + if (mIdleScheduler) { + mIdleScheduler->Disconnect(); + } +} + +size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mIdlePeriod) { + n += aMallocSizeOf(mIdlePeriod); + } + + return n; +} + +void IdlePeriodState::FlagNotIdle() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + EnsureIsActive(); + if (mIdleToken && mIdleToken < TimeStamp::Now()) { + ClearIdleToken(); + } +} + +void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent); + EnsureIsPaused(aProofOfUnlock); + ClearIdleToken(); +} + +TimeStamp IdlePeriodState::GetIdleDeadlineInternal( + bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + bool shuttingDown; + TimeStamp localIdleDeadline = + GetLocalIdleDeadline(shuttingDown, aProofOfUnlock); + if (!localIdleDeadline) { + if (!aIsPeek) { + EnsureIsPaused(aProofOfUnlock); + ClearIdleToken(); + } + return TimeStamp(); + } + + TimeStamp idleDeadline = + mHasPendingEventsPromisedIdleEvent || shuttingDown + ? localIdleDeadline + : GetIdleToken(localIdleDeadline, aProofOfUnlock); + if (!idleDeadline) { + if (!aIsPeek) { + EnsureIsPaused(aProofOfUnlock); + + // Don't call ClearIdleToken() here, since we may have a pending + // request already. + // + // RequestIdleToken can do all sorts of IPC stuff that might + // take mutexes. This is one reason why we need the + // MutexAutoUnlock reference! + RequestIdleToken(localIdleDeadline); + } + return TimeStamp(); + } + + if (!aIsPeek) { + EnsureIsActive(); + } + return idleDeadline; +} + +TimeStamp IdlePeriodState::GetLocalIdleDeadline( + bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + // If we are shutting down, we won't honor the idle period, and we will + // always process idle runnables. This will ensure that the idle queue + // gets exhausted at shutdown time to prevent intermittently leaking + // some runnables inside that queue and even worse potentially leaving + // some important cleanup work unfinished. + if (gXPCOMThreadsShutDown || + nsThreadManager::get().GetCurrentThread()->ShuttingDown()) { + aShuttingDown = true; + return TimeStamp::Now(); + } + + aShuttingDown = false; + TimeStamp idleDeadline; + // This GetIdlePeriodHint() call is the reason we need a MutexAutoUnlock here. + mIdlePeriod->GetIdlePeriodHint(&idleDeadline); + + // If HasPendingEvents() has been called and it has returned true because of + // pending idle events, there is a risk that we may decide here that we aren't + // idle and return null, in which case HasPendingEvents() has effectively + // lied. Since we can't go back and fix the past, we have to adjust what we + // do here and forcefully pick the idle queue task here. Note that this means + // that we are choosing to run a task from the idle queue when we would + // normally decide that we aren't in an idle period, but this can only happen + // if we fall out of the idle period in between the call to HasPendingEvents() + // and here, which should hopefully be quite rare. We are effectively + // choosing to prioritize the sanity of our API semantics over the optimal + // scheduling. + if (!mHasPendingEventsPromisedIdleEvent && + (!idleDeadline || idleDeadline < TimeStamp::Now())) { + return TimeStamp(); + } + if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) { + // If HasPendingEvents() has been called and it has returned true, but we're + // no longer in the idle period, we must return a valid timestamp to pretend + // that we are still in the idle period. + return TimeStamp::Now(); + } + return idleDeadline; +} + +TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint, + const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + if (!ShouldGetIdleToken()) { + return aLocalIdlePeriodHint; + } + + if (mIdleToken) { + TimeStamp now = TimeStamp::Now(); + if (mIdleToken < now) { + ClearIdleToken(); + return mIdleToken; + } + return mIdleToken < aLocalIdlePeriodHint ? mIdleToken + : aLocalIdlePeriodHint; + } + return TimeStamp(); +} + +void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mActive); + + if (!mIdleSchedulerInitialized) { + mIdleSchedulerInitialized = true; + if (ShouldGetIdleToken()) { + // For now cross-process idle scheduler is supported only on the main + // threads of the child processes. + mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler(); + if (mIdleScheduler) { + mIdleScheduler->Init(this); + } + } + } + + if (mIdleScheduler && !mIdleRequestId) { + TimeStamp now = TimeStamp::Now(); + if (aLocalIdlePeriodHint <= now) { + return; + } + + mIdleRequestId = ++sIdleRequestCounter; + mIdleScheduler->SendRequestIdleTime(mIdleRequestId, + aLocalIdlePeriodHint - now); + } +} + +void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + // We check the request ID. It's possible that the server may be granting a + // an ealier request that the client has since cancelled and re-requested. + if (mIdleRequestId == aId) { + mIdleToken = TimeStamp::Now() + aDuration; + } +} + +void IdlePeriodState::SetActive() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mActive); + if (mIdleScheduler) { + mIdleScheduler->SetActive(); + } + mActive = true; +} + +void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(mActive); + if (mIdleScheduler && mIdleScheduler->SetPaused()) { + // We may have gotten a free cpu core for running idle tasks. + // We don't try to catch the case when there are prioritized processes + // running. + + // This SendSchedule call is why we need the MutexAutoUnlock here, because + // IPC can do weird things with mutexes. + mIdleScheduler->SendSchedule(); + } + mActive = false; +} + +void IdlePeriodState::ClearIdleToken() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + if (mIdleRequestId) { + if (mIdleScheduler) { + // This SendIdleTimeUsed call is why we need to not be holding + // any locks here, because IPC can do weird things with mutexes. + // Ideally we'd have a MutexAutoUnlock& reference here, but some + // callers end up here while just not holding any locks at all. + mIdleScheduler->SendIdleTimeUsed(mIdleRequestId); + } + mIdleRequestId = 0; + mIdleToken = TimeStamp(); + } +} + +bool IdlePeriodState::ShouldGetIdleToken() { + return StaticPrefs::idle_period_cross_process_scheduling() && + XRE_IsContentProcess(); +} +} // namespace mozilla diff --git a/xpcom/threads/IdlePeriodState.h b/xpcom/threads/IdlePeriodState.h new file mode 100644 index 0000000000..58303b454b --- /dev/null +++ b/xpcom/threads/IdlePeriodState.h @@ -0,0 +1,194 @@ +/* -*- 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/. */ + +#ifndef mozilla_IdlePeriodState_h +#define mozilla_IdlePeriodState_h + +/** + * A class for tracking the state of our idle period. This includes keeping + * track of both the state of our process-local idle period estimate and, for + * content processes, managing communication with the parent process for + * cross-pprocess idle detection. + */ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" + +#include <stdint.h> + +class nsIIdlePeriod; + +namespace mozilla { +class TaskManager; +namespace ipc { +class IdleSchedulerChild; +} // namespace ipc + +class IdlePeriodState { + public: + explicit IdlePeriodState(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod); + + ~IdlePeriodState(); + + // Integration with memory reporting. + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + // Notification that whoever we are tracking idle state for has found a + // non-idle task to process. + // + // Must not be called while holding any locks. + void FlagNotIdle(); + + // Notification that whoever we are tracking idle state for has no more + // tasks (idle or not) to process. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock); + + // Notification that whoever we are tracking idle state has idle tasks that + // they are considering ready to run and that we should keep claiming they are + // ready to run until they call ForgetPendingTaskGuarantee(). + void EnforcePendingTaskGuarantee() { + mHasPendingEventsPromisedIdleEvent = true; + } + + // Notification that whoever we are tracking idle state for is done with our + // "we have an idle event ready to run" guarantee. When this happens, we can + // reset mHasPendingEventsPromisedIdleEvent to false, because we have + // fulfilled our contract. + void ForgetPendingTaskGuarantee() { + mHasPendingEventsPromisedIdleEvent = false; + } + + // Update our cached idle deadline so consumers can use it while holding + // locks. Consumers must ClearCachedIdleDeadline() once they are done. + void UpdateCachedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) { + mCachedIdleDeadline = GetIdleDeadlineInternal(false, aProofOfUnlock); + } + + // Reset our cached idle deadline, so we stop allowing idle runnables to run. + void ClearCachedIdleDeadline() { mCachedIdleDeadline = TimeStamp(); } + + // Get the current cached idle deadline. This may return a null timestamp. + TimeStamp GetCachedIdleDeadline() { return mCachedIdleDeadline; } + + // Peek our current idle deadline into mCachedIdleDeadline. This can cause + // mCachedIdleDeadline to be a null timestamp (which means we are not idle + // right now). This method does not have any side-effects on our state, apart + // from guaranteeing that if it returns non-null then GetDeadlineForIdleTask + // will return non-null until ForgetPendingTaskGuarantee() is called, and its + // effects on mCachedIdleDeadline. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void CachePeekedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) { + mCachedIdleDeadline = GetIdleDeadlineInternal(true, aProofOfUnlock); + } + + void SetIdleToken(uint64_t aId, TimeDuration aDuration); + + bool IsActive() { return mActive; } + + protected: + void EnsureIsActive() { + if (!mActive) { + SetActive(); + } + } + + void EnsureIsPaused(const MutexAutoUnlock& aProofOfUnlock) { + if (mActive) { + SetPaused(aProofOfUnlock); + } + } + + // Returns a null TimeStamp if we're not in the idle period. + TimeStamp GetLocalIdleDeadline(bool& aShuttingDown, + const MutexAutoUnlock& aProofOfUnlock); + + // Gets the idle token, which is the end time of the idle period. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint, + const MutexAutoUnlock& aProofOfUnlock); + + // In case of child processes, requests idle time from the cross-process + // idle scheduler. + void RequestIdleToken(TimeStamp aLocalIdlePeriodHint); + + // Mark that we don't have idle time to use, nor are expecting to get an idle + // token from the idle scheduler. This must be called while not holding any + // locks, but some of the callers aren't holding locks to start with, so + // consumers just need to make sure they are not holding locks when they call + // this. + void ClearIdleToken(); + + // SetActive should be called when the event queue is running any type of + // tasks. + void SetActive(); + // SetPaused should be called once the event queue doesn't have more + // tasks to process, or is waiting for the idle token. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void SetPaused(const MutexAutoUnlock& aProofOfUnlock); + + // Get or peek our idle deadline. When peeking, we generally don't change any + // of our internal state. When getting, we may request an idle token as + // needed. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + TimeStamp GetIdleDeadlineInternal(bool aIsPeek, + const MutexAutoUnlock& aProofOfUnlock); + + // Whether we should be getting an idle token (i.e. are a content process + // and are using cross process idle scheduling). + bool ShouldGetIdleToken(); + + // Set to true if we have claimed we have a ready-to-run idle task when asked. + // In that case, we will ensure that we allow at least one task to run when + // someone tries to run a task, even if we have run out of idle period at that + // point. This ensures that we never fail to produce a task to run if we + // claim we have a task ready to run. + bool mHasPendingEventsPromisedIdleEvent = false; + + // mIdlePeriod keeps track of the current idle period. Calling + // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when + // the current idle period will end. + nsCOMPtr<nsIIdlePeriod> mIdlePeriod; + + // If non-null, this timestamp represents the end time of the idle period. An + // idle period starts when we get the idle token from the parent process and + // ends when either there are no more things we want to run at idle priority + // or mIdleToken < TimeStamp::Now(), so we have reached our idle deadline. + TimeStamp mIdleToken; + + // The id of the last idle request to the cross-process idle scheduler. + uint64_t mIdleRequestId = 0; + + // If we're in a content process, we use mIdleScheduler to communicate with + // the parent process for purposes of cross-process idle tracking. + RefPtr<mozilla::ipc::IdleSchedulerChild> mIdleScheduler; + + // Our cached idle deadline. This is set by UpdateCachedIdleDeadline() and + // cleared by ClearCachedIdleDeadline(). Consumers should do the former while + // not holding any locks, but may do the latter while holding locks. + TimeStamp mCachedIdleDeadline; + + // mIdleSchedulerInitialized is true if our mIdleScheduler has been + // initialized. It may be null even after initialiazation, in various + // situations. + bool mIdleSchedulerInitialized = false; + + // mActive is true when the PrioritizedEventQueue or TaskController we are + // associated with is running tasks. + bool mActive = true; +}; + +} // namespace mozilla + +#endif // mozilla_IdlePeriodState_h diff --git a/xpcom/threads/IdleTaskRunner.cpp b/xpcom/threads/IdleTaskRunner.cpp new file mode 100644 index 0000000000..404410bec8 --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.cpp @@ -0,0 +1,181 @@ +/* -*- 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 "IdleTaskRunner.h" +#include "nsRefreshDriver.h" +#include "nsComponentManagerUtils.h" + +namespace mozilla { + +already_AddRefed<IdleTaskRunner> IdleTaskRunner::Create( + const CallbackType& aCallback, const char* aRunnableName, + uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing) { + if (aMayStopProcessing && aMayStopProcessing()) { + return nullptr; + } + + RefPtr<IdleTaskRunner> runner = + new IdleTaskRunner(aCallback, aRunnableName, aMaxDelay, aNonIdleBudget, + aRepeating, aMayStopProcessing); + runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch. + return runner.forget(); +} + +IdleTaskRunner::IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing) + : CancelableIdleRunnable(aRunnableName), + mCallback(aCallback), + mDelay(aMaxDelay), + mBudget(TimeDuration::FromMilliseconds(aNonIdleBudget)), + mRepeating(aRepeating), + mTimerActive(false), + mMayStopProcessing(aMayStopProcessing), + mName(aRunnableName) {} + +NS_IMETHODIMP +IdleTaskRunner::Run() { + if (!mCallback) { + return NS_OK; + } + + // Deadline is null when called from timer. + TimeStamp now = TimeStamp::Now(); + bool deadLineWasNull = mDeadline.IsNull(); + bool didRun = false; + bool allowIdleDispatch = false; + if (deadLineWasNull || ((now + mBudget) < mDeadline)) { + CancelTimer(); + didRun = mCallback(mDeadline); + // If we didn't do meaningful work, don't schedule using immediate + // idle dispatch, since that could lead to a loop until the idle + // period ends. + allowIdleDispatch = didRun; + } else if (now >= mDeadline) { + allowIdleDispatch = true; + } + + if (mCallback && (mRepeating || !didRun)) { + Schedule(allowIdleDispatch); + } else { + mCallback = nullptr; + } + + return NS_OK; +} + +static void TimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleTaskRunner> runnable = static_cast<IdleTaskRunner*>(aClosure); + runnable->Run(); +} + +void IdleTaskRunner::SetDeadline(mozilla::TimeStamp aDeadline) { + mDeadline = aDeadline; +} + +void IdleTaskRunner::SetBudget(int64_t aBudget) { + mBudget = TimeDuration::FromMilliseconds(aBudget); +} + +void IdleTaskRunner::SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTarget->IsOnCurrentThread()); + // aTarget is always the main thread event target provided from + // NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that + // CollectorRunner always run specifically the main thread. + SetTimerInternal(aDelay); +} + +nsresult IdleTaskRunner::Cancel() { + CancelTimer(); + mTimer = nullptr; + mScheduleTimer = nullptr; + mCallback = nullptr; + return NS_OK; +} + +static void ScheduleTimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleTaskRunner> runnable = static_cast<IdleTaskRunner*>(aClosure); + runnable->Schedule(true); +} + +void IdleTaskRunner::Schedule(bool aAllowIdleDispatch) { + if (!mCallback) { + return; + } + + if (mMayStopProcessing && mMayStopProcessing()) { + Cancel(); + return; + } + + mDeadline = TimeStamp(); + TimeStamp now = TimeStamp::Now(); + TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now); + if (hint != now) { + // RefreshDriver is ticking, let it schedule the idle dispatch. + nsRefreshDriver::DispatchIdleRunnableAfterTickUnlessExists(this, mDelay); + // Ensure we get called at some point, even if RefreshDriver is stopped. + SetTimerInternal(mDelay); + } else { + // RefreshDriver doesn't seem to be running. + if (aAllowIdleDispatch) { + nsCOMPtr<nsIRunnable> runnable = this; + SetTimerInternal(mDelay); + NS_DispatchToCurrentThreadQueue(runnable.forget(), + EventQueuePriority::Idle); + } else { + if (!mScheduleTimer) { + mScheduleTimer = NS_NewTimer(); + if (!mScheduleTimer) { + return; + } + } else { + mScheduleTimer->Cancel(); + } + // We weren't allowed to do idle dispatch immediately, do it after a + // short timeout. + mScheduleTimer->InitWithNamedFuncCallback( + ScheduleTimedOut, this, 16 /* ms */, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, mName); + } + } +} + +IdleTaskRunner::~IdleTaskRunner() { CancelTimer(); } + +void IdleTaskRunner::CancelTimer() { + nsRefreshDriver::CancelIdleRunnable(this); + if (mTimer) { + mTimer->Cancel(); + } + if (mScheduleTimer) { + mScheduleTimer->Cancel(); + } + mTimerActive = false; +} + +void IdleTaskRunner::SetTimerInternal(uint32_t aDelay) { + if (mTimerActive) { + return; + } + + if (!mTimer) { + mTimer = NS_NewTimer(); + } else { + mTimer->Cancel(); + } + + if (mTimer) { + mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, mName); + mTimerActive = true; + } +} + +} // end of namespace mozilla diff --git a/xpcom/threads/IdleTaskRunner.h b/xpcom/threads/IdleTaskRunner.h new file mode 100644 index 0000000000..93a55b3d7f --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.h @@ -0,0 +1,89 @@ +/* -*- 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/. */ + +#ifndef IdleTaskRunner_h +#define IdleTaskRunner_h + +#include "mozilla/TimeStamp.h" +#include "mozilla/TaskCategory.h" +#include "nsThreadUtils.h" +#include <functional> + +namespace mozilla { + +// A general purpose repeating callback runner (it can be configured to a +// one-time runner, too.) If it is running repeatedly, one has to either +// explicitly Cancel() the runner or have MayStopProcessing() callback return +// true to completely remove the runner. +class IdleTaskRunner final : public CancelableIdleRunnable { + public: + // Return true if some meaningful work was done. + using CallbackType = std::function<bool(TimeStamp aDeadline)>; + + // A callback for "stop processing" decision. Return true to + // stop processing. This can be an alternative to Cancel() or + // work together in different way. + using MayStopProcessingCallbackType = std::function<bool()>; + + public: + // An IdleTaskRunner will attempt to run in idle time, with a budget computed + // based on a (capped) estimate for how much idle time is available. If there + // is no idle time within `aMaxDelay` ms, it will fall back to running using + // a specified `aNonIdleBudget`. + static already_AddRefed<IdleTaskRunner> Create( + const CallbackType& aCallback, const char* aRunnableName, + uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing); + + NS_IMETHOD Run() override; + + // (Used by the task triggering code.) Record the end of the current idle + // period, or null if not running during idle time. + void SetDeadline(mozilla::TimeStamp aDeadline) override; + + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override; + + // Update the non-idle time budgeted for this callback. This really only + // makes sense for a repeating runner. + void SetBudget(int64_t aBudget); + + nsresult Cancel() override; + void Schedule(bool aAllowIdleDispatch); + + private: + explicit IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + uint32_t aMaxDelay, int64_t aNonIdleBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing); + ~IdleTaskRunner(); + void CancelTimer(); + void SetTimerInternal(uint32_t aDelay); + + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsITimer> mScheduleTimer; + CallbackType mCallback; + + // Wait this long for idle time before giving up and running a non-idle + // callback. + uint32_t mDelay; + + // If running during idle time, the expected end of the current idle period. + // The null timestamp when the run is triggered by aMaxDelay instead of idle. + TimeStamp mDeadline; + + // The expected amount of time the callback will run for, when not running + // during idle time. + TimeDuration mBudget; + + bool mRepeating; + bool mTimerActive; + MayStopProcessingCallbackType mMayStopProcessing; + const char* mName; +}; + +} // end of namespace mozilla. + +#endif diff --git a/xpcom/threads/InputEventStatistics.cpp b/xpcom/threads/InputEventStatistics.cpp new file mode 100644 index 0000000000..66391d17cb --- /dev/null +++ b/xpcom/threads/InputEventStatistics.cpp @@ -0,0 +1,68 @@ +/* -*- 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 "InputEventStatistics.h" + +#include "mozilla/Preferences.h" +#include "nsRefreshDriver.h" + +namespace mozilla { + +/*static*/ InputEventStatistics& InputEventStatistics::Get() { + static UniquePtr<InputEventStatistics> sInstance; + if (!sInstance) { + sInstance = MakeUnique<InputEventStatistics>(ConstructorCookie()); + ClearOnShutdown(&sInstance); + } + return *sInstance; +} + +TimeDuration InputEventStatistics::TimeDurationCircularBuffer::GetMean() { + return mTotal / (int64_t)mSize; +} + +InputEventStatistics::InputEventStatistics(ConstructorCookie&&) + : mEnable(false) { + MOZ_ASSERT(Preferences::IsServiceAvailable()); + uint32_t inputDuration = Preferences::GetUint( + "input_event_queue.default_duration_per_event", sDefaultInputDuration); + + TimeDuration defaultDuration = TimeDuration::FromMilliseconds(inputDuration); + + uint32_t count = Preferences::GetUint( + "input_event_queue.count_for_prediction", sInputCountForPrediction); + + mLastInputDurations = + MakeUnique<TimeDurationCircularBuffer>(count, defaultDuration); + + uint32_t maxDuration = Preferences::GetUint("input_event_queue.duration.max", + sMaxReservedTimeForHandlingInput); + + uint32_t minDuration = Preferences::GetUint("input_event_queue.duration.min", + sMinReservedTimeForHandlingInput); + + mMaxInputDuration = TimeDuration::FromMilliseconds(maxDuration); + mMinInputDuration = TimeDuration::FromMilliseconds(minDuration); +} + +TimeStamp InputEventStatistics::GetInputHandlingStartTime( + uint32_t aInputCount) { + MOZ_ASSERT(mEnable); + Maybe<TimeStamp> nextTickHint = nsRefreshDriver::GetNextTickHint(); + + if (nextTickHint.isNothing()) { + // Return a past time to process input events immediately. + return TimeStamp::Now() - TimeDuration::FromMilliseconds(1); + } + TimeDuration inputCost = mLastInputDurations->GetMean() * aInputCount; + inputCost = inputCost > mMaxInputDuration ? mMaxInputDuration + : inputCost < mMinInputDuration ? mMinInputDuration + : inputCost; + + return nextTickHint.value() - inputCost; +} + +} // namespace mozilla diff --git a/xpcom/threads/InputEventStatistics.h b/xpcom/threads/InputEventStatistics.h new file mode 100644 index 0000000000..e5531302b5 --- /dev/null +++ b/xpcom/threads/InputEventStatistics.h @@ -0,0 +1,102 @@ +/* -*- 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/. */ + +#if !defined(InputEventStatistics_h_) +# define InputEventStatistics_h_ + +# include "mozilla/TimeStamp.h" +# include "mozilla/UniquePtr.h" +# include "nsTArray.h" + +namespace mozilla { + +class InputEventStatistics { + // The default amount of time (milliseconds) required for handling a input + // event. + static const uint16_t sDefaultInputDuration = 1; + + // The number of processed input events we use to predict the amount of time + // required to process the following input events. + static const uint16_t sInputCountForPrediction = 9; + + // The default maximum and minimum time (milliseconds) we reserve for handling + // input events in each frame. + static const uint16_t sMaxReservedTimeForHandlingInput = 8; + static const uint16_t sMinReservedTimeForHandlingInput = 1; + + class TimeDurationCircularBuffer { + int16_t mSize; + int16_t mCurrentIndex; + nsTArray<TimeDuration> mBuffer; + TimeDuration mTotal; + + public: + TimeDurationCircularBuffer(uint32_t aSize, TimeDuration& aDefaultValue) + : mSize(aSize), mCurrentIndex(0) { + mSize = mSize == 0 ? sInputCountForPrediction : mSize; + for (int16_t index = 0; index < mSize; ++index) { + mBuffer.AppendElement(aDefaultValue); + mTotal += aDefaultValue; + } + } + + void Insert(TimeDuration& aDuration) { + mTotal += (aDuration - mBuffer[mCurrentIndex]); + mBuffer[mCurrentIndex++] = aDuration; + if (mCurrentIndex == mSize) { + mCurrentIndex = 0; + } + } + + TimeDuration GetMean(); + }; + + UniquePtr<TimeDurationCircularBuffer> mLastInputDurations; + TimeDuration mMaxInputDuration; + TimeDuration mMinInputDuration; + bool mEnable; + + // We'd like to have our constructor and destructor be private to enforce our + // singleton, but because UniquePtr needs to be able to destruct our class we + // can't. This is a trick that ensures that we're the only code that can + // construct ourselves: nobody else can access ConstructorCookie and therefore + // nobody else can construct an InputEventStatistics. + struct ConstructorCookie {}; + + public: + explicit InputEventStatistics(ConstructorCookie&&); + ~InputEventStatistics() = default; + + static InputEventStatistics& Get(); + + void UpdateInputDuration(TimeDuration aDuration) { + if (!mEnable) { + return; + } + mLastInputDurations->Insert(aDuration); + } + + TimeStamp GetInputHandlingStartTime(uint32_t aInputCount); + + void SetEnable(bool aEnable) { mEnable = aEnable; } +}; + +class MOZ_RAII AutoTimeDurationHelper final { + public: + AutoTimeDurationHelper() { mStartTime = TimeStamp::Now(); } + + ~AutoTimeDurationHelper() { + InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() - + mStartTime); + } + + private: + TimeStamp mStartTime; +}; + +} // namespace mozilla + +#endif // InputEventStatistics_h_ diff --git a/xpcom/threads/InputTaskManager.cpp b/xpcom/threads/InputTaskManager.cpp new file mode 100644 index 0000000000..8464978268 --- /dev/null +++ b/xpcom/threads/InputTaskManager.cpp @@ -0,0 +1,77 @@ +/* -*- 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 "InputTaskManager.h" + +namespace mozilla { + +StaticRefPtr<InputTaskManager> InputTaskManager::gInputTaskManager; + +void InputTaskManager::EnableInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_DISABLED); + mInputQueueState = STATE_ENABLED; + mInputHandlingStartTime = TimeStamp(); +} + +void InputTaskManager::FlushInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_ENABLED || + mInputQueueState == STATE_SUSPEND); + mInputQueueState = + mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND; +} + +void InputTaskManager::SuspendInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_ENABLED || + mInputQueueState == STATE_FLUSHING); + mInputQueueState = STATE_SUSPEND; +} + +void InputTaskManager::ResumeInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_SUSPEND); + mInputQueueState = STATE_ENABLED; +} + +int32_t InputTaskManager::GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) { + size_t inputCount = PendingTaskCount(); + if (State() == STATE_ENABLED && InputHandlingStartTime().IsNull() && + inputCount > 0) { + SetInputHandlingStartTime( + InputEventStatistics::Get().GetInputHandlingStartTime(inputCount)); + } + + if (inputCount > 0 && (State() == InputTaskManager::STATE_FLUSHING || + (State() == InputTaskManager::STATE_ENABLED && + !InputHandlingStartTime().IsNull() && + TimeStamp::Now() > InputHandlingStartTime()))) { + return 0; + } + + int32_t modifier = static_cast<int32_t>(EventQueuePriority::InputLow) - + static_cast<int32_t>(EventQueuePriority::MediumHigh); + return modifier; +} + +void InputTaskManager::WillRunTask() { + TaskManager::WillRunTask(); + mStartTimes.AppendElement(TimeStamp::Now()); +} + +void InputTaskManager::DidRunTask() { + TaskManager::DidRunTask(); + MOZ_ASSERT(!mStartTimes.IsEmpty()); + TimeStamp start = mStartTimes.PopLastElement(); + InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() - start); +} + +// static +void InputTaskManager::Init() { gInputTaskManager = new InputTaskManager(); } + +} // namespace mozilla diff --git a/xpcom/threads/InputTaskManager.h b/xpcom/threads/InputTaskManager.h new file mode 100644 index 0000000000..ac3e44b516 --- /dev/null +++ b/xpcom/threads/InputTaskManager.h @@ -0,0 +1,97 @@ +/* -*- 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/. */ + +#ifndef mozilla_InputTaskManager_h +#define mozilla_InputTaskManager_h + +#include "TaskController.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/StaticPrefs_dom.h" + +namespace mozilla { + +class InputTaskManager : public TaskManager { + public: + int32_t GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) final; + void WillRunTask() final; + void DidRunTask() final; + + enum InputEventQueueState { + STATE_DISABLED, + STATE_FLUSHING, + STATE_SUSPEND, + STATE_ENABLED + }; + + void EnableInputEventPrioritization(); + void FlushInputEventPrioritization(); + void SuspendInputEventPrioritization(); + void ResumeInputEventPrioritization(); + + InputEventQueueState State() { return mInputQueueState; } + + void SetState(InputEventQueueState aState) { mInputQueueState = aState; } + + TimeStamp InputHandlingStartTime() { return mInputHandlingStartTime; } + + void SetInputHandlingStartTime(TimeStamp aStartTime) { + mInputHandlingStartTime = aStartTime; + } + + static InputTaskManager* Get() { return gInputTaskManager.get(); } + static void Cleanup() { gInputTaskManager = nullptr; } + static void Init(); + + bool IsSuspended(const MutexAutoLock& aProofOfLock) override { + MOZ_ASSERT(NS_IsMainThread()); + return mSuspensionLevel > 0; + } + + bool IsSuspended() { + MOZ_ASSERT(NS_IsMainThread()); + return mSuspensionLevel > 0; + } + + void IncSuspensionLevel() { + MOZ_ASSERT(NS_IsMainThread()); + ++mSuspensionLevel; + } + + void DecSuspensionLevel() { + MOZ_ASSERT(NS_IsMainThread()); + --mSuspensionLevel; + } + + static bool CanSuspendInputEvent() { + // Ensure it's content process because InputTaskManager only + // works in e10s. + // + // Input tasks will have nullptr as their task manager when the + // event queue state is STATE_DISABLED, so we can't suspend + // input events. + return XRE_IsContentProcess() && + StaticPrefs::dom_input_events_canSuspendInBCG_enabled() && + InputTaskManager::Get()->State() != + InputEventQueueState::STATE_DISABLED; + } + + private: + InputTaskManager() : mInputQueueState(STATE_DISABLED) {} + + TimeStamp mInputHandlingStartTime; + Atomic<InputEventQueueState> mInputQueueState; + AutoTArray<TimeStamp, 4> mStartTimes; + + static StaticRefPtr<InputTaskManager> gInputTaskManager; + + // Number of BCGs have asked InputTaskManager to suspend input events + uint32_t mSuspensionLevel = 0; +}; + +} // namespace mozilla + +#endif // mozilla_InputTaskManager_h diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp new file mode 100644 index 0000000000..f4f93de3ce --- /dev/null +++ b/xpcom/threads/LazyIdleThread.cpp @@ -0,0 +1,628 @@ +/* -*- 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 "LazyIdleThread.h" + +#include "nsIObserverService.h" + +#include "GeckoProfiler.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" + +#ifdef DEBUG +# define ASSERT_OWNING_THREAD() \ + do { \ + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \ + } while (0) +#else +# define ASSERT_OWNING_THREAD() /* nothing */ +#endif + +namespace mozilla { + +LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const nsACString& aName, + ShutdownMethod aShutdownMethod, + nsIObserver* aIdleObserver) + : mMutex("LazyIdleThread::mMutex"), + mOwningEventTarget(GetCurrentSerialEventTarget()), + mIdleObserver(aIdleObserver), + mQueuedRunnables(nullptr), + mIdleTimeoutMS(aIdleTimeoutMS), + mPendingEventCount(0), + mIdleNotificationCount(0), + mShutdownMethod(aShutdownMethod), + mShutdown(false), + mThreadIsShuttingDown(false), + mIdleTimeoutEnabled(true), + mName(aName) { + MOZ_ASSERT(mOwningEventTarget, "Need owning thread!"); +} + +LazyIdleThread::~LazyIdleThread() { + ASSERT_OWNING_THREAD(); + + Shutdown(); +} + +void LazyIdleThread::SetWeakIdleObserver(nsIObserver* aObserver) { + ASSERT_OWNING_THREAD(); + + if (mShutdown) { + NS_WARNING_ASSERTION(!aObserver, + "Setting an observer after Shutdown was called!"); + return; + } + + mIdleObserver = aObserver; +} + +void LazyIdleThread::DisableIdleTimeout() { + ASSERT_OWNING_THREAD(); + if (!mIdleTimeoutEnabled) { + return; + } + mIdleTimeoutEnabled = false; + + if (mIdleTimer && NS_FAILED(mIdleTimer->Cancel())) { + NS_WARNING("Failed to cancel timer!"); + } + + MutexAutoLock lock(mMutex); + + // Pretend we have a pending event to keep the idle timer from firing. + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); + mPendingEventCount++; +} + +void LazyIdleThread::EnableIdleTimeout() { + ASSERT_OWNING_THREAD(); + if (mIdleTimeoutEnabled) { + return; + } + mIdleTimeoutEnabled = true; + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); + --mPendingEventCount; + } + + if (mThread) { + nsCOMPtr<nsIRunnable> runnable(new Runnable("LazyIdleThreadDummyRunnable")); + if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch!"); + } + } +} + +void LazyIdleThread::PreDispatch() { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mPendingEventCount < UINT32_MAX, "Way too many!"); + mPendingEventCount++; +} + +nsresult LazyIdleThread::EnsureThread() { + ASSERT_OWNING_THREAD(); + + if (mShutdown) { + return NS_ERROR_UNEXPECTED; + } + + if (mThread) { + return NS_OK; + } + + MOZ_ASSERT(!mPendingEventCount, "Shouldn't have events yet!"); + MOZ_ASSERT(!mIdleNotificationCount, "Shouldn't have idle events yet!"); + MOZ_ASSERT(!mIdleTimer, "Should have killed this long ago!"); + MOZ_ASSERT(!mThreadIsShuttingDown, "Should have cleared that!"); + + nsresult rv; + + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> obs = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = obs->AddObserver(this, "xpcom-shutdown-threads", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mIdleTimer = NS_NewTimer(); + if (NS_WARN_IF(!mIdleTimer)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( + "LazyIdleThread::InitThread", this, &LazyIdleThread::InitThread); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + rv = NS_NewNamedThread(mName, getter_AddRefs(mThread), runnable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void LazyIdleThread::InitThread() { + // Happens on mThread but mThread may not be set yet... + + nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread())); + MOZ_ASSERT(thread, "This should always succeed!"); + + if (NS_FAILED(thread->SetObserver(this))) { + NS_WARNING("Failed to set thread observer!"); + } +} + +void LazyIdleThread::CleanupThread() { + nsCOMPtr<nsIThreadInternal> thread(do_QueryInterface(NS_GetCurrentThread())); + MOZ_ASSERT(thread, "This should always succeed!"); + + if (NS_FAILED(thread->SetObserver(nullptr))) { + NS_WARNING("Failed to set thread observer!"); + } + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(!mThreadIsShuttingDown, "Shouldn't be true ever!"); + mThreadIsShuttingDown = true; + } +} + +void LazyIdleThread::ScheduleTimer() { + ASSERT_OWNING_THREAD(); + + bool shouldSchedule; + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mIdleNotificationCount, "Should have at least one!"); + --mIdleNotificationCount; + + shouldSchedule = !mIdleNotificationCount && !mPendingEventCount; + } + + if (mIdleTimer) { + if (NS_FAILED(mIdleTimer->Cancel())) { + NS_WARNING("Failed to cancel timer!"); + } + + if (shouldSchedule && NS_FAILED(mIdleTimer->InitWithCallback( + this, mIdleTimeoutMS, nsITimer::TYPE_ONE_SHOT))) { + NS_WARNING("Failed to schedule timer!"); + } + } +} + +nsresult LazyIdleThread::ShutdownThread() { + ASSERT_OWNING_THREAD(); + + // Before calling Shutdown() on the real thread we need to put a queue in + // place in case a runnable is posted to the thread while it's in the + // process of shutting down. This will be our queue. + AutoTArray<nsCOMPtr<nsIRunnable>, 10> queuedRunnables; + + nsresult rv; + + // Make sure to cancel the shutdown timer before spinning the event loop + // during |mThread->Shutdown()| below. Otherwise the timer might fire and we + // could reenter here. + if (mIdleTimer) { + rv = mIdleTimer->Cancel(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mIdleTimer = nullptr; + } + + if (mThread) { + if (mShutdownMethod == AutomaticShutdown && NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + NS_WARNING_ASSERTION(obs, "Failed to get observer service!"); + + if (obs && + NS_FAILED(obs->RemoveObserver(this, "xpcom-shutdown-threads"))) { + NS_WARNING("Failed to remove observer!"); + } + } + + if (mIdleObserver) { + mIdleObserver->Observe(static_cast<nsIThread*>(this), IDLE_THREAD_TOPIC, + nullptr); + } + +#ifdef DEBUG + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mThreadIsShuttingDown, "Huh?!"); + } +#endif + + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( + "LazyIdleThread::CleanupThread", this, &LazyIdleThread::CleanupThread); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + PreDispatch(); + + rv = mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Put the temporary queue in place before calling Shutdown(). + mQueuedRunnables = &queuedRunnables; + + if (NS_FAILED(mThread->Shutdown())) { + NS_ERROR("Failed to shutdown the thread!"); + } + + // Now unset the queue. + mQueuedRunnables = nullptr; + + mThread = nullptr; + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(!mPendingEventCount, "Huh?!"); + MOZ_ASSERT(!mIdleNotificationCount, "Huh?!"); + MOZ_ASSERT(mThreadIsShuttingDown, "Huh?!"); + mThreadIsShuttingDown = false; + } + } + + // If our temporary queue has any runnables then we need to dispatch them. + if (queuedRunnables.Length()) { + // If the thread manager has gone away then these runnables will never run. + if (mShutdown) { + NS_ERROR("Runnables dispatched to LazyIdleThread will never run!"); + return NS_OK; + } + + // Re-dispatch the queued runnables. + for (uint32_t index = 0; index < queuedRunnables.Length(); index++) { + nsCOMPtr<nsIRunnable> runnable; + runnable.swap(queuedRunnables[index]); + MOZ_ASSERT(runnable, "Null runnable?!"); + + if (NS_FAILED(Dispatch(runnable.forget(), NS_DISPATCH_NORMAL))) { + NS_ERROR("Failed to re-dispatch queued runnable!"); + } + } + } + + return NS_OK; +} + +void LazyIdleThread::SelfDestruct() { + MOZ_ASSERT(mRefCnt == 1, "Bad refcount!"); + delete this; +} + +NS_IMPL_ADDREF(LazyIdleThread) + +NS_IMETHODIMP_(MozExternalRefCountType) +LazyIdleThread::Release() { + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "LazyIdleThread"); + + if (!count) { + // Stabilize refcount. + mRefCnt = 1; + + nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod( + "LazyIdleThread::SelfDestruct", this, &LazyIdleThread::SelfDestruct); + NS_WARNING_ASSERTION(runnable, "Couldn't make runnable!"); + + if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + // The only way this could fail is if we're in shutdown, and in that case + // threads should have been joined already. Deleting here isn't dangerous + // anymore because we won't spin the event loop waiting to join the + // thread. + SelfDestruct(); + } + } + + return count; +} + +NS_IMPL_QUERY_INTERFACE(LazyIdleThread, nsIThread, nsIEventTarget, + nsISerialEventTarget, nsITimerCallback, + nsIThreadObserver, nsIObserver, nsINamed) + +NS_IMETHODIMP +LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + ASSERT_OWNING_THREAD(); + nsCOMPtr<nsIRunnable> event(aEvent); // avoid leaks + + // LazyIdleThread can't always support synchronous dispatch currently. + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_UNEXPECTED; + } + + // If our thread is shutting down then we can't actually dispatch right now. + // Queue this runnable for later. + if (UseRunnableQueue()) { + mQueuedRunnables->AppendElement(event); + return NS_OK; + } + + nsresult rv = EnsureThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + PreDispatch(); + + return mThread->Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) { + if (mThread) { + return mThread->GetRunningEventDelay(aDelay, aStart); + } + *aDelay = TimeDuration(); + *aStart = TimeStamp(); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) { + if (mThread) { + return mThread->SetRunningEventDelay(aDelay, aStart); + } + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) { + if (mThread) { + return mThread->IsOnCurrentThread(aIsOnCurrentThread); + } + + *aIsOnCurrentThread = false; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +LazyIdleThread::IsOnCurrentThreadInfallible() { + if (mThread) { + return mThread->IsOnCurrentThread(); + } + + return false; +} + +NS_IMETHODIMP +LazyIdleThread::GetPRThread(PRThread** aPRThread) { + if (mThread) { + return mThread->GetPRThread(aPRThread); + } + + *aPRThread = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +LazyIdleThread::GetCanInvokeJS(bool* aCanInvokeJS) { + *aCanInvokeJS = false; + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::SetCanInvokeJS(bool aCanInvokeJS) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::GetLastLongTaskEnd(TimeStamp* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::SetNameForWakeupTelemetry(const nsACString& aName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::AsyncShutdown() { + ASSERT_OWNING_THREAD(); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::Shutdown() { + ASSERT_OWNING_THREAD(); + + mShutdown = true; + + nsresult rv = ShutdownThread(); + MOZ_ASSERT(!mThread, "Should have destroyed this by now!"); + + mIdleObserver = nullptr; + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::HasPendingEvents(bool* aHasPendingEvents) { + // This is only supposed to be called from the thread itself so it's not + // implemented here. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::HasPendingHighPriorityEvents(bool* aHasPendingEvents) { + // This is only supposed to be called from the thread itself so it's not + // implemented here. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent, + EventQueuePriority aQueue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::ProcessNextEvent(bool aMayWait, bool* aEventWasProcessed) { + // This is only supposed to be called from the thread itself so it's not + // implemented here. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this!"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LazyIdleThread::Notify(nsITimer* aTimer) { + ASSERT_OWNING_THREAD(); + + { + MutexAutoLock lock(mMutex); + + if (mPendingEventCount || mIdleNotificationCount) { + // Another event was scheduled since this timer was set. Don't do + // anything and wait for the timer to fire again. + return NS_OK; + } + } + + nsresult rv = ShutdownThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::GetName(nsACString& aName) { + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::OnDispatchedEvent() { + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::OnProcessNextEvent(nsIThreadInternal* /* aThread */, + bool /* aMayWait */) { + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, + bool aEventWasProcessed) { + bool shouldNotifyIdle; + { + MutexAutoLock lock(mMutex); + + if (aEventWasProcessed) { + MOZ_ASSERT(mPendingEventCount, "Mismatched calls to observer methods!"); + --mPendingEventCount; + } + + if (mThreadIsShuttingDown) { + // We're shutting down, no need to fire any timer. + return NS_OK; + } + + shouldNotifyIdle = !mPendingEventCount; + if (shouldNotifyIdle) { + MOZ_ASSERT(mIdleNotificationCount < UINT32_MAX, "Way too many!"); + mIdleNotificationCount++; + } + } + + if (shouldNotifyIdle) { + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( + "LazyIdleThread::ScheduleTimer", this, &LazyIdleThread::ScheduleTimer); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = + mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(mShutdownMethod == AutomaticShutdown, + "Should not receive notifications if not AutomaticShutdown!"); + MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); + + Shutdown(); + return NS_OK; +} + +NS_IMETHODIMP +LazyIdleThread::GetEventTarget(nsIEventTarget** aEventTarget) { + nsCOMPtr<nsIEventTarget> target = this; + target.forget(aEventTarget); + return NS_OK; +} + +nsIEventTarget* LazyIdleThread::EventTarget() { return this; } + +nsISerialEventTarget* LazyIdleThread::SerialEventTarget() { return this; } + +} // namespace mozilla diff --git a/xpcom/threads/LazyIdleThread.h b/xpcom/threads/LazyIdleThread.h new file mode 100644 index 0000000000..43a660b92f --- /dev/null +++ b/xpcom/threads/LazyIdleThread.h @@ -0,0 +1,219 @@ +/* -*- 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/. */ + +#ifndef mozilla_lazyidlethread_h__ +#define mozilla_lazyidlethread_h__ + +#ifndef MOZILLA_INTERNAL_API +# error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)." +#endif + +#include "nsINamed.h" +#include "nsIObserver.h" +#include "nsIThreadInternal.h" +#include "nsITimer.h" + +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsTArrayForwardDeclare.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +#define IDLE_THREAD_TOPIC "thread-shutting-down" + +namespace mozilla { + +/** + * This class provides a basic event target that creates its thread lazily and + * destroys its thread after a period of inactivity. It may be created on any + * thread but it may only be used from the thread on which it is created. If it + * is created on the main thread then it will automatically join its thread on + * XPCOM shutdown using the Observer Service. + */ +class LazyIdleThread final : public nsIThread, + public nsITimerCallback, + public nsIThreadObserver, + public nsIObserver, + public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREAD + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSITHREADOBSERVER + NS_DECL_NSIOBSERVER + NS_DECL_NSINAMED + + enum ShutdownMethod { AutomaticShutdown = 0, ManualShutdown }; + + /** + * Create a new LazyIdleThread that will destroy its thread after the given + * number of milliseconds. + */ + LazyIdleThread(uint32_t aIdleTimeoutMS, const nsACString& aName, + ShutdownMethod aShutdownMethod = AutomaticShutdown, + nsIObserver* aIdleObserver = nullptr); + + /** + * Add an observer that will be notified when the thread is idle and about to + * be shut down. The aSubject argument can be QueryInterface'd to an nsIThread + * that can be used to post cleanup events. The aTopic argument will be + * IDLE_THREAD_TOPIC, and aData will be null. The LazyIdleThread does not add + * a reference to the observer to avoid circular references as it is assumed + * to be the owner. It is the caller's responsibility to clear this observer + * if the pointer becomes invalid. + */ + void SetWeakIdleObserver(nsIObserver* aObserver); + + /** + * Disable the idle timeout for this thread. No effect if the timeout is + * already disabled. + */ + void DisableIdleTimeout(); + + /** + * Enable the idle timeout. No effect if the timeout is already enabled. + */ + void EnableIdleTimeout(); + + private: + /** + * Calls Shutdown(). + */ + ~LazyIdleThread(); + + /** + * Called just before dispatching to mThread. + */ + void PreDispatch(); + + /** + * Makes sure a valid thread lives in mThread. + */ + nsresult EnsureThread(); + + /** + * Called on mThread to set up the thread observer. + */ + void InitThread(); + + /** + * Called on mThread to clean up the thread observer. + */ + void CleanupThread(); + + /** + * Called on the main thread when mThread believes itself to be idle. Sets up + * the idle timer. + */ + void ScheduleTimer(); + + /** + * Called when we are shutting down mThread. + */ + nsresult ShutdownThread(); + + /** + * Deletes this object. Used to delay calling mThread->Shutdown() during the + * final release (during a GC, for instance). + */ + void SelfDestruct(); + + /** + * Returns true if events should be queued rather than immediately dispatched + * to mThread. Currently only happens when the thread is shutting down. + */ + bool UseRunnableQueue() { return !!mQueuedRunnables; } + + /** + * Protects data that is accessed on both threads. + */ + mozilla::Mutex mMutex; + + /** + * Touched on both threads but set before mThread is created. Used to direct + * timer events to the owning thread. + */ + nsCOMPtr<nsISerialEventTarget> mOwningEventTarget; + + /** + * Only accessed on the owning thread. Set by EnsureThread(). + */ + nsCOMPtr<nsIThread> mThread; + + /** + * Protected by mMutex. Created when mThread has no pending events and fired + * at mOwningThread. Any thread that dispatches to mThread will take ownership + * of the timer and fire a separate cancel event to the owning thread. + */ + nsCOMPtr<nsITimer> mIdleTimer; + + /** + * Idle observer. Called when the thread is about to be shut down. Released + * only when Shutdown() is called. + */ + nsIObserver* MOZ_UNSAFE_REF( + "See the documentation for SetWeakIdleObserver for " + "how the owner of LazyIdleThread should manage the " + "lifetime information of this field") mIdleObserver; + + /** + * Temporary storage for events that happen to be dispatched while we're in + * the process of shutting down our real thread. + */ + nsTArray<nsCOMPtr<nsIRunnable>>* mQueuedRunnables; + + /** + * The number of milliseconds a thread should be idle before dying. + */ + const uint32_t mIdleTimeoutMS; + + /** + * The number of events that are pending on mThread. A nonzero value means + * that the thread cannot be cleaned up. + */ + uint32_t mPendingEventCount; + + /** + * The number of times that mThread has dispatched an idle notification. Any + * timer that fires while this count is nonzero can safely be ignored as + * another timer will be on the way. + */ + uint32_t mIdleNotificationCount; + + /** + * Whether or not the thread should automatically shutdown. If the owner + * specified ManualShutdown at construction time then the owner should take + * care to call Shutdown() manually when appropriate. + */ + ShutdownMethod mShutdownMethod; + + /** + * Only accessed on the owning thread. Set to true when Shutdown() has been + * called and prevents EnsureThread() from recreating mThread. + */ + bool mShutdown; + + /** + * Set from CleanupThread and lasting until the thread has shut down. Prevents + * further idle notifications during the shutdown process. + */ + bool mThreadIsShuttingDown; + + /** + * Whether or not the idle timeout is enabled. + */ + bool mIdleTimeoutEnabled; + + /** + * Name of the thread, set on the actual thread after it gets created. + */ + nsCString mName; +}; + +} // namespace mozilla + +#endif // mozilla_lazyidlethread_h__ diff --git a/xpcom/threads/LeakRefPtr.h b/xpcom/threads/LeakRefPtr.h new file mode 100644 index 0000000000..ee260dad7e --- /dev/null +++ b/xpcom/threads/LeakRefPtr.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +/* Smart pointer which leaks its owning refcounted object by default. */ + +#ifndef LeakRefPtr_h +#define LeakRefPtr_h + +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { + +/** + * Instance of this class behaves like a raw pointer which leaks the + * resource it's owning if not explicitly released. + */ +template <class T> +class LeakRefPtr { + public: + explicit LeakRefPtr(already_AddRefed<T>&& aPtr) : mRawPtr(aPtr.take()) {} + + explicit operator bool() const { return !!mRawPtr; } + + LeakRefPtr<T>& operator=(already_AddRefed<T>&& aPtr) { + mRawPtr = aPtr.take(); + return *this; + } + + T* get() const { return mRawPtr; } + + already_AddRefed<T> take() { + T* rawPtr = mRawPtr; + mRawPtr = nullptr; + return already_AddRefed<T>(rawPtr); + } + + void release() { NS_RELEASE(mRawPtr); } + + private: + T* MOZ_OWNING_REF mRawPtr; +}; + +} // namespace mozilla + +#endif // LeakRefPtr_h diff --git a/xpcom/threads/MainThreadIdlePeriod.cpp b/xpcom/threads/MainThreadIdlePeriod.cpp new file mode 100644 index 0000000000..9080a8850e --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.cpp @@ -0,0 +1,75 @@ +/* -*- 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 "MainThreadIdlePeriod.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_idle_period.h" +#include "mozilla/dom/Document.h" +#include "VRManagerChild.h" +#include "nsRefreshDriver.h" +#include "nsThreadUtils.h" + +// The amount of idle time (milliseconds) reserved for a long idle period. +static const double kLongIdlePeriodMS = 50.0; + +// The minimum amount of time (milliseconds) required for an idle period to be +// scheduled on the main thread. N.B. layout.idle_period.time_limit adds +// padding at the end of the idle period, which makes the point in time that we +// expect to become busy again be: +// now + idle_period.min + layout.idle_period.time_limit +// or during page load +// now + idle_period.during_page_load.min + layout.idle_period.time_limit + +static const uint32_t kMaxTimerThreadBound = 5; // milliseconds +static const uint32_t kMaxTimerThreadBoundClamp = 15; // milliseconds + +namespace mozilla { + +NS_IMETHODIMP +MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aIdleDeadline); + + TimeStamp now = TimeStamp::Now(); + TimeStamp currentGuess = + now + TimeDuration::FromMilliseconds(kLongIdlePeriodMS); + + currentGuess = nsRefreshDriver::GetIdleDeadlineHint(currentGuess); + if (XRE_IsContentProcess()) { + currentGuess = gfx::VRManagerChild::GetIdleDeadlineHint(currentGuess); + } + currentGuess = NS_GetTimerDeadlineHintOnCurrentThread(currentGuess, + kMaxTimerThreadBound); + + // If the idle period is too small, then just return a null time + // to indicate we are busy. Otherwise return the actual deadline. + TimeDuration minIdlePeriod = + TimeDuration::FromMilliseconds(StaticPrefs::idle_period_min()); + bool busySoon = currentGuess.IsNull() || + (now >= (currentGuess - minIdlePeriod)) || + currentGuess < mLastIdleDeadline; + + // During page load use higher minimum idle period. + if (!busySoon && XRE_IsContentProcess() && + mozilla::dom::Document::HasRecentlyStartedForegroundLoads()) { + TimeDuration minIdlePeriod = TimeDuration::FromMilliseconds( + StaticPrefs::idle_period_during_page_load_min()); + busySoon = (now >= (currentGuess - minIdlePeriod)); + } + + if (!busySoon) { + *aIdleDeadline = mLastIdleDeadline = currentGuess; + } + + return NS_OK; +} + +/* static */ +float MainThreadIdlePeriod::GetLongIdlePeriod() { return kLongIdlePeriodMS; } + +} // namespace mozilla diff --git a/xpcom/threads/MainThreadIdlePeriod.h b/xpcom/threads/MainThreadIdlePeriod.h new file mode 100644 index 0000000000..7e382f6771 --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_mainthreadidleperiod_h +#define mozilla_dom_mainthreadidleperiod_h + +#include "mozilla/TimeStamp.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class MainThreadIdlePeriod final : public IdlePeriod { + public: + NS_DECL_NSIIDLEPERIOD + + MainThreadIdlePeriod() : mLastIdleDeadline(TimeStamp::Now()) {} + + static float GetLongIdlePeriod(); + + private: + virtual ~MainThreadIdlePeriod() = default; + + TimeStamp mLastIdleDeadline; +}; + +} // namespace mozilla + +#endif // mozilla_dom_mainthreadidleperiod_h diff --git a/xpcom/threads/MainThreadUtils.h b/xpcom/threads/MainThreadUtils.h new file mode 100644 index 0000000000..aac81e88b2 --- /dev/null +++ b/xpcom/threads/MainThreadUtils.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef MainThreadUtils_h_ +#define MainThreadUtils_h_ + +#include "nscore.h" + +class nsIThread; + +/** + * Get a reference to the main thread. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetMainThread(nsIThread** aResult); + +#ifdef MOZILLA_INTERNAL_API +// Fast access to the current thread. Do not release the returned pointer! If +// you want to use this pointer from some other thread, then you will need to +// AddRef it. Otherwise, you should only consider this pointer valid from code +// running on the current thread. +extern nsIThread* NS_GetCurrentThread(); +#endif + +#ifdef MOZILLA_INTERNAL_API +bool NS_IsMainThreadTLSInitialized(); +extern "C" { +bool NS_IsMainThread(); +} + +namespace mozilla { + +# ifdef DEBUG +void AssertIsOnMainThread(); +# else +inline void AssertIsOnMainThread() {} +# endif + +} // namespace mozilla + +#endif + +#endif // MainThreadUtils_h_ diff --git a/xpcom/threads/Monitor.h b/xpcom/threads/Monitor.h new file mode 100644 index 0000000000..ff8204b5ad --- /dev/null +++ b/xpcom/threads/Monitor.h @@ -0,0 +1,119 @@ +/* -*- 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/. */ + +#ifndef mozilla_Monitor_h +#define mozilla_Monitor_h + +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" + +namespace mozilla { + +/** + * Monitor provides a *non*-reentrant monitor: *not* a Java-style + * monitor. If your code needs support for reentrancy, use + * ReentrantMonitor instead. (Rarely should reentrancy be needed.) + * + * Instead of directly calling Monitor methods, it's safer and simpler + * to instead use the RAII wrappers MonitorAutoLock and + * MonitorAutoUnlock. + */ +class Monitor { + public: + explicit Monitor(const char* aName) + : mMutex(aName), mCondVar(mMutex, "[Monitor.mCondVar]") {} + + ~Monitor() = default; + + void Lock() { mMutex.Lock(); } + [[nodiscard]] bool TryLock() { return mMutex.TryLock(); } + void Unlock() { mMutex.Unlock(); } + + void Wait() { mCondVar.Wait(); } + CVStatus Wait(TimeDuration aDuration) { return mCondVar.Wait(aDuration); } + + void Notify() { mCondVar.Notify(); } + void NotifyAll() { mCondVar.NotifyAll(); } + + void AssertCurrentThreadOwns() const { mMutex.AssertCurrentThreadOwns(); } + + void AssertNotCurrentThreadOwns() const { + mMutex.AssertNotCurrentThreadOwns(); + } + + private: + Monitor(); + Monitor(const Monitor&); + Monitor& operator=(const Monitor&); + + Mutex mMutex; + CondVar mCondVar; +}; + +/** + * Lock the monitor for the lexical scope instances of this class are + * bound to (except for MonitorAutoUnlock in nested scopes). + * + * The monitor must be unlocked when instances of this class are + * created. + */ +class MOZ_STACK_CLASS MonitorAutoLock { + public: + explicit MonitorAutoLock(Monitor& aMonitor) : mMonitor(&aMonitor) { + mMonitor->Lock(); + } + + ~MonitorAutoLock() { mMonitor->Unlock(); } + + void Wait() { mMonitor->Wait(); } + CVStatus Wait(TimeDuration aDuration) { return mMonitor->Wait(aDuration); } + + void Notify() { mMonitor->Notify(); } + void NotifyAll() { mMonitor->NotifyAll(); } + + private: + MonitorAutoLock(); + MonitorAutoLock(const MonitorAutoLock&); + MonitorAutoLock& operator=(const MonitorAutoLock&); + static void* operator new(size_t) noexcept(true); + + friend class MonitorAutoUnlock; + + Monitor* mMonitor; +}; + +/** + * Unlock the monitor for the lexical scope instances of this class + * are bound to (except for MonitorAutoLock in nested scopes). + * + * The monitor must be locked by the current thread when instances of + * this class are created. + */ +class MOZ_STACK_CLASS MonitorAutoUnlock { + public: + explicit MonitorAutoUnlock(Monitor& aMonitor) : mMonitor(&aMonitor) { + mMonitor->Unlock(); + } + + explicit MonitorAutoUnlock(MonitorAutoLock& aMonitorLock) + : mMonitor(aMonitorLock.mMonitor) { + mMonitor->Unlock(); + } + + ~MonitorAutoUnlock() { mMonitor->Lock(); } + + private: + MonitorAutoUnlock(); + MonitorAutoUnlock(const MonitorAutoUnlock&); + MonitorAutoUnlock& operator=(const MonitorAutoUnlock&); + static void* operator new(size_t) noexcept(true); + + Monitor* mMonitor; +}; + +} // namespace mozilla + +#endif // mozilla_Monitor_h diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h new file mode 100644 index 0000000000..39d8919882 --- /dev/null +++ b/xpcom/threads/MozPromise.h @@ -0,0 +1,1697 @@ +/* -*- 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/. */ + +#if !defined(MozPromise_h_) +# define MozPromise_h_ + +# include <type_traits> +# include <utility> + +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/Monitor.h" +# include "mozilla/Mutex.h" +# include "mozilla/RefPtr.h" +# include "mozilla/UniquePtr.h" +# include "mozilla/Variant.h" +# include "nsIDirectTaskDispatcher.h" +# include "nsISerialEventTarget.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +# ifdef MOZ_WIDGET_ANDROID +# include "mozilla/jni/GeckoResultUtils.h" +# endif + +# if MOZ_DIAGNOSTIC_ASSERT_ENABLED +# define PROMISE_DEBUG +# endif + +# ifdef PROMISE_DEBUG +# define PROMISE_ASSERT MOZ_RELEASE_ASSERT +# else +# define PROMISE_ASSERT(...) \ + do { \ + } while (0) +# endif + +# if DEBUG +# include "nsPrintfCString.h" +# endif + +namespace mozilla { + +namespace dom { +class Promise; +} + +extern LazyLogModule gMozPromiseLog; + +# define PROMISE_LOG(x, ...) \ + MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__)) + +namespace detail { +template <typename F> +struct MethodTraitsHelper : MethodTraitsHelper<decltype(&F::operator())> {}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...)> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) volatile> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const volatile> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename T> +struct MethodTrait : MethodTraitsHelper<std::remove_reference_t<T>> {}; + +} // namespace detail + +template <typename MethodType> +using TakesArgument = + std::integral_constant<bool, detail::MethodTrait<MethodType>::ArgSize != 0>; + +template <typename MethodType, typename TargetType> +using ReturnTypeIs = + std::is_convertible<typename detail::MethodTrait<MethodType>::ReturnType, + TargetType>; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise; + +template <typename Return> +struct IsMozPromise : std::false_type {}; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +struct IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>> + : std::true_type {}; + +/* + * A promise manages an asynchronous request that may or may not be able to be + * fulfilled immediately. When an API returns a promise, the consumer may attach + * callbacks to be invoked (asynchronously, on a specified thread) when the + * request is either completed (resolved) or cannot be completed (rejected). + * Whereas JS promise callbacks are dispatched from Microtask checkpoints, + * MozPromises resolution/rejection make a normal round-trip through the event + * loop, which simplifies their ordering semantics relative to other native + * code. + * + * MozPromises attempt to mirror the spirit of JS Promises to the extent that + * is possible (and desirable) in C++. While the intent is that MozPromises + * feel familiar to programmers who are accustomed to their JS-implemented + * cousin, we don't shy away from imposing restrictions and adding features that + * make sense for the use cases we encounter. + * + * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then() + * call accepts resolve and reject callbacks, and returns a magic object which + * will be implicitly converted to a MozPromise::Request or a MozPromise object + * depending on how the return value is used. The magic object serves several + * purposes for the consumer. + * + * (1) When converting to a MozPromise::Request, it allows the caller to + * cancel the delivery of the resolve/reject value if it has not already + * occurred, via Disconnect() (this must be done on the target thread to + * avoid racing). + * + * (2) When converting to a MozPromise (which is called a completion promise), + * it allows promise chaining so ->Then() can be called again to attach + * more resolve and reject callbacks. If the resolve/reject callback + * returns a new MozPromise, that promise is chained to the completion + * promise, such that its resolve/reject value will be forwarded along + * when it arrives. If the resolve/reject callback returns void, the + * completion promise is resolved/rejected with the same value that was + * passed to the callback. + * + * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs + * (rather than already_AddRefed) from various methods. This is done to allow + * elegant chaining of calls without cluttering up the code with intermediate + * variables, and without introducing separate API variants for callers that + * want a return value (from, say, ->Then()) from those that don't. + * + * When IsExclusive is true, the MozPromise does a release-mode assertion that + * there is at most one call to either Then(...) or ChainTo(...). + */ + +class MozPromiseRefcountable { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable) + protected: + virtual ~MozPromiseRefcountable() = default; +}; + +class MozPromiseBase : public MozPromiseRefcountable { + public: + virtual void AssertIsDead() = 0; +}; + +template <typename T> +class MozPromiseHolder; +template <typename T> +class MozPromiseRequestHolder; +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise : public MozPromiseBase { + static const uint32_t sMagic = 0xcecace11; + + // Return a |T&&| to enable move when IsExclusive is true or + // a |const T&| to enforce copy otherwise. + template <typename T, + typename R = std::conditional_t<IsExclusive, T&&, const T&>> + static R MaybeMove(T& aX) { + return static_cast<R>(aX); + } + + public: + typedef ResolveValueT ResolveValueType; + typedef RejectValueT RejectValueType; + class ResolveOrRejectValue { + public: + template <typename ResolveValueType_> + void SetResolve(ResolveValueType_&& aResolveValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex<ResolveIndex>{}, + std::forward<ResolveValueType_>(aResolveValue)); + } + + template <typename RejectValueType_> + void SetReject(RejectValueType_&& aRejectValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex<RejectIndex>{}, + std::forward<RejectValueType_>(aRejectValue)); + } + + template <typename ResolveValueType_> + static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) { + ResolveOrRejectValue val; + val.SetResolve(std::forward<ResolveValueType_>(aResolveValue)); + return val; + } + + template <typename RejectValueType_> + static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) { + ResolveOrRejectValue val; + val.SetReject(std::forward<RejectValueType_>(aRejectValue)); + return val; + } + + bool IsResolve() const { return mValue.template is<ResolveIndex>(); } + bool IsReject() const { return mValue.template is<RejectIndex>(); } + bool IsNothing() const { return mValue.template is<NothingIndex>(); } + + const ResolveValueType& ResolveValue() const { + return mValue.template as<ResolveIndex>(); + } + ResolveValueType& ResolveValue() { + return mValue.template as<ResolveIndex>(); + } + const RejectValueType& RejectValue() const { + return mValue.template as<RejectIndex>(); + } + RejectValueType& RejectValue() { return mValue.template as<RejectIndex>(); } + + private: + enum { NothingIndex, ResolveIndex, RejectIndex }; + using Storage = Variant<Nothing, ResolveValueType, RejectValueType>; + Storage mValue = Storage(VariantIndex<NothingIndex>{}); + }; + + protected: + // MozPromise is the public type, and never constructed directly. Construct + // a MozPromise::Private, defined below. + MozPromise(const char* aCreationSite, bool aIsCompletionPromise) + : mCreationSite(aCreationSite), + mMutex("MozPromise Mutex"), + mHaveRequest(false), + mIsCompletionPromise(aIsCompletionPromise) +# ifdef PROMISE_DEBUG + , + mMagic4(&mMutex) +# endif + { + PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this); + } + + public: + // MozPromise::Private allows us to separate the public interface (upon which + // consumers of the promise may invoke methods like Then()) from the private + // interface (upon which the creator of the promise may invoke Resolve() or + // Reject()). APIs should create and store a MozPromise::Private (usually + // via a MozPromiseHolder), and return a MozPromise to consumers. + // + // NB: We can include the definition of this class inline once B2G ICS is + // gone. + class Private; + + template <typename ResolveValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndResolve( + ResolveValueType_&& aResolveValue, const char* aResolveSite) { + static_assert(std::is_convertible_v<ResolveValueType_, ResolveValueT>, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + RefPtr<typename MozPromise::Private> p = + new MozPromise::Private(aResolveSite); + p->Resolve(std::forward<ResolveValueType_>(aResolveValue), aResolveSite); + return p; + } + + template <typename RejectValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndReject( + RejectValueType_&& aRejectValue, const char* aRejectSite) { + static_assert(std::is_convertible_v<RejectValueType_, RejectValueT>, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + RefPtr<typename MozPromise::Private> p = + new MozPromise::Private(aRejectSite); + p->Reject(std::forward<RejectValueType_>(aRejectValue), aRejectSite); + return p; + } + + template <typename ResolveOrRejectValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndResolveOrReject( + ResolveOrRejectValueType_&& aValue, const char* aSite) { + RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aSite); + p->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), aSite); + return p; + } + + typedef MozPromise<CopyableTArray<ResolveValueType>, RejectValueType, + IsExclusive> + AllPromiseType; + + typedef MozPromise<CopyableTArray<ResolveOrRejectValue>, bool, IsExclusive> + AllSettledPromiseType; + + private: + class AllPromiseHolder : public MozPromiseRefcountable { + public: + explicit AllPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllPromiseType::Private(__func__)), + mOutstandingPromises(aDependentPromises) { + MOZ_ASSERT(aDependentPromises > 0); + mResolveValues.SetLength(aDependentPromises); + } + + void Resolve(size_t aIndex, ResolveValueType&& aResolveValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mResolveValues[aIndex].emplace(std::move(aResolveValue)); + if (--mOutstandingPromises == 0) { + nsTArray<ResolveValueType> resolveValues; + resolveValues.SetCapacity(mResolveValues.Length()); + for (auto&& resolveValue : mResolveValues) { + resolveValues.AppendElement(std::move(resolveValue.ref())); + } + + mPromise->Resolve(std::move(resolveValues), __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + } + + void Reject(RejectValueType&& aRejectValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mPromise->Reject(std::move(aRejectValue), __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + + AllPromiseType* Promise() { return mPromise; } + + private: + nsTArray<Maybe<ResolveValueType>> mResolveValues; + RefPtr<typename AllPromiseType::Private> mPromise; + size_t mOutstandingPromises; + }; + + // Trying to pass ResolveOrRejectValue by value fails static analysis checks, + // so we need to use either a const& or an rvalue reference, depending on + // whether IsExclusive is true or not. + typedef std::conditional_t<IsExclusive, ResolveOrRejectValue&&, + const ResolveOrRejectValue&> + ResolveOrRejectValueParam; + + class AllSettledPromiseHolder : public MozPromiseRefcountable { + public: + explicit AllSettledPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllSettledPromiseType::Private(__func__)), + mOutstandingPromises(aDependentPromises) { + MOZ_ASSERT(aDependentPromises > 0); + mValues.SetLength(aDependentPromises); + } + + void Settle(size_t aIndex, ResolveOrRejectValueParam aValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mValues[aIndex].emplace(MaybeMove(aValue)); + if (--mOutstandingPromises == 0) { + nsTArray<ResolveOrRejectValue> values; + values.SetCapacity(mValues.Length()); + for (auto&& value : mValues) { + values.AppendElement(std::move(value.ref())); + } + + mPromise->Resolve(std::move(values), __func__); + mPromise = nullptr; + mValues.Clear(); + } + } + + AllSettledPromiseType* Promise() { return mPromise; } + + private: + nsTArray<Maybe<ResolveOrRejectValue>> mValues; + RefPtr<typename AllSettledPromiseType::Private> mPromise; + size_t mOutstandingPromises; + }; + + public: + [[nodiscard]] static RefPtr<AllPromiseType> All( + nsISerialEventTarget* aProcessingTarget, + nsTArray<RefPtr<MozPromise>>& aPromises) { + if (aPromises.Length() == 0) { + return AllPromiseType::CreateAndResolve( + CopyableTArray<ResolveValueType>(), __func__); + } + + RefPtr<AllPromiseHolder> holder = new AllPromiseHolder(aPromises.Length()); + RefPtr<AllPromiseType> promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then( + aProcessingTarget, __func__, + [holder, i](ResolveValueType aResolveValue) -> void { + holder->Resolve(i, std::move(aResolveValue)); + }, + [holder](RejectValueType aRejectValue) -> void { + holder->Reject(std::move(aRejectValue)); + }); + } + return promise; + } + + [[nodiscard]] static RefPtr<AllSettledPromiseType> AllSettled( + nsISerialEventTarget* aProcessingTarget, + nsTArray<RefPtr<MozPromise>>& aPromises) { + if (aPromises.Length() == 0) { + return AllSettledPromiseType::CreateAndResolve( + CopyableTArray<ResolveOrRejectValue>(), __func__); + } + + RefPtr<AllSettledPromiseHolder> holder = + new AllSettledPromiseHolder(aPromises.Length()); + RefPtr<AllSettledPromiseType> promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then(aProcessingTarget, __func__, + [holder, i](ResolveOrRejectValueParam aValue) -> void { + holder->Settle(i, MaybeMove(aValue)); + }); + } + return promise; + } + + class Request : public MozPromiseRefcountable { + public: + virtual void Disconnect() = 0; + + protected: + Request() : mComplete(false), mDisconnected(false) {} + virtual ~Request() = default; + + bool mComplete; + bool mDisconnected; + }; + + protected: + /* + * A ThenValue tracks a single consumer waiting on the promise. When a + * consumer invokes promise->Then(...), a ThenValue is created. Once the + * Promise is resolved or rejected, a {Resolve,Reject}Runnable is dispatched, + * which invokes the resolve/reject method and then deletes the ThenValue. + */ + class ThenValueBase : public Request { + friend class MozPromise; + static const uint32_t sMagic = 0xfadece11; + + public: + class ResolveOrRejectRunnable : public CancelableRunnable { + public: + ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise) + : CancelableRunnable( + "MozPromise::ThenValueBase::ResolveOrRejectRunnable"), + mThenValue(aThenValue), + mPromise(aPromise) { + MOZ_DIAGNOSTIC_ASSERT(!mPromise->IsPending()); + } + + ~ResolveOrRejectRunnable() { + if (mThenValue) { + mThenValue->AssertIsDead(); + } + } + + NS_IMETHOD Run() override { + PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this); + mThenValue->DoResolveOrReject(mPromise->Value()); + mThenValue = nullptr; + mPromise = nullptr; + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<ThenValueBase> mThenValue; + RefPtr<MozPromise> mPromise; + }; + + ThenValueBase(nsISerialEventTarget* aResponseTarget, const char* aCallSite) + : mResponseTarget(aResponseTarget), mCallSite(aCallSite) { + MOZ_ASSERT(aResponseTarget); + } + +# ifdef PROMISE_DEBUG + ~ThenValueBase() { + mMagic1 = 0; + mMagic2 = 0; + } +# endif + + void AssertIsDead() { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + // We want to assert that this ThenValues is dead - that is to say, that + // there are no consumers waiting for the result. In the case of a normal + // ThenValue, we check that it has been disconnected, which is the way + // that the consumer signals that it no longer wishes to hear about the + // result. If this ThenValue has a completion promise (which is mutually + // exclusive with being disconnectable), we recursively assert that every + // ThenValue associated with the completion promise is dead. + if (MozPromiseBase* p = CompletionPromise()) { + p->AssertIsDead(); + } else { + MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected); + } + } + + void Dispatch(MozPromise* aPromise) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + aPromise->mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!aPromise->IsPending()); + + nsCOMPtr<nsIRunnable> r = new ResolveOrRejectRunnable(this, aPromise); + PROMISE_LOG( + "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p] " + "%s dispatch", + aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite, + r.get(), aPromise, this, + aPromise->mUseSynchronousTaskDispatch ? "synchronous" + : aPromise->mUseDirectTaskDispatch ? "directtask" + : "normal"); + + if (aPromise->mUseSynchronousTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG("ThenValue::Dispatch running task synchronously [this=%p]", + this); + r->Run(); + return; + } + + if (aPromise->mUseDirectTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG( + "ThenValue::Dispatch dispatch task via direct task queue [this=%p]", + this); + nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = + do_QueryInterface(mResponseTarget); + if (dispatcher) { + dispatcher->DispatchDirectTask(r.forget()); + return; + } + NS_WARNING( + nsPrintfCString( + "Direct Task dispatching not available for thread \"%s\"", + PR_GetThreadName(PR_GetCurrentThread())) + .get()); + MOZ_DIAGNOSTIC_ASSERT( + false, + "mResponseTarget must implement nsIDirectTaskDispatcher for direct " + "task dispatching"); + } + + // Promise consumers are allowed to disconnect the Request object and + // then shut down the thread or task queue that the promise result would + // be dispatched on. So we unfortunately can't assert that promise + // dispatch succeeds. :-( + mResponseTarget->Dispatch(r.forget()); + } + + void Disconnect() override { + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); + MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete); + Request::mDisconnected = true; + + // We could support rejecting the completion promise on disconnection, but + // then we'd need to have some sort of default reject value. The use cases + // of disconnection and completion promise chaining seem pretty + // orthogonal, so let's use assert against it. + MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise()); + } + + protected: + virtual MozPromiseBase* CompletionPromise() const = 0; + virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0; + + void DoResolveOrReject(ResolveOrRejectValue& aValue) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); + Request::mComplete = true; + if (Request::mDisconnected) { + PROMISE_LOG( + "ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", + this); + return; + } + + // Invoke the resolve or reject method. + DoResolveOrRejectInternal(aValue); + } + + nsCOMPtr<nsISerialEventTarget> + mResponseTarget; // May be released on any thread. +# ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +# endif + const char* mCallSite; +# ifdef PROMISE_DEBUG + uint32_t mMagic2 = sMagic; +# endif + }; + + /* + * We create two overloads for invoking Resolve/Reject Methods so as to + * make the resolve/reject value argument "optional". + */ + template <typename ThisType, typename MethodType, typename ValueType> + static std::enable_if_t<TakesArgument<MethodType>::value, + typename detail::MethodTrait<MethodType>::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(std::forward<ValueType>(aValue)); + } + + template <typename ThisType, typename MethodType, typename ValueType> + static std::enable_if_t<!TakesArgument<MethodType>::value, + typename detail::MethodTrait<MethodType>::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(); + } + + // Called when promise chaining is supported. + template <bool SupportChaining, typename ThisType, typename MethodType, + typename ValueType, typename CompletionPromiseType> + static std::enable_if_t<SupportChaining, void> InvokeCallbackMethod( + ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, + CompletionPromiseType&& aCompletionPromise) { + auto p = InvokeMethod(aThisVal, aMethod, std::forward<ValueType>(aValue)); + if (aCompletionPromise) { + p->ChainTo(aCompletionPromise.forget(), "<chained completion promise>"); + } + } + + // Called when promise chaining is not supported. + template <bool SupportChaining, typename ThisType, typename MethodType, + typename ValueType, typename CompletionPromiseType> + static std::enable_if_t<!SupportChaining, void> InvokeCallbackMethod( + ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, + CompletionPromiseType&& aCompletionPromise) { + MOZ_DIAGNOSTIC_ASSERT( + !aCompletionPromise, + "Can't do promise chaining for a non-promise-returning method."); + InvokeMethod(aThisVal, aMethod, std::forward<ValueType>(aValue)); + } + + template <typename> + class ThenCommand; + + template <typename...> + class ThenValue; + + template <typename ThisType, typename ResolveMethodType, + typename RejectMethodType> + class ThenValue<ThisType*, ResolveMethodType, RejectMethodType> + : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveMethodType>::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait<RejectMethodType>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value && + std::is_same_v<R1, R2>>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, + ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite), + mThisVal(aThisVal), + mResolveMethod(aResolveMethod), + mRejectMethod(aRejectMethod) {} + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Null out our refcounted + // this-value now so that it's released predictably on the dispatch + // thread. + mThisVal = nullptr; + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + if (aValue.IsResolve()) { + InvokeCallbackMethod<SupportChaining::value>( + mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()), + std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod<SupportChaining::value>( + mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()), + std::move(mCompletionPromise)); + } + + // Null out mThisVal after invoking the callback so that any references + // are released predictably on the dispatch thread. Otherwise, it would be + // released on whatever thread last drops its reference to the ThenValue, + // which may or may not be ok. + mThisVal = nullptr; + } + + private: + RefPtr<ThisType> + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + template <typename ThisType, typename ResolveRejectMethodType> + class ThenValue<ThisType*, ResolveRejectMethodType> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer<typename detail::MethodTrait< + ResolveRejectMethodType>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, + ResolveRejectMethodType aResolveRejectMethod, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite), + mThisVal(aThisVal), + mResolveRejectMethod(aResolveRejectMethod) {} + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Null out our refcounted + // this-value now so that it's released predictably on the dispatch + // thread. + mThisVal = nullptr; + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + InvokeCallbackMethod<SupportChaining::value>( + mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue), + std::move(mCompletionPromise)); + + // Null out mThisVal after invoking the callback so that any references + // are released predictably on the dispatch thread. Otherwise, it would be + // released on whatever thread last drops its reference to the ThenValue, + // which may or may not be ok. + mThisVal = nullptr; + } + + private: + RefPtr<ThisType> + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveRejectMethodType mResolveRejectMethod; + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + // NB: We could use std::function here instead of a template if it were + // supported. :-( + template <typename ResolveFunction, typename RejectFunction> + class ThenValue<ResolveFunction, RejectFunction> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveFunction>::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait<RejectFunction>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value && + std::is_same_v<R1, R2>>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, + ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction, const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) { + mResolveFunction.emplace(std::move(aResolveFunction)); + mRejectFunction.emplace(std::move(aRejectFunction)); + } + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Destroy our callbacks + // now so that any references in closures are released predictable on + // the dispatch thread. + mResolveFunction.reset(); + mRejectFunction.reset(); + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + // Note: The usage of InvokeCallbackMethod here requires that + // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous + // classes with ::operator()), since it allows us to share code more + // easily. We could fix this if need be, though it's quite easy to work + // around by just capturing something. + if (aValue.IsResolve()) { + InvokeCallbackMethod<SupportChaining::value>( + mResolveFunction.ptr(), &ResolveFunction::operator(), + MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod<SupportChaining::value>( + mRejectFunction.ptr(), &RejectFunction::operator(), + MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise)); + } + + // Destroy callbacks after invocation so that any references in closures + // are released predictably on the dispatch thread. Otherwise, they would + // be released on whatever thread last drops its reference to the + // ThenValue, which may or may not be ok. + mResolveFunction.reset(); + mRejectFunction.reset(); + } + + private: + Maybe<ResolveFunction> + mResolveFunction; // Only accessed and deleted on dispatch thread. + Maybe<RejectFunction> + mRejectFunction; // Only accessed and deleted on dispatch thread. + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + template <typename ResolveRejectFunction> + class ThenValue<ResolveRejectFunction> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveRejectFunction>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, + ResolveRejectFunction&& aResolveRejectFunction, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) { + mResolveRejectFunction.emplace(std::move(aResolveRejectFunction)); + } + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Destroy our callbacks + // now so that any references in closures are released predictable on + // the dispatch thread. + mResolveRejectFunction.reset(); + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + // Note: The usage of InvokeCallbackMethod here requires that + // ResolveRejectFunction is capture-lambdas (i.e. anonymous + // classes with ::operator()), since it allows us to share code more + // easily. We could fix this if need be, though it's quite easy to work + // around by just capturing something. + InvokeCallbackMethod<SupportChaining::value>( + mResolveRejectFunction.ptr(), &ResolveRejectFunction::operator(), + MaybeMove(aValue), std::move(mCompletionPromise)); + + // Destroy callbacks after invocation so that any references in closures + // are released predictably on the dispatch thread. Otherwise, they would + // be released on whatever thread last drops its reference to the + // ThenValue, which may or may not be ok. + mResolveRejectFunction.reset(); + } + + private: + Maybe<ResolveRejectFunction> + mResolveRejectFunction; // Only accessed and deleted on dispatch + // thread. + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + public: + void ThenInternal(already_AddRefed<ThenValueBase> aThenValue, + const char* aCallSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + RefPtr<ThenValueBase> thenValue = aThenValue; + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT( + !IsExclusive || !mHaveRequest, + "Using an exclusive promise in a non-exclusive fashion"); + mHaveRequest = true; + PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]", + aCallSite, this, thenValue.get(), (int)IsPending()); + if (!IsPending()) { + thenValue->Dispatch(this); + } else { + mThenValues.AppendElement(thenValue.forget()); + } + } + + protected: + /* + * A command object to store all information needed to make a request to + * the promise. This allows us to delay the request until further use is + * known (whether it is ->Then() again for more promise chaining or ->Track() + * to terminate chaining and issue the request). + * + * This allows a unified syntax for promise chaining and disconnection + * and feels more like its JS counterpart. + */ + template <typename ThenValueType> + class ThenCommand { + // Allow Promise1::ThenCommand to access the private constructor, + // Promise2::ThenCommand(ThenCommand&&). + template <typename, typename, bool> + friend class MozPromise; + + using PromiseType = typename ThenValueType::PromiseType; + using Private = typename PromiseType::Private; + + ThenCommand(const char* aCallSite, + already_AddRefed<ThenValueType> aThenValue, + MozPromise* aReceiver) + : mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {} + + ThenCommand(ThenCommand&& aOther) = default; + + public: + ~ThenCommand() { + // Issue the request now if the return value of Then() is not used. + if (mThenValue) { + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + } + } + + // Allow RefPtr<MozPromise> p = somePromise->Then(); + // p->Then(thread1, ...); + // p->Then(thread2, ...); + operator RefPtr<PromiseType>() { + static_assert( + ThenValueType::SupportChaining::value, + "The resolve/reject callback needs to return a RefPtr<MozPromise> " + "in order to do promise chaining."); + + // mCompletionPromise must be created before ThenInternal() to avoid race. + RefPtr<Private> p = + new Private("<completion promise>", true /* aIsCompletionPromise */); + mThenValue->mCompletionPromise = p; + // Note ThenInternal() might nullify mCompletionPromise before return. + // So we need to return p instead of mCompletionPromise. + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + return p; + } + + template <typename... Ts> + auto Then(Ts&&... aArgs) -> decltype( + std::declval<PromiseType>().Then(std::forward<Ts>(aArgs)...)) { + return static_cast<RefPtr<PromiseType>>(*this)->Then( + std::forward<Ts>(aArgs)...); + } + + void Track(MozPromiseRequestHolder<MozPromise>& aRequestHolder) { + aRequestHolder.Track(do_AddRef(mThenValue)); + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + } + + // Allow calling ->Then() again for more promise chaining or ->Track() to + // end chaining and track the request for future disconnection. + ThenCommand* operator->() { return this; } + + private: + const char* mCallSite; + RefPtr<ThenValueType> mThenValue; + RefPtr<MozPromise> mReceiver; + }; + + public: + template <typename ThisType, typename... Methods, + typename ThenValueType = ThenValue<ThisType*, Methods...>, + typename ReturnType = ThenCommand<ThenValueType>> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + ThisType* aThisVal, Methods... aMethods) { + RefPtr<ThenValueType> thenValue = + new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + template <typename... Functions, + typename ThenValueType = ThenValue<Functions...>, + typename ReturnType = ThenCommand<ThenValueType>> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + Functions&&... aFunctions) { + RefPtr<ThenValueType> thenValue = + new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + void ChainTo(already_AddRefed<Private> aChainedPromise, + const char* aCallSite) { + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT( + !IsExclusive || !mHaveRequest, + "Using an exclusive promise in a non-exclusive fashion"); + mHaveRequest = true; + RefPtr<Private> chainedPromise = aChainedPromise; + PROMISE_LOG( + "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]", + aCallSite, this, chainedPromise.get(), (int)IsPending()); + + // We want to use the same type of dispatching method with the chained + // promises. + + // We need to ensure that the UseSynchronousTaskDispatch branch isn't taken + // at compilation time to ensure we're not triggering the static_assert in + // UseSynchronousTaskDispatch method. if constexpr (IsExclusive) ensures + // that. + if (mUseDirectTaskDispatch) { + chainedPromise->UseDirectTaskDispatch(aCallSite); + } else if constexpr (IsExclusive) { + if (mUseSynchronousTaskDispatch) { + chainedPromise->UseSynchronousTaskDispatch(aCallSite); + } + } + + if (!IsPending()) { + ForwardTo(chainedPromise); + } else { + mChainedPromises.AppendElement(chainedPromise); + } + } + +# ifdef MOZ_WIDGET_ANDROID + // Creates a C++ MozPromise from its Java counterpart, GeckoResult. + [[nodiscard]] static RefPtr<MozPromise> FromGeckoResult( + java::GeckoResult::Param aGeckoResult) { + using jni::GeckoResultCallback; + RefPtr<Private> p = new Private("GeckoResult Glue", false); + auto resolve = GeckoResultCallback::CreateAndAttach<ResolveValueType>( + [p](ResolveValueType aArg) { p->Resolve(aArg, __func__); }); + auto reject = GeckoResultCallback::CreateAndAttach<RejectValueType>( + [p](RejectValueType aArg) { p->Reject(aArg, __func__); }); + aGeckoResult->NativeThen(resolve, reject); + return p; + } +# endif + + // Creates a C++ MozPromise from its JS counterpart, dom::Promise. + // FromDomPromise currently only supports primitive types (int8/16/32, float, + // double) And the reject value type must be a nsresult. + // To use, please include MozPromiseInlines.h + static RefPtr<MozPromise> FromDomPromise(dom::Promise* aDOMPromise); + + // Note we expose the function AssertIsDead() instead of IsDead() since + // checking IsDead() is a data race in the situation where the request is not + // dead. Therefore we enforce the form |Assert(IsDead())| by exposing + // AssertIsDead() only. + void AssertIsDead() override { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + for (auto&& then : mThenValues) { + then->AssertIsDead(); + } + for (auto&& chained : mChainedPromises) { + chained->AssertIsDead(); + } + } + + protected: + bool IsPending() const { return mValue.IsNothing(); } + + ResolveOrRejectValue& Value() { + // This method should only be called once the value has stabilized. As + // such, we don't need to acquire the lock here. + MOZ_DIAGNOSTIC_ASSERT(!IsPending()); + return mValue; + } + + void DispatchAll() { + mMutex.AssertCurrentThreadOwns(); + for (auto&& thenValue : mThenValues) { + thenValue->Dispatch(this); + } + mThenValues.Clear(); + + for (auto&& chainedPromise : mChainedPromises) { + ForwardTo(chainedPromise); + } + mChainedPromises.Clear(); + } + + void ForwardTo(Private* aOther) { + MOZ_ASSERT(!IsPending()); + if (mValue.IsResolve()) { + aOther->Resolve(MaybeMove(mValue.ResolveValue()), "<chained promise>"); + } else { + aOther->Reject(MaybeMove(mValue.RejectValue()), "<chained promise>"); + } + } + + virtual ~MozPromise() { + PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this); + AssertIsDead(); + // We can't guarantee a completion promise will always be revolved or + // rejected since ResolveOrRejectRunnable might not run when dispatch fails. + if (!mIsCompletionPromise) { + MOZ_ASSERT(!IsPending()); + MOZ_ASSERT(mThenValues.IsEmpty()); + MOZ_ASSERT(mChainedPromises.IsEmpty()); + } +# ifdef PROMISE_DEBUG + mMagic1 = 0; + mMagic2 = 0; + mMagic3 = 0; + mMagic4 = nullptr; +# endif + }; + + const char* mCreationSite; // For logging + Mutex mMutex; + ResolveOrRejectValue mValue; + bool mUseSynchronousTaskDispatch = false; + bool mUseDirectTaskDispatch = false; +# ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +# endif + // Try shows we never have more than 3 elements when IsExclusive is false. + // So '3' is a good value to avoid heap allocation in most cases. + AutoTArray<RefPtr<ThenValueBase>, IsExclusive ? 1 : 3> mThenValues; +# ifdef PROMISE_DEBUG + uint32_t mMagic2 = sMagic; +# endif + nsTArray<RefPtr<Private>> mChainedPromises; +# ifdef PROMISE_DEBUG + uint32_t mMagic3 = sMagic; +# endif + bool mHaveRequest; + const bool mIsCompletionPromise; +# ifdef PROMISE_DEBUG + void* mMagic4; +# endif +}; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private + : public MozPromise<ResolveValueT, RejectValueT, IsExclusive> { + public: + explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false) + : MozPromise(aCreationSite, aIsCompletionPromise) {} + + template <typename ResolveValueT_> + void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, + this, mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aResolveSite, this, mCreationSite); + return; + } + mValue.SetResolve(std::forward<ResolveValueT_>(aResolveValue)); + DispatchAll(); + } + + template <typename RejectValueT_> + void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, + mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aRejectSite, this, mCreationSite); + return; + } + mValue.SetReject(std::forward<RejectValueT_>(aRejectValue)); + DispatchAll(); + } + + template <typename ResolveOrRejectValue_> + void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, + this, mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aSite, this, mCreationSite); + return; + } + mValue = std::forward<ResolveOrRejectValue_>(aValue); + DispatchAll(); + } + + // If the caller and target are both on the same thread, run the the resolve + // or reject callback synchronously. Otherwise, the task will be dispatched + // via the target Dispatch method. + void UseSynchronousTaskDispatch(const char* aSite) { + static_assert( + IsExclusive, + "Synchronous dispatch can only be used with exclusive promises"); + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)", + aSite, this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + mUseSynchronousTaskDispatch = true; + } + + // If the caller and target are both on the same thread, run the + // resolve/reject callback off the direct task queue instead. This avoids a + // full trip to the back of the event queue for each additional asynchronous + // step when using MozPromise, and is similar (but not identical to) the + // microtask semantics of JS promises. + void UseDirectTaskDispatch(const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite, + this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + MOZ_ASSERT(!mUseSynchronousTaskDispatch, + "Promise already set for synchronous dispatch"); + mUseDirectTaskDispatch = true; + } +}; + +// A generic promise type that does the trick for simple use cases. +typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> GenericPromise; + +// A generic, non-exclusive promise type that does the trick for simple use +// cases. +typedef MozPromise<bool, nsresult, /* IsExclusive = */ false> + GenericNonExclusivePromise; + +/* + * Class to encapsulate a promise for a particular role. Use this as the member + * variable for a class whose method returns a promise. + */ +template <typename PromiseType, typename ImplType> +class MozPromiseHolderBase { + public: + MozPromiseHolderBase() = default; + + MozPromiseHolderBase(MozPromiseHolderBase&& aOther) = default; + MozPromiseHolderBase& operator=(MozPromiseHolderBase&& aOther) = default; + + ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); } + + already_AddRefed<PromiseType> Ensure(const char* aMethodName) { + static_cast<ImplType*>(this)->Check(); + if (!mPromise) { + mPromise = new (typename PromiseType::Private)(aMethodName); + } + RefPtr<PromiseType> p = mPromise.get(); + return p.forget(); + } + + bool IsEmpty() const { + static_cast<const ImplType*>(this)->Check(); + return !mPromise; + } + + already_AddRefed<typename PromiseType::Private> Steal() { + static_cast<ImplType*>(this)->Check(); + return mPromise.forget(); + } + + template <typename ResolveValueType_> + void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) { + static_assert(std::is_convertible_v<ResolveValueType_, + typename PromiseType::ResolveValueType>, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Resolve(std::forward<ResolveValueType_>(aResolveValue), + aMethodName); + mPromise = nullptr; + } + + template <typename ResolveValueType_> + void ResolveIfExists(ResolveValueType_&& aResolveValue, + const char* aMethodName) { + if (!IsEmpty()) { + Resolve(std::forward<ResolveValueType_>(aResolveValue), aMethodName); + } + } + + template <typename RejectValueType_> + void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) { + static_assert(std::is_convertible_v<RejectValueType_, + typename PromiseType::RejectValueType>, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName); + mPromise = nullptr; + } + + template <typename RejectValueType_> + void RejectIfExists(RejectValueType_&& aRejectValue, + const char* aMethodName) { + if (!IsEmpty()) { + Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName); + } + } + + template <typename ResolveOrRejectValueType_> + void ResolveOrReject(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), + aMethodName); + mPromise = nullptr; + } + + template <typename ResolveOrRejectValueType_> + void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + if (!IsEmpty()) { + ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), + aMethodName); + } + } + + void UseSynchronousTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseSynchronousTaskDispatch(aSite); + } + + void UseDirectTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseDirectTaskDispatch(aSite); + } + + private: + RefPtr<typename PromiseType::Private> mPromise; +}; + +template <typename PromiseType> +class MozPromiseHolder + : public MozPromiseHolderBase<PromiseType, MozPromiseHolder<PromiseType>> { + public: + using MozPromiseHolderBase< + PromiseType, MozPromiseHolder<PromiseType>>::MozPromiseHolderBase; + static constexpr void Check(){}; +}; + +template <typename PromiseType> +class MozMonitoredPromiseHolder + : public MozPromiseHolderBase<PromiseType, + MozMonitoredPromiseHolder<PromiseType>> { + public: + // Provide a Monitor that should always be held when accessing this instance. + explicit MozMonitoredPromiseHolder(Monitor* const aMonitor) + : mMonitor(aMonitor) { + MOZ_ASSERT(aMonitor); + } + + MozMonitoredPromiseHolder(MozMonitoredPromiseHolder&& aOther) = delete; + MozMonitoredPromiseHolder& operator=(MozMonitoredPromiseHolder&& aOther) = + delete; + + void Check() const { mMonitor->AssertCurrentThreadOwns(); } + + private: + Monitor* const mMonitor; +}; + +/* + * Class to encapsulate a MozPromise::Request reference. Use this as the member + * variable for a class waiting on a MozPromise. + */ +template <typename PromiseType> +class MozPromiseRequestHolder { + public: + MozPromiseRequestHolder() = default; + ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); } + + void Track(already_AddRefed<typename PromiseType::Request> aRequest) { + MOZ_DIAGNOSTIC_ASSERT(!Exists()); + mRequest = aRequest; + } + + void Complete() { + MOZ_DIAGNOSTIC_ASSERT(Exists()); + mRequest = nullptr; + } + + // Disconnects and forgets an outstanding promise. The resolve/reject methods + // will never be called. + void Disconnect() { + MOZ_ASSERT(Exists()); + mRequest->Disconnect(); + mRequest = nullptr; + } + + void DisconnectIfExists() { + if (Exists()) { + Disconnect(); + } + } + + bool Exists() const { return !!mRequest; } + + private: + RefPtr<typename PromiseType::Request> mRequest; +}; + +// Asynchronous Potentially-Cross-Thread Method Calls. +// +// This machinery allows callers to schedule a promise-returning function +// (a method and object, or a function object like a lambda) to be invoked +// asynchronously on a given thread, while at the same time receiving a +// promise upon which to invoke Then() immediately. InvokeAsync dispatches a +// task to invoke the function on the proper thread and also chain the +// resulting promise to the one that the caller received, so that resolve/ +// reject values are forwarded through. + +namespace detail { + +// Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause +// assertions when used on templated types. +class MethodCallBase { + public: + MOZ_COUNTED_DEFAULT_CTOR(MethodCallBase) + MOZ_COUNTED_DTOR_VIRTUAL(MethodCallBase) +}; + +template <typename PromiseType, typename MethodType, typename ThisType, + typename... Storages> +class MethodCall : public MethodCallBase { + public: + template <typename... Args> + MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs) + : mMethod(aMethod), + mThisVal(aThisVal), + mArgs(std::forward<Args>(aArgs)...) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "Storages and Args should have equal sizes"); + } + + RefPtr<PromiseType> Invoke() { return mArgs.apply(mThisVal.get(), mMethod); } + + private: + MethodType mMethod; + RefPtr<ThisType> mThisVal; + RunnableMethodArguments<Storages...> mArgs; +}; + +template <typename PromiseType, typename MethodType, typename ThisType, + typename... Storages> +class ProxyRunnable : public CancelableRunnable { + public: + ProxyRunnable( + typename PromiseType::Private* aProxyPromise, + MethodCall<PromiseType, MethodType, ThisType, Storages...>* aMethodCall) + : CancelableRunnable("detail::ProxyRunnable"), + mProxyPromise(aProxyPromise), + mMethodCall(aMethodCall) {} + + NS_IMETHOD Run() override { + RefPtr<PromiseType> p = mMethodCall->Invoke(); + mMethodCall = nullptr; + p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>"); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<typename PromiseType::Private> mProxyPromise; + UniquePtr<MethodCall<PromiseType, MethodType, ThisType, Storages...>> + mMethodCall; +}; + +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes> +static RefPtr<PromiseType> InvokeAsyncImpl( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + MOZ_ASSERT(aTarget); + + typedef RefPtr<PromiseType> (ThisType::*MethodType)(ArgTypes...); + typedef detail::MethodCall<PromiseType, MethodType, ThisType, Storages...> + MethodCallType; + typedef detail::ProxyRunnable<PromiseType, MethodType, ThisType, Storages...> + ProxyRunnableType; + + MethodCallType* methodCall = new MethodCallType( + aMethod, aThisVal, std::forward<ActualArgTypes>(aArgs)...); + RefPtr<typename PromiseType::Private> p = + new (typename PromiseType::Private)(aCallerName); + RefPtr<ProxyRunnableType> r = new ProxyRunnableType(p, methodCall); + aTarget->Dispatch(r.forget()); + return p; +} + +constexpr bool Any() { return false; } + +template <typename T1> +constexpr bool Any(T1 a) { + return static_cast<bool>(a); +} + +template <typename T1, typename... Ts> +constexpr bool Any(T1 a, Ts... aOthers) { + return a || Any(aOthers...); +} + +} // namespace detail + +// InvokeAsync with explicitly-specified storages. +// See ParameterStorage in nsThreadUtils.h for help. +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes, + std::enable_if_t<sizeof...(Storages) != 0, int> = 0> +static RefPtr<PromiseType> InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + static_assert( + sizeof...(Storages) == sizeof...(ArgTypes), + "Provided Storages and method's ArgTypes should have equal sizes"); + static_assert(sizeof...(Storages) == sizeof...(ActualArgTypes), + "Provided Storages and ActualArgTypes should have equal sizes"); + return detail::InvokeAsyncImpl<Storages...>( + aTarget, aThisVal, aCallerName, aMethod, + std::forward<ActualArgTypes>(aArgs)...); +} + +// InvokeAsync with no explicitly-specified storages, will copy arguments and +// then move them out of the runnable into the target method parameters. +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes, + std::enable_if_t<sizeof...(Storages) == 0, int> = 0> +static RefPtr<PromiseType> InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + static_assert( + !detail::Any( + std::is_pointer_v<std::remove_reference_t<ActualArgTypes>>...), + "Cannot pass pointer types through InvokeAsync, Storages must be " + "provided"); + static_assert(sizeof...(ArgTypes) == sizeof...(ActualArgTypes), + "Method's ArgTypes and ActualArgTypes should have equal sizes"); + return detail::InvokeAsyncImpl< + StoreCopyPassByRRef<std::decay_t<ActualArgTypes>>...>( + aTarget, aThisVal, aCallerName, aMethod, + std::forward<ActualArgTypes>(aArgs)...); +} + +namespace detail { + +template <typename Function, typename PromiseType> +class ProxyFunctionRunnable : public CancelableRunnable { + using FunctionStorage = std::decay_t<Function>; + + public: + template <typename F> + ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise, + F&& aFunction) + : CancelableRunnable("detail::ProxyFunctionRunnable"), + mProxyPromise(aProxyPromise), + mFunction(new FunctionStorage(std::forward<F>(aFunction))) {} + + NS_IMETHOD Run() override { + RefPtr<PromiseType> p = (*mFunction)(); + mFunction = nullptr; + p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>"); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<typename PromiseType::Private> mProxyPromise; + UniquePtr<FunctionStorage> mFunction; +}; + +// Note: The following struct and function are not for public consumption (yet?) +// as we would prefer all calls to pass on-the-spot lambdas (or at least moved +// function objects). They could be moved outside of detail if really needed. + +// We prefer getting function objects by non-lvalue-ref (to avoid copying them +// and their captures). This struct is a tag that allows the use of objects +// through lvalue-refs where necessary. +struct AllowInvokeAsyncFunctionLVRef {}; + +// Invoke a function object (e.g., lambda or std/mozilla::function) +// asynchronously; note that the object will be copied if provided by +// lvalue-ref. Return a promise that the function should eventually resolve or +// reject. +template <typename Function> +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + AllowInvokeAsyncFunctionLVRef, Function&& aFunction) + -> decltype(aFunction()) { + static_assert( + IsRefcountedSmartPointer<decltype(aFunction())>::value && + IsMozPromise< + typename RemoveSmartPointer<decltype(aFunction())>::Type>::value, + "Function object must return RefPtr<MozPromise>"); + MOZ_ASSERT(aTarget); + typedef typename RemoveSmartPointer<decltype(aFunction())>::Type PromiseType; + typedef detail::ProxyFunctionRunnable<Function, PromiseType> + ProxyRunnableType; + + auto p = MakeRefPtr<typename PromiseType::Private>(aCallerName); + auto r = MakeRefPtr<ProxyRunnableType>(p, std::forward<Function>(aFunction)); + aTarget->Dispatch(r.forget()); + return p; +} + +} // namespace detail + +// Invoke a function object (e.g., lambda) asynchronously. +// Return a promise that the function should eventually resolve or reject. +template <typename Function> +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + Function&& aFunction) -> decltype(aFunction()) { + static_assert(!std::is_lvalue_reference_v<Function>, + "Function object must not be passed by lvalue-ref (to avoid " + "unplanned copies); Consider move()ing the object."); + return detail::InvokeAsync(aTarget, aCallerName, + detail::AllowInvokeAsyncFunctionLVRef(), + std::forward<Function>(aFunction)); +} + +# undef PROMISE_LOG +# undef PROMISE_ASSERT +# undef PROMISE_DEBUG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/MozPromiseInlines.h b/xpcom/threads/MozPromiseInlines.h new file mode 100644 index 0000000000..28903917ba --- /dev/null +++ b/xpcom/threads/MozPromiseInlines.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#if !defined(MozPromiseInlines_h_) +# define MozPromiseInlines_h_ + +# include <type_traits> + +# include "mozilla/MozPromise.h" +# include "mozilla/dom/PrimitiveConversions.h" +# include "mozilla/dom/PromiseNativeHandler.h" + +namespace mozilla { + +// Creates a C++ MozPromise from its JS counterpart, dom::Promise. +// FromDomPromise currently only supports primitive types (int8/16/32, float, +// double) And the reject value type must be a nsresult. +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +RefPtr<MozPromise<ResolveValueT, RejectValueT, IsExclusive>> +MozPromise<ResolveValueT, RejectValueT, IsExclusive>::FromDomPromise( + dom::Promise* aDOMPromise) { + static_assert(std::is_same_v<RejectValueType, nsresult>, + "Reject type must be nsresult"); + RefPtr<Private> p = new Private(__func__); + RefPtr<dom::DomPromiseListener> listener = new dom::DomPromiseListener( + aDOMPromise, + [p](JSContext* aCx, JS::Handle<JS::Value> aValue) { + ResolveValueT value; + bool ok = dom::ValueToPrimitive<ResolveValueT, + dom::ConversionBehavior::eDefault>( + aCx, aValue, "Resolution value", &value); + if (!ok) { + p->Reject(NS_ERROR_FAILURE, __func__); + return; + } + p->Resolve(value, __func__); + }, + [p](nsresult aError) { p->Reject(aError, __func__); }); + return p; +} + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/Mutex.h b/xpcom/threads/Mutex.h new file mode 100644 index 0000000000..e6d58806c9 --- /dev/null +++ b/xpcom/threads/Mutex.h @@ -0,0 +1,248 @@ +/* -*- 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/. */ + +#ifndef mozilla_Mutex_h +#define mozilla_Mutex_h + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PlatformMutex.h" +#include "nsISupports.h" + +// +// Provides: +// +// - Mutex, a non-recursive mutex +// - MutexAutoLock, an RAII class for ensuring that Mutexes are properly +// locked and unlocked +// - MutexAutoUnlock, complementary sibling to MutexAutoLock +// +// - OffTheBooksMutex, a non-recursive mutex that doesn't do leak checking +// - OffTheBooksMutexAuto{Lock,Unlock} - Like MutexAuto{Lock,Unlock}, but for +// an OffTheBooksMutex. +// +// Using MutexAutoLock/MutexAutoUnlock etc. is MUCH preferred to making bare +// calls to Lock and Unlock. +// +namespace mozilla { + +/** + * OffTheBooksMutex is identical to Mutex, except that OffTheBooksMutex doesn't + * include leak checking. Sometimes you want to intentionally "leak" a mutex + * until shutdown; in these cases, OffTheBooksMutex is for you. + */ +class OffTheBooksMutex : public detail::MutexImpl, BlockingResourceBase { + public: + /** + * @param aName A name which can reference this lock + * @returns If failure, nullptr + * If success, a valid Mutex* which must be destroyed + * by Mutex::DestroyMutex() + **/ + explicit OffTheBooksMutex(const char* aName) + : BlockingResourceBase(aName, eMutex) +#ifdef DEBUG + , + mOwningThread(nullptr) +#endif + { + } + + ~OffTheBooksMutex() { +#ifdef DEBUG + MOZ_ASSERT(!mOwningThread, "destroying a still-owned lock!"); +#endif + } + +#ifndef DEBUG + /** + * Lock this mutex. + **/ + void Lock() { this->lock(); } + + /** + * Try to lock this mutex, returning true if we were successful. + **/ + [[nodiscard]] bool TryLock() { return this->tryLock(); } + + /** + * Unlock this mutex. + **/ + void Unlock() { this->unlock(); } + + /** + * Assert that the current thread owns this mutex in debug builds. + * + * Does nothing in non-debug builds. + **/ + void AssertCurrentThreadOwns() const {} + + /** + * Assert that the current thread does not own this mutex. + * + * Note that this function is not implemented for debug builds *and* + * non-debug builds due to difficulties in dealing with memory ordering. + * + * It is therefore mostly useful as documentation. + **/ + void AssertNotCurrentThreadOwns() const {} + +#else + void Lock(); + [[nodiscard]] bool TryLock(); + void Unlock(); + + void AssertCurrentThreadOwns() const; + + void AssertNotCurrentThreadOwns() const { + // FIXME bug 476536 + } + +#endif // ifndef DEBUG + + private: + OffTheBooksMutex(); + OffTheBooksMutex(const OffTheBooksMutex&); + OffTheBooksMutex& operator=(const OffTheBooksMutex&); + + friend class OffTheBooksCondVar; + +#ifdef DEBUG + PRThread* mOwningThread; +#endif +}; + +/** + * Mutex + * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this + * mutex within a scope, instead of calling Lock/Unlock directly. + */ +class Mutex : public OffTheBooksMutex { + public: + explicit Mutex(const char* aName) : OffTheBooksMutex(aName) { + MOZ_COUNT_CTOR(Mutex); + } + + MOZ_COUNTED_DTOR(Mutex) + + private: + Mutex(); + Mutex(const Mutex&); + Mutex& operator=(const Mutex&); +}; + +namespace detail { +template <typename T> +class MOZ_RAII BaseAutoUnlock; + +/** + * MutexAutoLock + * Acquires the Mutex when it enters scope, and releases it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Lock and Unlock. + */ +template <typename T> +class MOZ_RAII BaseAutoLock { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aLock A valid mozilla::Mutex* returned by + * mozilla::Mutex::NewMutex. + **/ + explicit BaseAutoLock(T aLock) : mLock(aLock) { mLock.Lock(); } + + ~BaseAutoLock(void) { mLock.Unlock(); } + + // Assert that aLock is the mutex passed to the constructor and that the + // current thread owns the mutex. In coding patterns such as: + // + // void LockedMethod(const BaseAutoLock<T>& aProofOfLock) + // { + // aProofOfLock.AssertOwns(mMutex); + // ... + // } + // + // Without this assertion, it could be that mMutex is not actually + // locked. It's possible to have code like: + // + // BaseAutoLock lock(someMutex); + // ... + // BaseAutoUnlock unlock(someMutex); + // ... + // LockedMethod(lock); + // + // and in such a case, simply asserting that the mutex pointers match is not + // sufficient; mutex ownership must be asserted as well. + // + // Note that if you are going to use the coding pattern presented above, you + // should use this method in preference to using AssertCurrentThreadOwns on + // the mutex you expected to be held, since this method provides stronger + // guarantees. + void AssertOwns(const T& aMutex) const { + MOZ_ASSERT(&aMutex == &mLock); + mLock.AssertCurrentThreadOwns(); + } + + private: + BaseAutoLock(); + BaseAutoLock(BaseAutoLock&); + BaseAutoLock& operator=(BaseAutoLock&); + static void* operator new(size_t) noexcept(true); + + friend class BaseAutoUnlock<T>; + + T mLock; +}; + +template <typename MutexType> +BaseAutoLock(MutexType&) -> BaseAutoLock<MutexType&>; +} // namespace detail + +typedef detail::BaseAutoLock<Mutex&> MutexAutoLock; +typedef detail::BaseAutoLock<OffTheBooksMutex&> OffTheBooksMutexAutoLock; + +namespace detail { +/** + * BaseAutoUnlock + * Releases the Mutex when it enters scope, and re-acquires it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Unlock and Lock. + */ +template <typename T> +class MOZ_RAII BaseAutoUnlock { + public: + explicit BaseAutoUnlock(T aLock) : mLock(aLock) { mLock.Unlock(); } + + explicit BaseAutoUnlock(BaseAutoLock<T>& aAutoLock) : mLock(aAutoLock.mLock) { + NS_ASSERTION(mLock, "null lock"); + mLock->Unlock(); + } + + ~BaseAutoUnlock() { mLock.Lock(); } + + private: + BaseAutoUnlock(); + BaseAutoUnlock(BaseAutoUnlock&); + BaseAutoUnlock& operator=(BaseAutoUnlock&); + static void* operator new(size_t) noexcept(true); + + T mLock; +}; + +template <typename MutexType> +BaseAutoUnlock(MutexType&) -> BaseAutoUnlock<MutexType&>; +} // namespace detail + +typedef detail::BaseAutoUnlock<Mutex&> MutexAutoUnlock; +typedef detail::BaseAutoUnlock<OffTheBooksMutex&> OffTheBooksMutexAutoUnlock; + +} // namespace mozilla + +#endif // ifndef mozilla_Mutex_h diff --git a/xpcom/threads/PerformanceCounter.cpp b/xpcom/threads/PerformanceCounter.cpp new file mode 100644 index 0000000000..65ee441809 --- /dev/null +++ b/xpcom/threads/PerformanceCounter.cpp @@ -0,0 +1,73 @@ +/* -*- 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/Logging.h" +#include "mozilla/PerformanceCounter.h" + +using mozilla::DispatchCategory; +using mozilla::DispatchCounter; +using mozilla::PerformanceCounter; + +static mozilla::LazyLogModule sPerformanceCounter("PerformanceCounter"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sPerformanceCounter, mozilla::LogLevel::Debug, args) + +// Global counter used by PerformanceCounter CTOR via NextCounterID(). +static mozilla::Atomic<uint64_t> gNextCounterID(0); + +static uint64_t NextCounterID() { + // This can return the same value on different processes but + // we're fine with this behavior because consumers can use a (pid, counter_id) + // tuple to make instances globally unique in a browser session. + return ++gNextCounterID; +} + +// this instance is the extension for the worker +const DispatchCategory DispatchCategory::Worker = + DispatchCategory((uint32_t)TaskCategory::Count); + +PerformanceCounter::PerformanceCounter(const nsACString& aName) + : mExecutionDuration(0), + mTotalDispatchCount(0), + mDispatchCounter(), + mName(aName), + mID(NextCounterID()) { + LOG(("PerformanceCounter created with ID %" PRIu64, mID)); +} + +void PerformanceCounter::IncrementDispatchCounter(DispatchCategory aCategory) { + mDispatchCounter[aCategory.GetValue()] += 1; + mTotalDispatchCount += 1; + LOG(("[%s][%" PRIu64 "] Total dispatch %" PRIu64, mName.get(), GetID(), + uint64_t(mTotalDispatchCount))); +} + +void PerformanceCounter::IncrementExecutionDuration(uint32_t aMicroseconds) { + mExecutionDuration += aMicroseconds; + LOG(("[%s][%" PRIu64 "] Total duration %" PRIu64, mName.get(), GetID(), + uint64_t(mExecutionDuration))); +} + +const DispatchCounter& PerformanceCounter::GetDispatchCounter() const { + return mDispatchCounter; +} + +uint64_t PerformanceCounter::GetExecutionDuration() const { + return mExecutionDuration; +} + +uint64_t PerformanceCounter::GetTotalDispatchCount() const { + return mTotalDispatchCount; +} + +uint32_t PerformanceCounter::GetDispatchCount( + DispatchCategory aCategory) const { + return mDispatchCounter[aCategory.GetValue()]; +} + +uint64_t PerformanceCounter::GetID() const { return mID; } diff --git a/xpcom/threads/PerformanceCounter.h b/xpcom/threads/PerformanceCounter.h new file mode 100644 index 0000000000..4181320e10 --- /dev/null +++ b/xpcom/threads/PerformanceCounter.h @@ -0,0 +1,139 @@ +/* -*- 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/. */ + +#ifndef mozilla_PerformanceCounter_h +#define mozilla_PerformanceCounter_h + +#include "mozilla/Array.h" +#include "mozilla/Atomics.h" +#include "mozilla/TaskCategory.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +namespace mozilla { + +/* + * The DispatchCategory class is used to fake the inheritance + * of the TaskCategory enum so we can extend it to hold + * one more value corresponding to the category + * we use when a worker dispatches a call. + * + */ +class DispatchCategory final { + public: + explicit DispatchCategory(uint32_t aValue) : mValue(aValue) { + // Since DispatchCategory is adding one single value to the + // TaskCategory enum, we can check here that the value is + // the next index e.g. TaskCategory::Count + MOZ_ASSERT(aValue == (uint32_t)TaskCategory::Count); + } + + constexpr explicit DispatchCategory(TaskCategory aValue) + : mValue((uint32_t)aValue) {} + + uint32_t GetValue() const { return mValue; } + + static const DispatchCategory Worker; + + private: + uint32_t mValue; +}; + +typedef Array<Atomic<uint32_t>, (uint32_t)TaskCategory::Count + 1> + DispatchCounter; + +// PerformanceCounter is a class that can be used to keep track of +// runnable execution times and dispatch counts. +// +// - runnable execution time: time spent in a runnable when called +// in nsThread::ProcessNextEvent (not counting recursive calls) +// - dispatch counts: number of times a tracked runnable is dispatched +// in nsThread. Useful to measure the activity of a tab or worker. +// +// The PerformanceCounter class is currently instantiated in DocGroup +// and WorkerPrivate in order to count how many scheduler dispatches +// are done through them, and how long the execution lasts. +// +// The execution time is calculated by the nsThread class (and its +// inherited WorkerThread class) in its ProcessNextEvent method. +// +// For each processed runnable, nsThread will reach out the +// PerformanceCounter attached to the runnable via its DocGroup +// or WorkerPrivate and call IncrementExecutionDuration() +// +// Notice that the execution duration counting takes into account +// recursivity. If an event triggers a recursive call to +// nsThread::ProcessNextEVent, the counter will discard the time +// spent in sub events. +class PerformanceCounter final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PerformanceCounter) + + explicit PerformanceCounter(const nsACString& aName); + + /** + * This is called everytime a runnable is dispatched. + * + * aCategory can be used to distinguish counts per TaskCategory + * + * Note that an overflow will simply reset the counter. + */ + void IncrementDispatchCounter(DispatchCategory aCategory); + + /** + * This is called via nsThread::ProcessNextEvent to measure runnable + * execution duration. + * + * Note that an overflow will simply reset the counter. + */ + void IncrementExecutionDuration(uint32_t aMicroseconds); + + /** + * Returns a category/counter array of all dispatches. + */ + const DispatchCounter& GetDispatchCounter() const; + + /** + * Returns the total execution duration. + */ + uint64_t GetExecutionDuration() const; + + /** + * Returns the number of dispatches per TaskCategory. + */ + uint32_t GetDispatchCount(DispatchCategory aCategory) const; + + /** + * Returns the total number of dispatches. + */ + uint64_t GetTotalDispatchCount() const; + + /** + * Returns the unique id for the instance. + * + * Used to distinguish instances since the lifespan of + * a PerformanceCounter can be shorter than the + * host it's tracking. That leads to edge cases + * where a counter appears to have values that go + * backwards. Having this id let the consumers + * detect that they are dealing with a new counter + * when it happens. + */ + uint64_t GetID() const; + + private: + ~PerformanceCounter() = default; + + Atomic<uint64_t> mExecutionDuration; + Atomic<uint64_t> mTotalDispatchCount; + DispatchCounter mDispatchCounter; + nsCString mName; + const uint64_t mID; +}; + +} // namespace mozilla + +#endif // mozilla_PerformanceCounter_h diff --git a/xpcom/threads/Queue.h b/xpcom/threads/Queue.h new file mode 100644 index 0000000000..754224ca7e --- /dev/null +++ b/xpcom/threads/Queue.h @@ -0,0 +1,242 @@ +/* -*- 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/. */ + +#ifndef mozilla_Queue_h +#define mozilla_Queue_h + +#include "mozilla/MemoryReporting.h" + +namespace mozilla { + +// define to turn on additional (DEBUG) asserts +// #define EXTRA_ASSERTS 1 + +// A queue implements a singly linked list of pages, each of which contains some +// number of elements. Since the queue needs to store a "next" pointer, the +// actual number of elements per page won't be quite as many as were requested. +// +// This class should only be used if it's valid to construct T elements from all +// zeroes. The class also fails to call the destructor on items. However, it +// will only destroy items after it has moved out their contents. The queue is +// required to be empty when it is destroyed. +// +// Each page consists of N entries. We use the head buffer as a circular buffer +// if it's the only buffer; if we have more than one buffer when the head is +// empty we release it. This avoids occasional freeing and reallocating buffers +// every N entries. We'll still allocate and free every N if the normal queue +// depth is greated than N. A fancier solution would be to move an empty Head +// buffer to be an empty tail buffer, freeing if we have multiple empty tails, +// but that probably isn't worth it. +// +// Cases: +// a) single buffer, circular +// Push: if not full: +// Add to tail, bump tail and reset to 0 if at end +// full: +// Add new page, insert there and set tail to 1 +// Pop: +// take entry and bump head, reset to 0 if at end +// b) multiple buffers: +// Push: if not full: +// Add to tail, bump tail +// full: +// Add new page, insert there and set tail to 1 +// Pop: +// take entry and bump head, reset to 0 if at end +// if buffer is empty, free head buffer and promote next to head +// +template <class T, size_t RequestedItemsPerPage = 256> +class Queue { + public: + Queue() = default; + + ~Queue() { + MOZ_ASSERT(IsEmpty()); + + if (mHead) { + free(mHead); + } + } + + T& Push(T&& aElement) { +#if defined(EXTRA_ASSERTS) && DEBUG + size_t original_length = Count(); +#endif + if (!mHead) { + mHead = NewPage(); + MOZ_ASSERT(mHead); + + mTail = mHead; + T& eltLocation = mTail->mEvents[0]; + eltLocation = std::move(aElement); + mOffsetHead = 0; + mHeadLength = 1; +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return eltLocation; + } + if ((mHead == mTail && mHeadLength == ItemsPerPage) || + (mHead != mTail && mTailLength == ItemsPerPage)) { + // either we have one (circular) buffer and it's full, or + // we have multiple buffers and the last buffer is full + Page* page = NewPage(); + MOZ_ASSERT(page); + + mTail->mNext = page; + mTail = page; + T& eltLocation = page->mEvents[0]; + eltLocation = std::move(aElement); + mTailLength = 1; +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return eltLocation; + } + if (mHead == mTail) { + // we have space in the (single) head buffer + uint16_t offset = (mOffsetHead + mHeadLength++) % ItemsPerPage; + T& eltLocation = mTail->mEvents[offset]; + eltLocation = std::move(aElement); +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return eltLocation; + } + // else we have space to insert into last buffer + T& eltLocation = mTail->mEvents[mTailLength++]; + eltLocation = std::move(aElement); +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return eltLocation; + } + + bool IsEmpty() const { + return !mHead || (mHead == mTail && mHeadLength == 0); + } + + T Pop() { +#if defined(EXTRA_ASSERTS) && DEBUG + size_t original_length = Count(); +#endif + MOZ_ASSERT(!IsEmpty()); + + T result = std::move(mHead->mEvents[mOffsetHead]); + mOffsetHead = (mOffsetHead + 1) % ItemsPerPage; + mHeadLength -= 1; + + // Check if mHead points to empty (circular) Page and we have more + // pages + if (mHead != mTail && mHeadLength == 0) { + Page* dead = mHead; + mHead = mHead->mNext; + free(dead); + mOffsetHead = 0; + // if there are still >1 pages, the new head is full. + if (mHead != mTail) { + mHeadLength = ItemsPerPage; + } else { + mHeadLength = mTailLength; + mTailLength = 0; + } + } + +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length - 1); +#endif + return result; + } + + T& FirstElement() { + MOZ_ASSERT(!IsEmpty()); + return mHead->mEvents[mOffsetHead]; + } + + const T& FirstElement() const { + MOZ_ASSERT(!IsEmpty()); + return mHead->mEvents[mOffsetHead]; + } + + T& LastElement() { + MOZ_ASSERT(!IsEmpty()); + uint16_t offset = + mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1; + return mTail->mEvents[offset]; + } + + const T& LastElement() const { + MOZ_ASSERT(!IsEmpty()); + uint16_t offset = + mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1; + return mTail->mEvents[offset]; + } + + size_t Count() const { + // It is obvious count is 0 when the queue is empty. + if (!mHead) { + return 0; + } + + // Compute full (intermediate) pages; Doesn't count first or last page + int count = 0; + // 1 buffer will have mHead == mTail; 2 will have mHead->mNext == mTail + for (Page* page = mHead; page != mTail && page->mNext != mTail; + page = page->mNext) { + count += ItemsPerPage; + } + // add first and last page + count += mHeadLength + mTailLength; + MOZ_ASSERT(count >= 0); + + return count; + } + + size_t ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mHead) { + for (Page* page = mHead; page != mTail; page = page->mNext) { + n += aMallocSizeOf(page); + } + } + return n; + } + + size_t ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + static_assert( + (RequestedItemsPerPage & (RequestedItemsPerPage - 1)) == 0, + "RequestedItemsPerPage should be a power of two to avoid heap slop."); + + // Since a Page must also contain a "next" pointer, we use one of the items to + // store this pointer. If sizeof(T) > sizeof(Page*), then some space will be + // wasted. So be it. + static const size_t ItemsPerPage = RequestedItemsPerPage - 1; + + // Page objects are linked together to form a simple deque. + struct Page { + struct Page* mNext; + T mEvents[ItemsPerPage]; + }; + + static Page* NewPage() { + return static_cast<Page*>(moz_xcalloc(1, sizeof(Page))); + } + + Page* mHead = nullptr; + Page* mTail = nullptr; + + uint16_t mOffsetHead = 0; // Read position in head page + uint16_t mHeadLength = 0; // Number of items in the head page + uint16_t mTailLength = 0; // Number of items in the tail page +}; + +} // namespace mozilla + +#endif // mozilla_Queue_h diff --git a/xpcom/threads/RWLock.cpp b/xpcom/threads/RWLock.cpp new file mode 100644 index 0000000000..ffdd78ceb3 --- /dev/null +++ b/xpcom/threads/RWLock.cpp @@ -0,0 +1,87 @@ +/* -*- 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/RWLock.h" + +#ifdef XP_WIN +# include <windows.h> + +static_assert(sizeof(SRWLOCK) <= sizeof(void*), "SRWLOCK is too big!"); + +# define NativeHandle(m) (reinterpret_cast<SRWLOCK*>(&m)) +#else +# define NativeHandle(m) (&m) +#endif + +namespace mozilla { + +RWLock::RWLock(const char* aName) + : BlockingResourceBase(aName, eMutex) +#ifdef DEBUG + , + mOwningThread(nullptr) +#endif +{ +#ifdef XP_WIN + InitializeSRWLock(NativeHandle(mRWLock)); +#else + MOZ_RELEASE_ASSERT(pthread_rwlock_init(NativeHandle(mRWLock), nullptr) == 0, + "pthread_rwlock_init failed"); +#endif +} + +#ifdef DEBUG +bool RWLock::LockedForWritingByCurrentThread() { + return mOwningThread == PR_GetCurrentThread(); +} +#endif + +#ifndef XP_WIN +RWLock::~RWLock() { + MOZ_RELEASE_ASSERT(pthread_rwlock_destroy(NativeHandle(mRWLock)) == 0, + "pthread_rwlock_destroy failed"); +} +#endif + +void RWLock::ReadLockInternal() { +#ifdef XP_WIN + AcquireSRWLockShared(NativeHandle(mRWLock)); +#else + MOZ_RELEASE_ASSERT(pthread_rwlock_rdlock(NativeHandle(mRWLock)) == 0, + "pthread_rwlock_rdlock failed"); +#endif +} + +void RWLock::ReadUnlockInternal() { +#ifdef XP_WIN + ReleaseSRWLockShared(NativeHandle(mRWLock)); +#else + MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(NativeHandle(mRWLock)) == 0, + "pthread_rwlock_unlock failed"); +#endif +} + +void RWLock::WriteLockInternal() { +#ifdef XP_WIN + AcquireSRWLockExclusive(NativeHandle(mRWLock)); +#else + MOZ_RELEASE_ASSERT(pthread_rwlock_wrlock(NativeHandle(mRWLock)) == 0, + "pthread_rwlock_wrlock failed"); +#endif +} + +void RWLock::WriteUnlockInternal() { +#ifdef XP_WIN + ReleaseSRWLockExclusive(NativeHandle(mRWLock)); +#else + MOZ_RELEASE_ASSERT(pthread_rwlock_unlock(NativeHandle(mRWLock)) == 0, + "pthread_rwlock_unlock failed"); +#endif +} + +} // namespace mozilla + +#undef NativeHandle diff --git a/xpcom/threads/RWLock.h b/xpcom/threads/RWLock.h new file mode 100644 index 0000000000..13d711ae8b --- /dev/null +++ b/xpcom/threads/RWLock.h @@ -0,0 +1,197 @@ +/* -*- 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/. */ + +// An interface for read-write locks. + +#ifndef mozilla_RWLock_h +#define mozilla_RWLock_h + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/BlockingResourceBase.h" + +#ifndef XP_WIN +# include <pthread.h> +#endif + +namespace mozilla { + +// A RWLock is similar to a Mutex, but whereas a Mutex permits only a single +// reader thread or a single writer thread to access a piece of data, a +// RWLock distinguishes between readers and writers: you may have multiple +// reader threads concurrently accessing a piece of data or a single writer +// thread. This difference should guide your usage of RWLock: if you are not +// reading the data from multiple threads simultaneously or you are writing +// to the data roughly as often as read from it, then Mutex will suit your +// purposes just fine. +// +// You should be using the AutoReadLock and AutoWriteLock classes, below, +// for RAII read locking and write locking, respectively. If you really must +// take a read lock manually, call the ReadLock method; to relinquish that +// read lock, call the ReadUnlock method. Similarly, WriteLock and WriteUnlock +// perform the same operations, but for write locks. +// +// It is unspecified what happens when a given thread attempts to acquire the +// same lock in multiple ways; some underlying implementations of RWLock do +// support acquiring a read lock multiple times on a given thread, but you +// should not rely on this behavior. +// +// It is unspecified whether RWLock gives priority to waiting readers or +// a waiting writer when unlocking. +class RWLock : public BlockingResourceBase { + public: + explicit RWLock(const char* aName); + + // Windows rwlocks don't need any special handling to be destroyed, but + // POSIX ones do. +#ifdef XP_WIN + ~RWLock() = default; +#else + ~RWLock(); +#endif + +#ifdef DEBUG + bool LockedForWritingByCurrentThread(); + void ReadLock(); + void ReadUnlock(); + void WriteLock(); + void WriteUnlock(); +#else + void ReadLock() { ReadLockInternal(); } + void ReadUnlock() { ReadUnlockInternal(); } + void WriteLock() { WriteLockInternal(); } + void WriteUnlock() { WriteUnlockInternal(); } +#endif + + private: + void ReadLockInternal(); + void ReadUnlockInternal(); + void WriteLockInternal(); + void WriteUnlockInternal(); + + RWLock() = delete; + RWLock(const RWLock&) = delete; + RWLock& operator=(const RWLock&) = delete; + +#ifndef XP_WIN + pthread_rwlock_t mRWLock; +#else + // SRWLock is pointer-sized. We declare it in such a fashion here to + // avoid pulling in windows.h wherever this header is used. + void* mRWLock; +#endif + +#ifdef DEBUG + // We record the owning thread for write locks only. + PRThread* mOwningThread; +#endif +}; + +template <typename T> +class MOZ_RAII BaseAutoReadLock { + public: + explicit BaseAutoReadLock(T& aLock) : mLock(&aLock) { + MOZ_ASSERT(mLock, "null lock"); + mLock->ReadLock(); + } + + ~BaseAutoReadLock() { mLock->ReadUnlock(); } + + private: + BaseAutoReadLock() = delete; + BaseAutoReadLock(const BaseAutoReadLock&) = delete; + BaseAutoReadLock& operator=(const BaseAutoReadLock&) = delete; + + T* mLock; +}; + +template <typename T> +class MOZ_RAII BaseAutoWriteLock final { + public: + explicit BaseAutoWriteLock(T& aLock) : mLock(&aLock) { + MOZ_ASSERT(mLock, "null lock"); + mLock->WriteLock(); + } + + ~BaseAutoWriteLock() { mLock->WriteUnlock(); } + + private: + BaseAutoWriteLock() = delete; + BaseAutoWriteLock(const BaseAutoWriteLock&) = delete; + BaseAutoWriteLock& operator=(const BaseAutoWriteLock&) = delete; + + T* mLock; +}; + +// Read lock and unlock a RWLock with RAII semantics. Much preferred to bare +// calls to ReadLock() and ReadUnlock(). +typedef BaseAutoReadLock<RWLock> AutoReadLock; + +// Write lock and unlock a RWLock with RAII semantics. Much preferred to bare +// calls to WriteLock() and WriteUnlock(). +typedef BaseAutoWriteLock<RWLock> AutoWriteLock; + +// XXX: normally we would define StaticRWLock as +// MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS, but the contexts in which it +// is used (e.g. member variables in a third-party library) are non-trivial +// to modify to properly declare everything at static scope. As those +// third-party libraries are the only clients, put it behind the detail +// namespace to discourage other (possibly erroneous) uses from popping up. + +namespace detail { + +class StaticRWLock { + public: + // In debug builds, check that mLock is initialized for us as we expect by + // the compiler. In non-debug builds, don't declare a constructor so that + // the compiler can see that the constructor is trivial. +#ifdef DEBUG + StaticRWLock() { MOZ_ASSERT(!mLock); } +#endif + + void ReadLock() { Lock()->ReadLock(); } + void ReadUnlock() { Lock()->ReadUnlock(); } + void WriteLock() { Lock()->WriteLock(); } + void WriteUnlock() { Lock()->WriteUnlock(); } + + private: + RWLock* Lock() { + if (mLock) { + return mLock; + } + + RWLock* lock = new RWLock("StaticRWLock"); + if (!mLock.compareExchange(nullptr, lock)) { + delete lock; + } + + return mLock; + } + + Atomic<RWLock*> mLock; + + // Disallow copy constructor, but only in debug mode. We only define + // a default constructor in debug mode (see above); if we declared + // this constructor always, the compiler wouldn't generate a trivial + // default constructor for us in non-debug mode. +#ifdef DEBUG + StaticRWLock(const StaticRWLock& aOther); +#endif + + // Disallow these operators. + StaticRWLock& operator=(StaticRWLock* aRhs); + static void* operator new(size_t) noexcept(true); + static void operator delete(void*); +}; + +typedef BaseAutoReadLock<StaticRWLock> StaticAutoReadLock; +typedef BaseAutoWriteLock<StaticRWLock> StaticAutoWriteLock; + +} // namespace detail + +} // namespace mozilla + +#endif // mozilla_RWLock_h diff --git a/xpcom/threads/RecursiveMutex.cpp b/xpcom/threads/RecursiveMutex.cpp new file mode 100644 index 0000000000..7c45052c07 --- /dev/null +++ b/xpcom/threads/RecursiveMutex.cpp @@ -0,0 +1,85 @@ +/* -*- 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/RecursiveMutex.h" + +#ifdef XP_WIN +# include <windows.h> + +# define NativeHandle(m) (reinterpret_cast<CRITICAL_SECTION*>(&m)) +#endif + +namespace mozilla { + +RecursiveMutex::RecursiveMutex(const char* aName) + : BlockingResourceBase(aName, eRecursiveMutex) +#ifdef DEBUG + , + mOwningThread(nullptr), + mEntryCount(0) +#endif +{ +#ifdef XP_WIN + // This number was adapted from NSPR. + static const DWORD sLockSpinCount = 100; + +# if defined(RELEASE_OR_BETA) + // Vista and later automatically allocate and subsequently leak a debug info + // object for each critical section that we allocate unless we tell the + // system not to do that. + DWORD flags = CRITICAL_SECTION_NO_DEBUG_INFO; +# else + DWORD flags = 0; +# endif + BOOL r = + InitializeCriticalSectionEx(NativeHandle(mMutex), sLockSpinCount, flags); + MOZ_RELEASE_ASSERT(r); +#else + pthread_mutexattr_t attr; + + MOZ_RELEASE_ASSERT(pthread_mutexattr_init(&attr) == 0, + "pthread_mutexattr_init failed"); + + MOZ_RELEASE_ASSERT( + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0, + "pthread_mutexattr_settype failed"); + + MOZ_RELEASE_ASSERT(pthread_mutex_init(&mMutex, &attr) == 0, + "pthread_mutex_init failed"); + + MOZ_RELEASE_ASSERT(pthread_mutexattr_destroy(&attr) == 0, + "pthread_mutexattr_destroy failed"); +#endif +} + +RecursiveMutex::~RecursiveMutex() { +#ifdef XP_WIN + DeleteCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_destroy(&mMutex) == 0, + "pthread_mutex_destroy failed"); +#endif +} + +void RecursiveMutex::LockInternal() { +#ifdef XP_WIN + EnterCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_lock(&mMutex) == 0, + "pthread_mutex_lock failed"); +#endif +} + +void RecursiveMutex::UnlockInternal() { +#ifdef XP_WIN + LeaveCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_unlock(&mMutex) == 0, + "pthread_mutex_unlock failed"); +#endif +} + +} // namespace mozilla diff --git a/xpcom/threads/RecursiveMutex.h b/xpcom/threads/RecursiveMutex.h new file mode 100644 index 0000000000..4dd256a039 --- /dev/null +++ b/xpcom/threads/RecursiveMutex.h @@ -0,0 +1,112 @@ +/* -*- 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/. */ + +// A lock that can be acquired multiple times on the same thread. + +#ifndef mozilla_RecursiveMutex_h +#define mozilla_RecursiveMutex_h + +#include "mozilla/BlockingResourceBase.h" + +#ifndef XP_WIN +# include <pthread.h> +#endif + +namespace mozilla { + +class RecursiveMutex : public BlockingResourceBase { + public: + explicit RecursiveMutex(const char* aName); + ~RecursiveMutex(); + +#ifdef DEBUG + void Lock(); + void Unlock(); +#else + void Lock() { LockInternal(); } + void Unlock() { UnlockInternal(); } +#endif + +#ifdef DEBUG + /** + * AssertCurrentThreadIn + **/ + void AssertCurrentThreadIn(); + /** + * AssertNotCurrentThreadIn + **/ + void AssertNotCurrentThreadIn() { + // Not currently implemented. See bug 476536 for discussion. + } +#else + void AssertCurrentThreadIn() {} + void AssertNotCurrentThreadIn() {} +#endif + + private: + RecursiveMutex() = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; + + void LockInternal(); + void UnlockInternal(); + +#ifdef DEBUG + PRThread* mOwningThread; + size_t mEntryCount; +#endif + +#if !defined(XP_WIN) + pthread_mutex_t mMutex; +#else + // We eschew including windows.h and using CRITICAL_SECTION here so that files + // including us don't also pull in windows.h. Just use a type that's big + // enough for CRITICAL_SECTION, and we'll fix it up later. + void* mMutex[6]; +#endif +}; + +class MOZ_RAII RecursiveMutexAutoLock { + public: + explicit RecursiveMutexAutoLock(RecursiveMutex& aRecursiveMutex) + : mRecursiveMutex(&aRecursiveMutex) { + NS_ASSERTION(mRecursiveMutex, "null mutex"); + mRecursiveMutex->Lock(); + } + + ~RecursiveMutexAutoLock(void) { mRecursiveMutex->Unlock(); } + + private: + RecursiveMutexAutoLock() = delete; + RecursiveMutexAutoLock(const RecursiveMutexAutoLock&) = delete; + RecursiveMutexAutoLock& operator=(const RecursiveMutexAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + mozilla::RecursiveMutex* mRecursiveMutex; +}; + +class MOZ_RAII RecursiveMutexAutoUnlock { + public: + explicit RecursiveMutexAutoUnlock(RecursiveMutex& aRecursiveMutex) + : mRecursiveMutex(&aRecursiveMutex) { + NS_ASSERTION(mRecursiveMutex, "null mutex"); + mRecursiveMutex->Unlock(); + } + + ~RecursiveMutexAutoUnlock(void) { mRecursiveMutex->Lock(); } + + private: + RecursiveMutexAutoUnlock() = delete; + RecursiveMutexAutoUnlock(const RecursiveMutexAutoUnlock&) = delete; + RecursiveMutexAutoUnlock& operator=(const RecursiveMutexAutoUnlock&) = delete; + static void* operator new(size_t) noexcept(true); + + mozilla::RecursiveMutex* mRecursiveMutex; +}; + +} // namespace mozilla + +#endif // mozilla_RecursiveMutex_h diff --git a/xpcom/threads/ReentrantMonitor.h b/xpcom/threads/ReentrantMonitor.h new file mode 100644 index 0000000000..d1d1145d1b --- /dev/null +++ b/xpcom/threads/ReentrantMonitor.h @@ -0,0 +1,242 @@ +/* -*- 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/. */ + +#ifndef mozilla_ReentrantMonitor_h +#define mozilla_ReentrantMonitor_h + +#include "prmon.h" + +#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG) +# include "GeckoProfiler.h" +#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG) + +#include "mozilla/BlockingResourceBase.h" +#include "nsISupports.h" + +// +// Provides: +// +// - ReentrantMonitor, a Java-like monitor +// - ReentrantMonitorAutoEnter, an RAII class for ensuring that +// ReentrantMonitors are properly entered and exited +// +// Using ReentrantMonitorAutoEnter is MUCH preferred to making bare calls to +// ReentrantMonitor.Enter and Exit. +// +namespace mozilla { + +/** + * ReentrantMonitor + * Java-like monitor. + * When possible, use ReentrantMonitorAutoEnter to hold this monitor within a + * scope, instead of calling Enter/Exit directly. + **/ +class ReentrantMonitor : BlockingResourceBase { + public: + /** + * ReentrantMonitor + * @param aName A name which can reference this monitor + */ + explicit ReentrantMonitor(const char* aName) + : BlockingResourceBase(aName, eReentrantMonitor) +#ifdef DEBUG + , + mEntryCount(0) +#endif + { + MOZ_COUNT_CTOR(ReentrantMonitor); + mReentrantMonitor = PR_NewMonitor(); + if (!mReentrantMonitor) { + MOZ_CRASH("Can't allocate mozilla::ReentrantMonitor"); + } + } + + /** + * ~ReentrantMonitor + **/ + ~ReentrantMonitor() { + NS_ASSERTION(mReentrantMonitor, + "improperly constructed ReentrantMonitor or double free"); + PR_DestroyMonitor(mReentrantMonitor); + mReentrantMonitor = 0; + MOZ_COUNT_DTOR(ReentrantMonitor); + } + +#ifndef DEBUG + /** + * Enter + * @see prmon.h + **/ + void Enter() { PR_EnterMonitor(mReentrantMonitor); } + + /** + * Exit + * @see prmon.h + **/ + void Exit() { PR_ExitMonitor(mReentrantMonitor); } + + /** + * Wait + * @see prmon.h + **/ + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) { +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + return PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS + ? NS_OK + : NS_ERROR_FAILURE; + } + +#else // ifndef DEBUG + void Enter(); + void Exit(); + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT); + +#endif // ifndef DEBUG + + /** + * Notify + * @see prmon.h + **/ + nsresult Notify() { + return PR_Notify(mReentrantMonitor) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + + /** + * NotifyAll + * @see prmon.h + **/ + nsresult NotifyAll() { + return PR_NotifyAll(mReentrantMonitor) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + +#ifdef DEBUG + /** + * AssertCurrentThreadIn + * @see prmon.h + **/ + void AssertCurrentThreadIn() { + PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor); + } + + /** + * AssertNotCurrentThreadIn + * @see prmon.h + **/ + void AssertNotCurrentThreadIn() { + // FIXME bug 476536 + } + +#else + void AssertCurrentThreadIn() {} + void AssertNotCurrentThreadIn() {} + +#endif // ifdef DEBUG + + private: + ReentrantMonitor(); + ReentrantMonitor(const ReentrantMonitor&); + ReentrantMonitor& operator=(const ReentrantMonitor&); + + PRMonitor* mReentrantMonitor; +#ifdef DEBUG + int32_t mEntryCount; +#endif +}; + +/** + * ReentrantMonitorAutoEnter + * Enters the ReentrantMonitor when it enters scope, and exits it when + * it leaves scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Enter and Exit. + */ +class MOZ_STACK_CLASS ReentrantMonitorAutoEnter { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. + **/ + explicit ReentrantMonitorAutoEnter( + mozilla::ReentrantMonitor& aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->Enter(); + } + + ~ReentrantMonitorAutoEnter(void) { mReentrantMonitor->Exit(); } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) { + return mReentrantMonitor->Wait(aInterval); + } + + nsresult Notify() { return mReentrantMonitor->Notify(); } + nsresult NotifyAll() { return mReentrantMonitor->NotifyAll(); } + + private: + ReentrantMonitorAutoEnter(); + ReentrantMonitorAutoEnter(const ReentrantMonitorAutoEnter&); + ReentrantMonitorAutoEnter& operator=(const ReentrantMonitorAutoEnter&); + static void* operator new(size_t) noexcept(true); + + friend class ReentrantMonitorAutoExit; + + mozilla::ReentrantMonitor* mReentrantMonitor; +}; + +/** + * ReentrantMonitorAutoExit + * Exit the ReentrantMonitor when it enters scope, and enters it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Exit and Enter. + */ +class MOZ_STACK_CLASS ReentrantMonitorAutoExit { + public: + /** + * Constructor + * The constructor releases the given lock. The destructor + * acquires the lock. The lock must be held before constructing + * this object! + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. It + * must be already locked. + **/ + explicit ReentrantMonitorAutoExit(ReentrantMonitor& aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + explicit ReentrantMonitorAutoExit( + ReentrantMonitorAutoEnter& aReentrantMonitorAutoEnter) + : mReentrantMonitor(aReentrantMonitorAutoEnter.mReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + ~ReentrantMonitorAutoExit(void) { mReentrantMonitor->Enter(); } + + private: + ReentrantMonitorAutoExit(); + ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&); + ReentrantMonitorAutoExit& operator=(const ReentrantMonitorAutoExit&); + static void* operator new(size_t) noexcept(true); + + ReentrantMonitor* mReentrantMonitor; +}; + +} // namespace mozilla + +#endif // ifndef mozilla_ReentrantMonitor_h diff --git a/xpcom/threads/SchedulerGroup.cpp b/xpcom/threads/SchedulerGroup.cpp new file mode 100644 index 0000000000..e17066340c --- /dev/null +++ b/xpcom/threads/SchedulerGroup.cpp @@ -0,0 +1,157 @@ +/* -*- 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/SchedulerGroup.h" + +#include <utility> + +#include "jsfriendapi.h" +#include "mozilla/Atomics.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsINamed.h" +#include "nsQueryObject.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +namespace { + +static Atomic<uint64_t> gEarliestUnprocessedVsync(0); + +} // namespace + +/* static */ +nsresult SchedulerGroup::UnlabeledDispatch( + TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) { + if (NS_IsMainThread()) { + return NS_DispatchToCurrentThread(std::move(aRunnable)); + } else { + return NS_DispatchToMainThread(std::move(aRunnable)); + } +} + +/* static */ +void SchedulerGroup::MarkVsyncReceived() { + if (gEarliestUnprocessedVsync) { + // If we've seen a vsync already, but haven't handled it, keep the + // older one. + return; + } + + MOZ_ASSERT(!NS_IsMainThread()); + bool inconsistent = false; + TimeStamp creation = TimeStamp::ProcessCreation(&inconsistent); + if (inconsistent) { + return; + } + + gEarliestUnprocessedVsync = (TimeStamp::Now() - creation).ToMicroseconds(); +} + +/* static */ +void SchedulerGroup::MarkVsyncRan() { gEarliestUnprocessedVsync = 0; } + +SchedulerGroup::SchedulerGroup() : mIsRunning(false) {} + +/* static */ +nsresult SchedulerGroup::DispatchWithDocGroup( + TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable, + dom::DocGroup* aDocGroup) { + return LabeledDispatch(aCategory, std::move(aRunnable), aDocGroup); +} + +/* static */ +nsresult SchedulerGroup::Dispatch(TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable) { + return LabeledDispatch(aCategory, std::move(aRunnable), nullptr); +} + +/* static */ +nsresult SchedulerGroup::LabeledDispatch( + TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable, + dom::DocGroup* aDocGroup) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + if (XRE_IsContentProcess()) { + RefPtr<Runnable> internalRunnable = + new Runnable(runnable.forget(), aDocGroup); + return InternalUnlabeledDispatch(aCategory, internalRunnable.forget()); + } + return UnlabeledDispatch(aCategory, runnable.forget()); +} + +/*static*/ +nsresult SchedulerGroup::InternalUnlabeledDispatch( + TaskCategory aCategory, already_AddRefed<Runnable>&& aRunnable) { + if (NS_IsMainThread()) { + // NS_DispatchToCurrentThread will not leak the passed in runnable + // when it fails, so we don't need to do anything special. + return NS_DispatchToCurrentThread(std::move(aRunnable)); + } + + RefPtr<Runnable> runnable(aRunnable); + nsresult rv = NS_DispatchToMainThread(do_AddRef(runnable)); + if (NS_FAILED(rv)) { + // Dispatch failed. This is a situation where we would have used + // NS_DispatchToMainThread rather than calling into the SchedulerGroup + // machinery, and the caller would be expecting to leak the nsIRunnable + // originally passed in. But because we've had to wrap things up + // internally, we were going to leak the nsIRunnable *and* our Runnable + // wrapper. But there's no reason that we have to leak our Runnable + // wrapper; we can just leak the wrapped nsIRunnable, and let the caller + // take care of unleaking it if they need to. + Unused << runnable->mRunnable.forget().take(); + nsrefcnt refcnt = runnable.get()->Release(); + MOZ_RELEASE_ASSERT(refcnt == 1, "still holding an unexpected reference!"); + } + + return rv; +} + +SchedulerGroup::Runnable::Runnable(already_AddRefed<nsIRunnable>&& aRunnable, + dom::DocGroup* aDocGroup) + : mozilla::Runnable("SchedulerGroup::Runnable"), + mRunnable(std::move(aRunnable)), + mDocGroup(aDocGroup) {} + +dom::DocGroup* SchedulerGroup::Runnable::DocGroup() const { return mDocGroup; } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +SchedulerGroup::Runnable::GetName(nsACString& aName) { + // Try to get a name from the underlying runnable. + nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable); + if (named) { + named->GetName(aName); + } + if (aName.IsEmpty()) { + aName.AssignLiteral("anonymous"); + } + + return NS_OK; +} +#endif + +NS_IMETHODIMP +SchedulerGroup::Runnable::Run() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + // The runnable's destructor can have side effects, so try to execute it in + // the scope of the SchedulerGroup. + nsCOMPtr<nsIRunnable> runnable(std::move(mRunnable)); + return runnable->Run(); +} + +NS_IMETHODIMP +SchedulerGroup::Runnable::GetPriority(uint32_t* aPriority) { + *aPriority = nsIRunnablePriority::PRIORITY_NORMAL; + nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable); + return runnablePrio ? runnablePrio->GetPriority(aPriority) : NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(SchedulerGroup::Runnable, mozilla::Runnable, + nsIRunnablePriority, SchedulerGroup::Runnable) diff --git a/xpcom/threads/SchedulerGroup.h b/xpcom/threads/SchedulerGroup.h new file mode 100644 index 0000000000..7221ce3bb7 --- /dev/null +++ b/xpcom/threads/SchedulerGroup.h @@ -0,0 +1,118 @@ +/* -*- 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/. */ + +#ifndef mozilla_SchedulerGroup_h +#define mozilla_SchedulerGroup_h + +#include "mozilla/RefPtr.h" +#include "mozilla/TaskCategory.h" +#include "nsCOMPtr.h" +#include "nsID.h" +#include "nsIRunnable.h" +#include "nsISupports.h" +#include "nsStringFwd.h" +#include "nsThreadUtils.h" +#include "nscore.h" + +class nsIEventTarget; +class nsIRunnable; +class nsISerialEventTarget; + +namespace mozilla { +class AbstractThread; +namespace dom { +class DocGroup; +} // namespace dom + +#define NS_SCHEDULERGROUPRUNNABLE_IID \ + { \ + 0xd31b7420, 0x872b, 0x4cfb, { \ + 0xa9, 0xc6, 0xae, 0x4c, 0x0f, 0x06, 0x36, 0x74 \ + } \ + } + +// The "main thread" in Gecko will soon be a set of cooperatively scheduled +// "fibers". Global state in Gecko will be partitioned into a series of "groups" +// (with roughly one group per tab). Runnables will be annotated with the set of +// groups that they touch. Two runnables may run concurrently on different +// fibers as long as they touch different groups. +// +// A SchedulerGroup is an abstract class to represent a "group". Essentially the +// only functionality offered by a SchedulerGroup is the ability to dispatch +// runnables to the group. DocGroup, and SystemGroup are the concrete +// implementations of SchedulerGroup. +class SchedulerGroup { + public: + SchedulerGroup(); + + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + class Runnable final : public mozilla::Runnable, public nsIRunnablePriority { + public: + Runnable(already_AddRefed<nsIRunnable>&& aRunnable, + dom::DocGroup* aDocGroup); + + dom::DocGroup* DocGroup() const; + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +#endif + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIRUNNABLEPRIORITY + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID); + + private: + friend class SchedulerGroup; + + ~Runnable() = default; + + nsCOMPtr<nsIRunnable> mRunnable; + RefPtr<dom::DocGroup> mDocGroup; + }; + friend class Runnable; + + static nsresult Dispatch(TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable); + + static nsresult UnlabeledDispatch(TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable); + + static void MarkVsyncReceived(); + + static void MarkVsyncRan(); + + static nsresult DispatchWithDocGroup( + TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable, + dom::DocGroup* aDocGroup); + + protected: + static nsresult InternalUnlabeledDispatch( + TaskCategory aCategory, already_AddRefed<Runnable>&& aRunnable); + + static nsresult LabeledDispatch(TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable, + dom::DocGroup* aDocGroup); + + // Shuts down this dispatcher. If aXPCOMShutdown is true, invalidates this + // dispatcher. + void Shutdown(bool aXPCOMShutdown); + + bool mIsRunning; + + // Number of events that are currently enqueued for this SchedulerGroup + // (across all queues). + size_t mEventCount = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerGroup::Runnable, + NS_SCHEDULERGROUPRUNNABLE_IID); + +} // namespace mozilla + +#endif // mozilla_SchedulerGroup_h diff --git a/xpcom/threads/SharedThreadPool.cpp b/xpcom/threads/SharedThreadPool.cpp new file mode 100644 index 0000000000..fede9a2f39 --- /dev/null +++ b/xpcom/threads/SharedThreadPool.cpp @@ -0,0 +1,232 @@ +/* -*- 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/SharedThreadPool.h" +#include "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "nsDataHashtable.h" +#include "nsXPCOMCIDInternal.h" +#include "nsComponentManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIThreadManager.h" +#include "nsThreadPool.h" +#ifdef XP_WIN +# include "ThreadPoolCOMListener.h" +#endif + +namespace mozilla { + +// Created and destroyed on the main thread. +static StaticAutoPtr<ReentrantMonitor> sMonitor; + +// Hashtable, maps thread pool name to SharedThreadPool instance. +// Modified only on the main thread. +static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>> + sPools; + +static already_AddRefed<nsIThreadPool> CreateThreadPool(const nsCString& aName); + +class SharedThreadPoolShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + protected: + virtual ~SharedThreadPoolShutdownObserver() = default; +}; + +NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports) + +NS_IMETHODIMP +SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads")); +#ifdef EARLY_BETA_OR_EARLIER + { + ReentrantMonitorAutoEnter mon(*sMonitor); + if (!sPools->Iter().Done()) { + nsAutoCString str; + for (auto i = sPools->Iter(); !i.Done(); i.Next()) { + str.AppendPrintf("\"%s\" ", nsAutoCString(i.Key()).get()); + } + printf_stderr( + "SharedThreadPool in xpcom-shutdown-threads. Waiting for " + "pools %s\n", + str.get()); + } + } +#endif + SharedThreadPool::SpinUntilEmpty(); + sMonitor = nullptr; + sPools = nullptr; + return NS_OK; +} + +void SharedThreadPool::InitStatics() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sMonitor && !sPools); + sMonitor = new ReentrantMonitor("SharedThreadPool"); + sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>(); + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + nsCOMPtr<nsIObserver> obs = new SharedThreadPoolShutdownObserver(); + obsService->AddObserver(obs, "xpcom-shutdown-threads", false); +} + +/* static */ +bool SharedThreadPool::IsEmpty() { + ReentrantMonitorAutoEnter mon(*sMonitor); + return !sPools->Count(); +} + +/* static */ +void SharedThreadPool::SpinUntilEmpty() { + MOZ_ASSERT(NS_IsMainThread()); + SpinEventLoopUntil([]() -> bool { + sMonitor->AssertNotCurrentThreadIn(); + return IsEmpty(); + }); +} + +already_AddRefed<SharedThreadPool> SharedThreadPool::Get( + const nsCString& aName, uint32_t aThreadLimit) { + MOZ_ASSERT(sMonitor && sPools); + ReentrantMonitorAutoEnter mon(*sMonitor); + RefPtr<SharedThreadPool> pool; + nsresult rv; + + if (auto entry = sPools->LookupForAdd(aName)) { + pool = entry.Data(); + if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { + NS_WARNING("Failed to set limits on thread pool"); + } + } else { + nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName)); + if (NS_WARN_IF(!threadPool)) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + pool = new SharedThreadPool(aName, threadPool); + + // Set the thread and idle limits. Note that we don't rely on the + // EnsureThreadLimitIsAtLeast() call below, as the default thread limit + // is 4, and if aThreadLimit is less than 4 we'll end up with a pool + // with 4 threads rather than what we expected; so we'll have unexpected + // behaviour. + rv = pool->SetThreadLimit(aThreadLimit); + if (NS_WARN_IF(NS_FAILED(rv))) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + + rv = pool->SetIdleThreadLimit(aThreadLimit); + if (NS_WARN_IF(NS_FAILED(rv))) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + + entry.OrInsert([pool]() { return pool.get(); }); + } + + return pool.forget(); +} + +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) { + MOZ_ASSERT(sMonitor); + ReentrantMonitorAutoEnter mon(*sMonitor); + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count = ++mRefCnt; + NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) { + MOZ_ASSERT(sMonitor); + ReentrantMonitorAutoEnter mon(*sMonitor); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "SharedThreadPool"); + if (count) { + return count; + } + + // Remove SharedThreadPool from table of pools. + sPools->Remove(mName); + MOZ_ASSERT(!sPools->Get(mName)); + + // Dispatch an event to the main thread to call Shutdown() on + // the nsIThreadPool. The Runnable here will add a refcount to the pool, + // and when the Runnable releases the nsIThreadPool it will be deleted. + NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool, + &nsIThreadPool::Shutdown)); + + // Stabilize refcount, so that if something in the dtor QIs, it won't explode. + mRefCnt = 1; + delete this; + return 0; +} + +NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) + +SharedThreadPool::SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool) + : mName(aName), mPool(aPool), mRefCnt(0) { + mEventTarget = aPool; +} + +SharedThreadPool::~SharedThreadPool() = default; + +nsresult SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) { + // We limit the number of threads that we use. Note that we + // set the thread limit to the same as the idle limit so that we're not + // constantly creating and destroying threads (see Bug 881954). When the + // thread pool threads shutdown they dispatch an event to the main thread + // to call nsIThread::Shutdown(), and if we're very busy that can take a + // while to run, and we end up with dozens of extra threads. Note that + // threads that are idle for 60 seconds are shutdown naturally. + uint32_t existingLimit = 0; + nsresult rv; + + rv = mPool->GetThreadLimit(&existingLimit); + NS_ENSURE_SUCCESS(rv, rv); + if (aLimit > existingLimit) { + rv = mPool->SetThreadLimit(aLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mPool->GetIdleThreadLimit(&existingLimit); + NS_ENSURE_SUCCESS(rv, rv); + if (aLimit > existingLimit) { + rv = mPool->SetIdleThreadLimit(aLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +static already_AddRefed<nsIThreadPool> CreateThreadPool( + const nsCString& aName) { + nsCOMPtr<nsIThreadPool> pool = new nsThreadPool(); + + nsresult rv = pool->SetName(aName); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, nullptr); + +#ifdef XP_WIN + // Ensure MSCOM is initialized on the thread pools threads. + nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener(); + rv = pool->SetListener(listener); + NS_ENSURE_SUCCESS(rv, nullptr); +#endif + + return pool.forget(); +} + +} // namespace mozilla diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h new file mode 100644 index 0000000000..936cac5cd8 --- /dev/null +++ b/xpcom/threads/SharedThreadPool.h @@ -0,0 +1,128 @@ +/* -*- 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/. */ + +#ifndef SharedThreadPool_h_ +#define SharedThreadPool_h_ + +#include <utility> +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefCountType.h" +#include "nsCOMPtr.h" +#include "nsID.h" +#include "nsIEventTarget.h" +#include "nsIThreadPool.h" +#include "nsString.h" +#include "nscore.h" + +class nsIRunnable; + +namespace mozilla { + +// Wrapper that makes an nsIThreadPool a singleton, and provides a +// consistent threadsafe interface to get instances. Callers simply get a +// SharedThreadPool by the name of its nsIThreadPool. All get requests of +// the same name get the same SharedThreadPool. Users must store a reference +// to the pool, and when the last reference to a SharedThreadPool is dropped +// the pool is shutdown and deleted. Users aren't required to manually +// shutdown the pool, and can release references on any thread. This can make +// it significantly easier to use thread pools, because the caller doesn't need +// to worry about joining and tearing it down. +// +// On Windows all threads in the pool have MSCOM initialized with +// COINIT_MULTITHREADED. Note that not all users of MSCOM use this mode see [1], +// and mixing MSCOM objects between the two is terrible for performance, and can +// cause some functions to fail. So be careful when using Win32 APIs on a +// SharedThreadPool, and avoid sharing objects if at all possible. +// +// [1] +// https://searchfox.org/mozilla-central/search?q=coinitialize&redirect=false +class SharedThreadPool : public nsIThreadPool { + public: + // Gets (possibly creating) the shared thread pool singleton instance with + // thread pool named aName. + static already_AddRefed<SharedThreadPool> Get(const nsCString& aName, + uint32_t aThreadLimit = 4); + + // We implement custom threadsafe AddRef/Release pair, that destroys the + // the shared pool singleton when the refcount drops to 0. The addref/release + // are implemented using locking, so it's not recommended that you use them + // in a tight loop. + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + + // Forward behaviour to wrapped thread pool implementation. + NS_FORWARD_SAFE_NSITHREADPOOL(mPool); + + // Call this when dispatching from an event on the same + // threadpool that is about to complete. We should not create a new thread + // in that case since a thread is about to become idle. + nsresult DispatchFromEndOfTaskInThisPool(nsIRunnable* event) { + return Dispatch(event, NS_DISPATCH_AT_END); + } + + NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override { + return Dispatch(event, flags); + } + + NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event, + uint32_t flags = NS_DISPATCH_NORMAL) override { + return !mEventTarget ? NS_ERROR_NULL_POINTER + : mEventTarget->Dispatch(std::move(event), flags); + } + + NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + using nsIEventTarget::Dispatch; + + NS_IMETHOD IsOnCurrentThread(bool* _retval) override { + return !mEventTarget ? NS_ERROR_NULL_POINTER + : mEventTarget->IsOnCurrentThread(_retval); + } + + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible() override { + return mEventTarget && mEventTarget->IsOnCurrentThread(); + } + + // Creates necessary statics. Called once at startup. + static void InitStatics(); + + // Spins the event loop until all thread pools are shutdown. + // *Must* be called on the main thread. + static void SpinUntilEmpty(); + + private: + // Returns whether there are no pools in existence at the moment. + static bool IsEmpty(); + + // Creates a singleton SharedThreadPool wrapper around aPool. + // aName is the name of the aPool, and is used to lookup the + // SharedThreadPool in the hash table of all created pools. + SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool); + virtual ~SharedThreadPool(); + + nsresult EnsureThreadLimitIsAtLeast(uint32_t aThreadLimit); + + // Name of mPool. + const nsCString mName; + + // Thread pool being wrapped. + nsCOMPtr<nsIThreadPool> mPool; + + // Refcount. We implement custom ref counting so that the thread pool is + // shutdown in a threadsafe manner and singletonness is preserved. + nsrefcnt mRefCnt; + + // mPool QI'd to nsIEventTarget. We cache this, so that we can use + // NS_FORWARD_SAFE_NSIEVENTTARGET above. + nsCOMPtr<nsIEventTarget> mEventTarget; +}; + +} // namespace mozilla + +#endif // SharedThreadPool_h_ diff --git a/xpcom/threads/SpinEventLoopUntil.h b/xpcom/threads/SpinEventLoopUntil.h new file mode 100644 index 0000000000..398c7084a2 --- /dev/null +++ b/xpcom/threads/SpinEventLoopUntil.h @@ -0,0 +1,108 @@ +/* -*- 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/. */ + +#ifndef xpcom_threads_SpinEventLoopUntil_h__ +#define xpcom_threads_SpinEventLoopUntil_h__ + +#include "MainThreadUtils.h" +#include "mozilla/Maybe.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +class nsIThread; + +// A wrapper for nested event loops. +// +// This function is intended to make code more obvious (do you remember +// what NS_ProcessNextEvent(nullptr, true) means?) and slightly more +// efficient, as people often pass nullptr or NS_GetCurrentThread to +// NS_ProcessNextEvent, which results in needless querying of the current +// thread every time through the loop. +// +// You should use this function in preference to NS_ProcessNextEvent inside +// a loop unless one of the following is true: +// +// * You need to pass `false` to NS_ProcessNextEvent; or +// * You need to do unusual things around the call to NS_ProcessNextEvent, +// such as unlocking mutexes that you are holding. +// +// If you *do* need to call NS_ProcessNextEvent manually, please do call +// NS_GetCurrentThread() outside of your loop and pass the returned pointer +// into NS_ProcessNextEvent for a tiny efficiency win. +namespace mozilla { + +// You should normally not need to deal with this template parameter. If +// you enjoy esoteric event loop details, read on. +// +// If you specify that NS_ProcessNextEvent wait for an event, it is possible +// for NS_ProcessNextEvent to return false, i.e. to indicate that an event +// was not processed. This can only happen when the thread has been shut +// down by another thread, but is still attempting to process events outside +// of a nested event loop. +// +// This behavior is admittedly strange. The scenario it deals with is the +// following: +// +// * The current thread has been shut down by some owner thread. +// * The current thread is spinning an event loop waiting for some condition +// to become true. +// * Said condition is actually being fulfilled by another thread, so there +// are timing issues in play. +// +// Thus, there is a small window where the current thread's event loop +// spinning can check the condition, find it false, and call +// NS_ProcessNextEvent to wait for another event. But we don't actually +// want it to wait indefinitely, because there might not be any other events +// in the event loop, and the current thread can't accept dispatched events +// because it's being shut down. Thus, actually blocking would hang the +// thread, which is bad. The solution, then, is to detect such a scenario +// and not actually block inside NS_ProcessNextEvent. +// +// But this is a problem, because we want to return the status of +// NS_ProcessNextEvent to the caller of SpinEventLoopUntil if possible. In +// the above scenario, however, we'd stop spinning prematurely and cause +// all sorts of havoc. We therefore have this template parameter to +// control whether errors are ignored or passed out to the caller of +// SpinEventLoopUntil. The latter is the default; if you find yourself +// wanting to use the former, you should think long and hard before doing +// so, and write a comment like this defending your choice. + +enum class ProcessFailureBehavior { + IgnoreAndContinue, + ReportToCaller, +}; + +template < + ProcessFailureBehavior Behavior = ProcessFailureBehavior::ReportToCaller, + typename Pred> +bool SpinEventLoopUntil(Pred&& aPredicate, nsIThread* aThread = nullptr) { + nsIThread* thread = aThread ? aThread : NS_GetCurrentThread(); + + // From a latency perspective, spinning the event loop is like leaving script + // and returning to the event loop. Tell the watchdog we stopped running + // script (until we return). + mozilla::Maybe<xpc::AutoScriptActivity> asa; + if (NS_IsMainThread()) { + asa.emplace(false); + } + + while (!aPredicate()) { + bool didSomething = NS_ProcessNextEvent(thread, true); + + if (Behavior == ProcessFailureBehavior::IgnoreAndContinue) { + // Don't care what happened, continue on. + continue; + } else if (!didSomething) { + return false; + } + } + + return true; +} + +} // namespace mozilla + +#endif // xpcom_threads_SpinEventLoopUntil_h__ diff --git a/xpcom/threads/StateMirroring.h b/xpcom/threads/StateMirroring.h new file mode 100644 index 0000000000..c233116962 --- /dev/null +++ b/xpcom/threads/StateMirroring.h @@ -0,0 +1,393 @@ +/* -*- 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/. */ + +#if !defined(StateMirroring_h_) +# define StateMirroring_h_ + +# include <cstddef> +# include "mozilla/AbstractThread.h" +# include "mozilla/AlreadyAddRefed.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/RefPtr.h" +# include "mozilla/StateWatching.h" +# include "nsCOMPtr.h" +# include "nsIRunnable.h" +# include "nsISupports.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +/* + * The state-mirroring machinery allows pieces of interesting state to be + * observed on multiple thread without locking. The basic strategy is to track + * changes in a canonical value and post updates to other threads that hold + * mirrors for that value. + * + * One problem with the naive implementation of such a system is that some + * pieces of state need to be updated atomically, and certain other operations + * need to wait for these atomic updates to complete before executing. The + * state-mirroring machinery solves this problem by requiring that its owner + * thread uses tail dispatch, and posting state update events (which should + * always be run first by TaskDispatcher implementations) to that tail + * dispatcher. This ensures that state changes are always atomic from the + * perspective of observing threads. + * + * Given that state-mirroring is an automatic background process, we try to + * avoid burdening the caller with worrying too much about teardown. To that + * end, we don't assert dispatch success for any of the notifications, and + * assume that any canonical or mirror owned by a thread for whom dispatch fails + * will soon be disconnected by its holder anyway. + * + * Given that semantics may change and comments tend to go out of date, we + * deliberately don't provide usage examples here. Grep around to find them. + */ + +namespace mozilla { + +// Mirror<T> and Canonical<T> inherit WatchTarget, so we piggy-back on the +// logging that WatchTarget already does. Given that, it makes sense to share +// the same log module. +# define MIRROR_LOG(x, ...) \ + MOZ_ASSERT(gStateWatchingLog); \ + MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) + +template <typename T> +class AbstractMirror; + +/* + * AbstractCanonical is a superclass from which all Canonical values must + * inherit. It serves as the interface of operations which may be performed (via + * asynchronous dispatch) by other threads, in particular by the corresponding + * Mirror value. + */ +template <typename T> +class AbstractCanonical { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractCanonical) + AbstractCanonical(AbstractThread* aThread) : mOwnerThread(aThread) {} + virtual void AddMirror(AbstractMirror<T>* aMirror) = 0; + virtual void RemoveMirror(AbstractMirror<T>* aMirror) = 0; + + AbstractThread* OwnerThread() const { return mOwnerThread; } + + protected: + virtual ~AbstractCanonical() {} + RefPtr<AbstractThread> mOwnerThread; +}; + +/* + * AbstractMirror is a superclass from which all Mirror values must + * inherit. It serves as the interface of operations which may be performed (via + * asynchronous dispatch) by other threads, in particular by the corresponding + * Canonical value. + */ +template <typename T> +class AbstractMirror { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractMirror) + AbstractMirror(AbstractThread* aThread) : mOwnerThread(aThread) {} + virtual void UpdateValue(const T& aNewValue) = 0; + virtual void NotifyDisconnected() = 0; + + AbstractThread* OwnerThread() const { return mOwnerThread; } + + protected: + virtual ~AbstractMirror() {} + RefPtr<AbstractThread> mOwnerThread; +}; + +/* + * Canonical<T> is a wrapper class that allows a given value to be mirrored by + * other threads. It maintains a list of active mirrors, and queues updates for + * them when the internal value changes. When changing the value, the caller + * needs to pass a TaskDispatcher object, which fires the updates at the + * appropriate time. Canonical<T> is also a WatchTarget, and may be set up to + * trigger other routines (on the same thread) when the canonical value changes. + * + * Canonical<T> is intended to be used as a member variable, so it doesn't + * actually inherit AbstractCanonical<T> (a refcounted type). Rather, it + * contains an inner class called |Impl| that implements most of the interesting + * logic. + */ +template <typename T> +class Canonical { + public: + Canonical(AbstractThread* aThread, const T& aInitialValue, + const char* aName) { + mImpl = new Impl(aThread, aInitialValue, aName); + } + + ~Canonical() {} + + private: + class Impl : public AbstractCanonical<T>, public WatchTarget { + public: + using AbstractCanonical<T>::OwnerThread; + + Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName) + : AbstractCanonical<T>(aThread), + WatchTarget(aName), + mValue(aInitialValue) { + MIRROR_LOG("%s [%p] initialized", mName, this); + MOZ_ASSERT(aThread->SupportsTailDispatch(), + "Can't get coherency without tail dispatch"); + } + + void AddMirror(AbstractMirror<T>* aMirror) override { + MIRROR_LOG("%s [%p] adding mirror %p", mName, this, aMirror); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(!mMirrors.Contains(aMirror)); + mMirrors.AppendElement(aMirror); + aMirror->OwnerThread()->DispatchStateChange(MakeNotifier(aMirror)); + } + + void RemoveMirror(AbstractMirror<T>* aMirror) override { + MIRROR_LOG("%s [%p] removing mirror %p", mName, this, aMirror); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(mMirrors.Contains(aMirror)); + mMirrors.RemoveElement(aMirror); + } + + void DisconnectAll() { + MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this); + for (size_t i = 0; i < mMirrors.Length(); ++i) { + mMirrors[i]->OwnerThread()->Dispatch( + NewRunnableMethod("AbstractMirror::NotifyDisconnected", mMirrors[i], + &AbstractMirror<T>::NotifyDisconnected)); + } + mMirrors.Clear(); + } + + operator const T&() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + return mValue; + } + + void Set(const T& aNewValue) { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + + if (aNewValue == mValue) { + return; + } + + // Notify same-thread watchers. The state watching machinery will make + // sure that notifications run at the right time. + NotifyWatchers(); + + // Check if we've already got a pending update. If so we won't schedule + // another one. + bool alreadyNotifying = mInitialValue.isSome(); + + // Stash the initial value if needed, then update to the new value. + if (mInitialValue.isNothing()) { + mInitialValue.emplace(mValue); + } + mValue = aNewValue; + + // We wait until things have stablized before sending state updates so + // that we can avoid sending multiple updates, and possibly avoid sending + // any updates at all if the value ends up where it started. + if (!alreadyNotifying) { + AbstractThread::DispatchDirectTask(NewRunnableMethod( + "Canonical::Impl::DoNotify", this, &Impl::DoNotify)); + } + } + + Impl& operator=(const T& aNewValue) { + Set(aNewValue); + return *this; + } + Impl& operator=(const Impl& aOther) { + Set(aOther); + return *this; + } + Impl(const Impl& aOther) = delete; + + protected: + ~Impl() { MOZ_DIAGNOSTIC_ASSERT(mMirrors.IsEmpty()); } + + private: + void DoNotify() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(mInitialValue.isSome()); + bool same = mInitialValue.ref() == mValue; + mInitialValue.reset(); + + if (same) { + MIRROR_LOG("%s [%p] unchanged - not sending update", mName, this); + return; + } + + for (size_t i = 0; i < mMirrors.Length(); ++i) { + mMirrors[i]->OwnerThread()->DispatchStateChange( + MakeNotifier(mMirrors[i])); + } + } + + already_AddRefed<nsIRunnable> MakeNotifier(AbstractMirror<T>* aMirror) { + return NewRunnableMethod<T>("AbstractMirror::UpdateValue", aMirror, + &AbstractMirror<T>::UpdateValue, mValue); + ; + } + + T mValue; + Maybe<T> mInitialValue; + nsTArray<RefPtr<AbstractMirror<T>>> mMirrors; + }; + + public: + // NB: Because mirror-initiated disconnection can race with canonical- + // initiated disconnection, a canonical should never be reinitialized. + // Forward control operations to the Impl. + void DisconnectAll() { return mImpl->DisconnectAll(); } + + // Access to the Impl. + operator Impl&() { return *mImpl; } + Impl* operator&() { return mImpl; } + + // Access to the T. + const T& Ref() const { return *mImpl; } + operator const T&() const { return Ref(); } + void Set(const T& aNewValue) { mImpl->Set(aNewValue); } + Canonical& operator=(const T& aNewValue) { + Set(aNewValue); + return *this; + } + Canonical& operator=(const Canonical& aOther) { + Set(aOther); + return *this; + } + Canonical(const Canonical& aOther) = delete; + + private: + RefPtr<Impl> mImpl; +}; + +/* + * Mirror<T> is a wrapper class that allows a given value to mirror that of a + * Canonical<T> owned by another thread. It registers itself with a + * Canonical<T>, and is periodically updated with new values. Mirror<T> is also + * a WatchTarget, and may be set up to trigger other routines (on the same + * thread) when the mirrored value changes. + * + * Mirror<T> is intended to be used as a member variable, so it doesn't actually + * inherit AbstractMirror<T> (a refcounted type). Rather, it contains an inner + * class called |Impl| that implements most of the interesting logic. + */ +template <typename T> +class Mirror { + public: + Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName) { + mImpl = new Impl(aThread, aInitialValue, aName); + } + + ~Mirror() { + // As a member of complex objects, a Mirror<T> may be destroyed on a + // different thread than its owner, or late in shutdown during CC. Given + // that, we require manual disconnection so that callers can put things in + // the right place. + MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected()); + } + + private: + class Impl : public AbstractMirror<T>, public WatchTarget { + public: + using AbstractMirror<T>::OwnerThread; + + Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName) + : AbstractMirror<T>(aThread), + WatchTarget(aName), + mValue(aInitialValue) { + MIRROR_LOG("%s [%p] initialized", mName, this); + MOZ_ASSERT(aThread->SupportsTailDispatch(), + "Can't get coherency without tail dispatch"); + } + + operator const T&() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + return mValue; + } + + virtual void UpdateValue(const T& aNewValue) override { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + if (mValue != aNewValue) { + mValue = aNewValue; + WatchTarget::NotifyWatchers(); + } + } + + virtual void NotifyDisconnected() override { + MIRROR_LOG("%s [%p] Notifed of disconnection from %p", mName, this, + mCanonical.get()); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + mCanonical = nullptr; + } + + bool IsConnected() const { return !!mCanonical; } + + void Connect(AbstractCanonical<T>* aCanonical) { + MIRROR_LOG("%s [%p] Connecting to %p", mName, this, aCanonical); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(!IsConnected()); + MOZ_ASSERT(OwnerThread()->RequiresTailDispatch(aCanonical->OwnerThread()), + "Can't get coherency without tail dispatch"); + + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>( + "AbstractCanonical::AddMirror", aCanonical, + &AbstractCanonical<T>::AddMirror, this); + aCanonical->OwnerThread()->Dispatch(r.forget()); + mCanonical = aCanonical; + } + + public: + void DisconnectIfConnected() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + if (!IsConnected()) { + return; + } + + MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this, + mCanonical.get()); + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>( + "AbstractCanonical::RemoveMirror", mCanonical, + &AbstractCanonical<T>::RemoveMirror, this); + mCanonical->OwnerThread()->Dispatch(r.forget()); + mCanonical = nullptr; + } + + protected: + ~Impl() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); } + + private: + T mValue; + RefPtr<AbstractCanonical<T>> mCanonical; + }; + + public: + // Forward control operations to the Impl<T>. + void Connect(AbstractCanonical<T>* aCanonical) { mImpl->Connect(aCanonical); } + void DisconnectIfConnected() { mImpl->DisconnectIfConnected(); } + + // Access to the Impl<T>. + operator Impl&() { return *mImpl; } + Impl* operator&() { return mImpl; } + + // Access to the T. + const T& Ref() const { return *mImpl; } + operator const T&() const { return Ref(); } + + private: + RefPtr<Impl> mImpl; +}; + +# undef MIRROR_LOG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/StateWatching.h b/xpcom/threads/StateWatching.h new file mode 100644 index 0000000000..3da0c63bfe --- /dev/null +++ b/xpcom/threads/StateWatching.h @@ -0,0 +1,302 @@ +/* -*- 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/. */ + +#if !defined(StateWatching_h_) +# define StateWatching_h_ + +# include <cstddef> +# include <new> +# include <utility> +# include "mozilla/AbstractThread.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/RefPtr.h" +# include "nsISupports.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +/* + * The state-watching machinery automates the process of responding to changes + * in various pieces of state. + * + * A standard programming pattern is as follows: + * + * mFoo = ...; + * NotifyStuffChanged(); + * ... + * mBar = ...; + * NotifyStuffChanged(); + * + * This pattern is error-prone and difficult to audit because it requires the + * programmer to manually trigger the update routine. This can be especially + * problematic when the update routine depends on numerous pieces of state, and + * when that state is modified across a variety of helper methods. In these + * cases the responsibility for invoking the routine is often unclear, causing + * developers to scatter calls to it like pixie dust. This can result in + * duplicate invocations (which is wasteful) and missing invocations in corner- + * cases (which is a source of bugs). + * + * This file provides a set of primitives that automatically handle updates and + * allow the programmers to explicitly construct a graph of state dependencies. + * When used correctly, it eliminates the guess-work and wasted cycles described + * above. + * + * There are two basic pieces: + * (1) Objects that can be watched for updates. These inherit WatchTarget. + * (2) Objects that receive objects and trigger processing. These inherit + * AbstractWatcher. In the current machinery, these exist only internally + * within the WatchManager, though that could change. + * + * Note that none of this machinery is thread-safe - it must all happen on the + * same owning thread. To solve multi-threaded use-cases, use state mirroring + * and watch the mirrored value. + * + * Given that semantics may change and comments tend to go out of date, we + * deliberately don't provide usage examples here. Grep around to find them. + */ + +namespace mozilla { + +extern LazyLogModule gStateWatchingLog; + +# define WATCH_LOG(x, ...) \ + MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) + +/* + * AbstractWatcher is a superclass from which all watchers must inherit. + */ +class AbstractWatcher { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher) + AbstractWatcher() : mDestroyed(false) {} + bool IsDestroyed() { return mDestroyed; } + virtual void Notify() = 0; + + protected: + virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); } + bool mDestroyed; +}; + +/* + * WatchTarget is a superclass from which all watchable things must inherit. + * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass + * needs only to invoke NotifyWatchers when something changes. + * + * The functionality that this class provides is not threadsafe, and should only + * be used on the thread that owns that WatchTarget. + */ +class WatchTarget { + public: + explicit WatchTarget(const char* aName) : mName(aName) {} + + void AddWatcher(AbstractWatcher* aWatcher) { + MOZ_ASSERT(!mWatchers.Contains(aWatcher)); + mWatchers.AppendElement(aWatcher); + } + + void RemoveWatcher(AbstractWatcher* aWatcher) { + MOZ_ASSERT(mWatchers.Contains(aWatcher)); + mWatchers.RemoveElement(aWatcher); + } + + protected: + void NotifyWatchers() { + WATCH_LOG("%s[%p] notifying watchers\n", mName, this); + PruneWatchers(); + for (size_t i = 0; i < mWatchers.Length(); ++i) { + mWatchers[i]->Notify(); + } + } + + private: + // We don't have Watchers explicitly unregister themselves when they die, + // because then they'd need back-references to all the WatchTargets they're + // subscribed to, and WatchTargets aren't reference-counted. So instead we + // just prune dead ones at appropriate times, which works just fine. + void PruneWatchers() { + mWatchers.RemoveElementsBy( + [](const auto& watcher) { return watcher->IsDestroyed(); }); + } + + nsTArray<RefPtr<AbstractWatcher>> mWatchers; + + protected: + const char* mName; +}; + +/* + * Watchable is a wrapper class that turns any primitive into a WatchTarget. + */ +template <typename T> +class Watchable : public WatchTarget { + public: + Watchable(const T& aInitialValue, const char* aName) + : WatchTarget(aName), mValue(aInitialValue) {} + + const T& Ref() const { return mValue; } + operator const T&() const { return Ref(); } + Watchable& operator=(const T& aNewValue) { + if (aNewValue != mValue) { + mValue = aNewValue; + NotifyWatchers(); + } + + return *this; + } + + private: + Watchable(const Watchable& aOther) = delete; + Watchable& operator=(const Watchable& aOther) = delete; + + T mValue; +}; + +// Manager class for state-watching. Declare one of these in any class for which +// you want to invoke method callbacks. +// +// Internally, WatchManager maintains one AbstractWatcher per callback method. +// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple. +// This causes an AbstractWatcher for |Callback| to be instantiated if it +// doesn't already exist, and registers it with |WatchTarget|. +// +// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire +// watch callbacks no more than once per task, once all other operations for +// that task have been completed. +// +// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType| +// objects. Given that, it and its owned objects can't hold permanent strong +// refs to the owner, since that would keep the owner alive indefinitely. +// Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire. +// This ensures that everything is kept alive just long enough. +template <typename OwnerType> +class WatchManager { + public: + typedef void (OwnerType::*CallbackMethod)(); + explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread) + : mOwner(aOwner), mOwnerThread(aOwnerThread) {} + + ~WatchManager() { + if (!IsShutdown()) { + Shutdown(); + } + } + + bool IsShutdown() const { return !mOwner; } + + // Shutdown needs to happen on mOwnerThread. If the WatchManager will be + // destroyed on a different thread, Shutdown() must be called manually. + void Shutdown() { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + for (auto& watcher : mWatchers) { + watcher->Destroy(); + } + mWatchers.Clear(); + mOwner = nullptr; + } + + void Watch(WatchTarget& aTarget, CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + aTarget.AddWatcher(&EnsureWatcher(aMethod)); + } + + void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + MOZ_ASSERT(watcher); + aTarget.RemoveWatcher(watcher); + } + + void ManualNotify(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + MOZ_ASSERT(watcher); + watcher->Notify(); + } + + private: + class PerCallbackWatcher : public AbstractWatcher { + public: + PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread, + CallbackMethod aMethod) + : mOwner(aOwner), + mOwnerThread(aOwnerThread), + mCallbackMethod(aMethod) {} + + void Destroy() { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + mDestroyed = true; + mOwner = nullptr; + } + + void Notify() override { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_DIAGNOSTIC_ASSERT(mOwner, + "mOwner is only null after destruction, " + "at which point we shouldn't be notified"); + if (mNotificationPending) { + // We've already got a notification job in the pipe. + return; + } + mNotificationPending = true; + + // Queue up our notification jobs to run in a stable state. + AbstractThread::DispatchDirectTask( + NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify", + [self = RefPtr<PerCallbackWatcher>(this), + owner = RefPtr<OwnerType>(mOwner)]() { + if (!self->mDestroyed) { + ((*owner).*(self->mCallbackMethod))(); + } + self->mNotificationPending = false; + })); + } + + bool CallbackMethodIs(CallbackMethod aMethod) const { + return mCallbackMethod == aMethod; + } + + private: + ~PerCallbackWatcher() = default; + + OwnerType* mOwner; // Never null. + bool mNotificationPending = false; + RefPtr<AbstractThread> mOwnerThread; + CallbackMethod mCallbackMethod; + }; + + PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + for (auto& watcher : mWatchers) { + if (watcher->CallbackMethodIs(aMethod)) { + return watcher; + } + } + return nullptr; + } + + PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + if (watcher) { + return *watcher; + } + watcher = mWatchers + .AppendElement(MakeAndAddRef<PerCallbackWatcher>( + mOwner, mOwnerThread, aMethod)) + ->get(); + return *watcher; + } + + nsTArray<RefPtr<PerCallbackWatcher>> mWatchers; + OwnerType* mOwner; + RefPtr<AbstractThread> mOwnerThread; +}; + +# undef WATCH_LOG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/SyncRunnable.h b/xpcom/threads/SyncRunnable.h new file mode 100644 index 0000000000..49235e764a --- /dev/null +++ b/xpcom/threads/SyncRunnable.h @@ -0,0 +1,133 @@ +/* -*- 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/. */ + +#ifndef mozilla_SyncRunnable_h +#define mozilla_SyncRunnable_h + +#include <utility> + +#include "mozilla/AbstractThread.h" +#include "mozilla/Monitor.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +/** + * This class will wrap a nsIRunnable and dispatch it to the main thread + * synchronously. This is different from nsIEventTarget.DISPATCH_SYNC: + * this class does not spin the event loop waiting for the event to be + * dispatched. This means that you don't risk reentrance from pending + * messages, but you must be sure that the target thread does not ever block + * on this thread, or else you will deadlock. + * + * Typical usage: + * RefPtr<SyncRunnable> sr = new SyncRunnable(new myrunnable...()); + * sr->DispatchToThread(t); + * + * We also provide a convenience wrapper: + * SyncRunnable::DispatchToThread(new myrunnable...()); + * + */ +class SyncRunnable : public Runnable { + public: + explicit SyncRunnable(nsIRunnable* aRunnable) + : Runnable("SyncRunnable"), + mRunnable(aRunnable), + mMonitor("SyncRunnable"), + mDone(false) {} + + explicit SyncRunnable(already_AddRefed<nsIRunnable> aRunnable) + : Runnable("SyncRunnable"), + mRunnable(std::move(aRunnable)), + mMonitor("SyncRunnable"), + mDone(false) {} + + nsresult DispatchToThread(nsIEventTarget* aThread, + bool aForceDispatch = false) { + nsresult rv; + bool on; + + if (!aForceDispatch) { + rv = aThread->IsOnCurrentThread(&on); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv) && on) { + mRunnable->Run(); + return NS_OK; + } + } + + rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) { + mozilla::MonitorAutoLock lock(mMonitor); + // This could be synchronously dispatching to a thread currently waiting + // for JS execution clearance. Yield JS execution. + dom::AutoYieldJSThreadExecution yield; + + while (!mDone) { + lock.Wait(); + } + } + return rv; + } + + nsresult DispatchToThread(AbstractThread* aThread, + bool aForceDispatch = false) { + if (!aForceDispatch && aThread->IsCurrentThreadIn()) { + mRunnable->Run(); + return NS_OK; + } + + // Check we don't have tail dispatching here. Otherwise we will deadlock + // ourself when spinning the loop below. + MOZ_ASSERT(!aThread->RequiresTailDispatchFromCurrentThread()); + + nsresult rv = aThread->Dispatch(RefPtr<nsIRunnable>(this).forget()); + if (NS_SUCCEEDED(rv)) { + mozilla::MonitorAutoLock lock(mMonitor); + while (!mDone) { + lock.Wait(); + } + } + return rv; + } + + static nsresult DispatchToThread(nsIEventTarget* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) { + RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable)); + return s->DispatchToThread(aThread, aForceDispatch); + } + + static nsresult DispatchToThread(AbstractThread* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) { + RefPtr<SyncRunnable> s(new SyncRunnable(aRunnable)); + return s->DispatchToThread(aThread, aForceDispatch); + } + + protected: + NS_IMETHOD Run() override { + mRunnable->Run(); + + mozilla::MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(!mDone); + + mDone = true; + mMonitor.Notify(); + + return NS_OK; + } + + private: + nsCOMPtr<nsIRunnable> mRunnable; + mozilla::Monitor mMonitor; + bool mDone; +}; + +} // namespace mozilla + +#endif // mozilla_SyncRunnable_h diff --git a/xpcom/threads/SynchronizedEventQueue.cpp b/xpcom/threads/SynchronizedEventQueue.cpp new file mode 100644 index 0000000000..59161b7f9d --- /dev/null +++ b/xpcom/threads/SynchronizedEventQueue.cpp @@ -0,0 +1,26 @@ +/* -*- 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 "SynchronizedEventQueue.h" +#include "nsIThreadInternal.h" + +using namespace mozilla; + +void SynchronizedEventQueue::AddObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ASSERT(!mEventObservers.Contains(aObserver)); + mEventObservers.AppendElement(aObserver); +} + +void SynchronizedEventQueue::RemoveObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ALWAYS_TRUE(mEventObservers.RemoveElement(aObserver)); +} + +const nsTObserverArray<nsCOMPtr<nsIThreadObserver>>& +SynchronizedEventQueue::EventObservers() { + return mEventObservers; +} diff --git a/xpcom/threads/SynchronizedEventQueue.h b/xpcom/threads/SynchronizedEventQueue.h new file mode 100644 index 0000000000..81a48fa51b --- /dev/null +++ b/xpcom/threads/SynchronizedEventQueue.h @@ -0,0 +1,119 @@ +/* -*- 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/. */ + +#ifndef mozilla_SynchronizedEventQueue_h +#define mozilla_SynchronizedEventQueue_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/EventQueue.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "nsIThreadInternal.h" +#include "nsCOMPtr.h" +#include "nsTObserverArray.h" + +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThreadObserver; + +namespace mozilla { + +// A SynchronizedEventQueue is an abstract class for event queues that can be +// used across threads. A SynchronizedEventQueue implementation will typically +// use locks and condition variables to guarantee consistency. The methods of +// SynchronizedEventQueue are split between ThreadTargetSink (which contains +// methods for posting events) and SynchronizedEventQueue (which contains +// methods for getting events). This split allows event targets (specifically +// ThreadEventTarget) to use a narrow interface, since they only need to post +// events. +// +// ThreadEventQueue is the canonical implementation of +// SynchronizedEventQueue. When Quantum DOM is implemented, we will use a +// different synchronized queue on the main thread, SchedulerEventQueue, which +// will handle the cooperative threading model. + +class ThreadTargetSink { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadTargetSink) + + virtual bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) = 0; + + // After this method is called, no more events can be posted. + virtual void Disconnect(const MutexAutoLock& aProofOfLock) = 0; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + virtual size_t SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const = 0; + + protected: + virtual ~ThreadTargetSink() = default; +}; + +class SynchronizedEventQueue : public ThreadTargetSink { + public: + virtual already_AddRefed<nsIRunnable> GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) = 0; + virtual bool HasPendingEvent() = 0; + + // This method atomically checks if there are pending events and, if there are + // none, forbids future events from being posted. It returns true if there + // were no pending events. + virtual bool ShutdownIfNoPendingEvents() = 0; + + // These methods provide access to an nsIThreadObserver, whose methods are + // called when posting and processing events. SetObserver should only be + // called on the thread that processes events. GetObserver can be called from + // any thread. GetObserverOnThread must be used from the thread that processes + // events; it does not acquire a lock. + virtual already_AddRefed<nsIThreadObserver> GetObserver() = 0; + virtual already_AddRefed<nsIThreadObserver> GetObserverOnThread() = 0; + virtual void SetObserver(nsIThreadObserver* aObserver) = 0; + + void AddObserver(nsIThreadObserver* aObserver); + void RemoveObserver(nsIThreadObserver* aObserver); + const nsTObserverArray<nsCOMPtr<nsIThreadObserver>>& EventObservers(); + + size_t SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const override { + return mEventObservers.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * This method causes any events currently enqueued on the thread to be + * suppressed until PopEventQueue is called, and any event dispatched to this + * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be + * nested and must each be paired with a call to PopEventQueue in order to + * restore the original state of the thread. The returned nsIEventTarget may + * be used to push events onto the nested queue. Dispatching will be disabled + * once the event queue is popped. The thread will only ever process pending + * events for the innermost event queue. Must only be called on the target + * thread. + */ + virtual already_AddRefed<nsISerialEventTarget> PushEventQueue() = 0; + + /** + * Revert a call to PushEventQueue. When an event queue is popped, any events + * remaining in the queue are appended to the elder queue. This also causes + * the nsIEventTarget returned from PushEventQueue to stop dispatching events. + * Must only be called on the target thread, and with the innermost event + * queue. + */ + virtual void PopEventQueue(nsIEventTarget* aTarget) = 0; + + protected: + virtual ~SynchronizedEventQueue() = default; + + private: + nsTObserverArray<nsCOMPtr<nsIThreadObserver>> mEventObservers; +}; + +} // namespace mozilla + +#endif // mozilla_SynchronizedEventQueue_h diff --git a/xpcom/threads/TaskCategory.h b/xpcom/threads/TaskCategory.h new file mode 100644 index 0000000000..8f189c8737 --- /dev/null +++ b/xpcom/threads/TaskCategory.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#ifndef mozilla_TaskCategory_h +#define mozilla_TaskCategory_h + +namespace mozilla { + +// The different kinds of tasks we can dispatch to a SystemGroup, TabGroup, or +// DocGroup. +enum class TaskCategory { + // User input (clicks, keypresses, etc.) + UI, + + // Data from the network + Network, + + // setTimeout, setInterval + Timer, + + // Runnables posted from a worker to the main thread + Worker, + + // requestIdleCallback + IdleCallback, + + // Vsync notifications + RefreshDriver, + + // GC/CC-related tasks + GarbageCollection, + + // Most DOM events (postMessage, media, plugins) + Other, + + // Runnables related to Performance Counting + Performance, + + Count +}; + +} // namespace mozilla + +#endif // mozilla_TaskCategory_h diff --git a/xpcom/threads/TaskController.cpp b/xpcom/threads/TaskController.cpp new file mode 100644 index 0000000000..a30353bc20 --- /dev/null +++ b/xpcom/threads/TaskController.cpp @@ -0,0 +1,936 @@ +/* -*- 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 "TaskController.h" +#include "nsIIdleRunnable.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" +#include <algorithm> +#include <initializer_list> +#include "GeckoProfiler.h" +#include "mozilla/EventQueue.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" +#include "nsIThreadInternal.h" +#include "nsQueryObject.h" +#include "nsThread.h" +#include "prenv.h" +#include "prsystem.h" + +#ifdef XP_WIN +typedef HRESULT(WINAPI* SetThreadDescriptionPtr)(HANDLE hThread, + PCWSTR lpThreadDescription); +#endif + +namespace mozilla { + +std::unique_ptr<TaskController> TaskController::sSingleton; +thread_local size_t mThreadPoolIndex = -1; +std::atomic<uint64_t> Task::sCurrentTaskSeqNo = 0; + +const int32_t kMaximumPoolThreadCount = 8; + +static int32_t GetPoolThreadCount() { + if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) { + return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0); + } + + int32_t numCores = std::max<int32_t>(1, PR_GetNumberOfProcessors()); + + if (numCores == 1) { + return 1; + } + if (numCores == 2) { + return 2; + } + return std::min<int32_t>(kMaximumPoolThreadCount, numCores - 1); +} + +bool TaskManager:: + UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + const MutexAutoLock& aProofOfLock, IterationType aIterationType) { + mCurrentSuspended = IsSuspended(aProofOfLock); + + if (aIterationType == IterationType::EVENT_LOOP_TURN) { + int32_t oldModifier = mCurrentPriorityModifier; + mCurrentPriorityModifier = + GetPriorityModifierForEventLoopTurn(aProofOfLock); + + if (mCurrentPriorityModifier != oldModifier) { + return true; + } + } + return false; +} + +Task* Task::GetHighestPriorityDependency() { + Task* currentTask = this; + + while (!currentTask->mDependencies.empty()) { + auto iter = currentTask->mDependencies.begin(); + + while (iter != currentTask->mDependencies.end()) { + if ((*iter)->mCompleted) { + auto oldIter = iter; + iter++; + // Completed tasks are removed here to prevent needlessly keeping them + // alive or iterating over them in the future. + currentTask->mDependencies.erase(oldIter); + continue; + } + + currentTask = iter->get(); + break; + } + } + + return currentTask == this ? nullptr : currentTask; +} + +TaskController* TaskController::Get() { + MOZ_ASSERT(sSingleton.get()); + return sSingleton.get(); +} + +bool TaskController::Initialize() { + MOZ_ASSERT(!sSingleton); + sSingleton = std::make_unique<TaskController>(); + return sSingleton->InitializeInternal(); +} + +void ThreadFuncPoolThread(void* aIndex) { + mThreadPoolIndex = *reinterpret_cast<int32_t*>(aIndex); + delete reinterpret_cast<int32_t*>(aIndex); + TaskController::Get()->RunPoolThread(); +} + +#ifdef XP_WIN +static SetThreadDescriptionPtr sSetThreadDescriptionFunc = nullptr; +#endif + +bool TaskController::InitializeInternal() { + InputTaskManager::Init(); + mMTProcessingRunnable = NS_NewRunnableFunction( + "TaskController::ExecutePendingMTTasks()", + []() { TaskController::Get()->ProcessPendingMTTask(); }); + mMTBlockingProcessingRunnable = NS_NewRunnableFunction( + "TaskController::ExecutePendingMTTasks()", + []() { TaskController::Get()->ProcessPendingMTTask(true); }); + +#ifdef XP_WIN + sSetThreadDescriptionFunc = + reinterpret_cast<SetThreadDescriptionPtr>(::GetProcAddress( + ::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription")); +#endif + + return true; +} + +// Ample stack size allocated for applications like ImageLib's AV1 decoder. +const PRUint32 sStackSize = 512u * 1024u; + +void TaskController::InitializeThreadPool() { + mPoolInitializationMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!mThreadPoolInitialized); + mThreadPoolInitialized = true; + + int32_t poolSize = GetPoolThreadCount(); + for (int32_t i = 0; i < poolSize; i++) { + int32_t* index = new int32_t(i); + mPoolThreads.push_back( + {PR_CreateThread(PR_USER_THREAD, ThreadFuncPoolThread, index, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 512u * 1024u), + nullptr}); + } +} + +void TaskController::SetPerformanceCounterState( + PerformanceCounterState* aPerformanceCounterState) { + mPerformanceCounterState = aPerformanceCounterState; +} + +/* static */ +void TaskController::Shutdown() { + InputTaskManager::Cleanup(); + if (sSingleton) { + sSingleton->ShutdownThreadPoolInternal(); + sSingleton->ShutdownInternal(); + } + MOZ_ASSERT(!sSingleton); +} + +void TaskController::ShutdownThreadPoolInternal() { + { + // Prevent racecondition on mShuttingDown and wait. + MutexAutoLock lock(mGraphMutex); + + mShuttingDown = true; + mThreadPoolCV.NotifyAll(); + } + for (PoolThread& thread : mPoolThreads) { + PR_JoinThread(thread.mThread); + } +} + +void TaskController::ShutdownInternal() { sSingleton = nullptr; } + +void TaskController::RunPoolThread() { + IOInterposer::RegisterCurrentThread(); + + // This is used to hold on to a task to make sure it is released outside the + // lock. This is required since it's perfectly feasible for task destructors + // to post events themselves. + RefPtr<Task> lastTask; + +#ifdef XP_WIN + nsAutoString threadWName; + threadWName.AppendLiteral(u"TaskController Thread #"); + threadWName.AppendInt(static_cast<int64_t>(mThreadPoolIndex)); + + if (sSetThreadDescriptionFunc) { + sSetThreadDescriptionFunc( + ::GetCurrentThread(), + reinterpret_cast<const WCHAR*>(threadWName.BeginReading())); + } +#endif + nsAutoCString threadName; + threadName.AppendLiteral("TaskController Thread #"); + threadName.AppendInt(static_cast<int64_t>(mThreadPoolIndex)); + PROFILER_REGISTER_THREAD(threadName.BeginReading()); + + MutexAutoLock lock(mGraphMutex); + while (true) { + bool ranTask = false; + + if (!mThreadableTasks.empty()) { + for (auto iter = mThreadableTasks.begin(); iter != mThreadableTasks.end(); + ++iter) { + // Search for the highest priority dependency of the highest priority + // task. + + // We work with rawptrs to avoid needless refcounting. All our tasks + // are always kept alive by the graph. If one is removed from the graph + // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask. + Task* task = iter->get(); + + MOZ_ASSERT(!task->mTaskManager); + + mPoolThreads[mThreadPoolIndex].mEffectiveTaskPriority = + task->GetPriority(); + + Task* nextTask; + while ((nextTask = task->GetHighestPriorityDependency())) { + task = nextTask; + } + + if (task->IsMainThreadOnly() || task->mInProgress) { + continue; + } + + mPoolThreads[mThreadPoolIndex].mCurrentTask = task; + mThreadableTasks.erase(task->mIterator); + task->mIterator = mThreadableTasks.end(); + task->mInProgress = true; + + bool taskCompleted = false; + { + MutexAutoUnlock unlock(mGraphMutex); + lastTask = nullptr; + taskCompleted = task->Run(); + ranTask = true; + } + + task->mInProgress = false; + + if (!taskCompleted) { + // Presumably this task was interrupted, leave its dependencies + // unresolved and reinsert into the queue. + auto insertion = mThreadableTasks.insert( + mPoolThreads[mThreadPoolIndex].mCurrentTask); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + } else { + task->mCompleted = true; +#ifdef DEBUG + task->mIsInGraph = false; +#endif + task->mDependencies.clear(); + // This may have unblocked a main thread task. We could do this only + // if there was a main thread task before this one in the dependency + // chain. + mMayHaveMainThreadTask = true; + // Since this could have multiple dependencies thare are restricted + // to the main thread. Let's make sure that's awake. + EnsureMainThreadTasksScheduled(); + + MaybeInterruptTask(GetHighestPriorityMTTask()); + } + + // Store last task for release next time we release the lock or enter + // wait state. + lastTask = mPoolThreads[mThreadPoolIndex].mCurrentTask.forget(); + break; + } + } + + // Ensure the last task is released before we enter the wait state. + if (lastTask) { + MutexAutoUnlock unlock(mGraphMutex); + lastTask = nullptr; + + // Run another loop iteration, while we were unlocked there was an + // opportunity for another task to be posted or shutdown to be initiated. + continue; + } + + if (!ranTask) { + if (mShuttingDown) { + IOInterposer::UnregisterCurrentThread(); + MOZ_ASSERT(mThreadableTasks.empty()); + return; + } + + AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE); + mThreadPoolCV.Wait(); + } + } +} + +void TaskController::AddTask(already_AddRefed<Task>&& aTask) { + RefPtr<Task> task(aTask); + + if (!task->IsMainThreadOnly()) { + MutexAutoLock lock(mPoolInitializationMutex); + if (!mThreadPoolInitialized) { + InitializeThreadPool(); + mThreadPoolInitialized = true; + } + } + + MutexAutoLock lock(mGraphMutex); + + if (TaskManager* manager = task->GetManager()) { + if (manager->mTaskCount == 0) { + mTaskManagers.insert(manager); + } + manager->DidQueueTask(); + + // Set this here since if this manager's priority modifier doesn't change + // we will not reprioritize when iterating over the queue. + task->mPriorityModifier = manager->mCurrentPriorityModifier; + } + +#ifdef MOZ_GECKO_PROFILER + task->mInsertionTime = TimeStamp::Now(); +#endif + +#ifdef DEBUG + task->mIsInGraph = true; + + for (const RefPtr<Task>& otherTask : task->mDependencies) { + MOZ_ASSERT(!otherTask->mTaskManager || + otherTask->mTaskManager == task->mTaskManager); + } +#endif + + LogTask::LogDispatch(task); + + std::pair<std::set<RefPtr<Task>, Task::PriorityCompare>::iterator, bool> + insertion; + if (task->IsMainThreadOnly()) { + insertion = mMainThreadTasks.insert(std::move(task)); + } else { + insertion = mThreadableTasks.insert(std::move(task)); + } + (*insertion.first)->mIterator = insertion.first; + MOZ_ASSERT(insertion.second); + + MaybeInterruptTask(*insertion.first); +} + +void TaskController::WaitForTaskOrMessage() { + MutexAutoLock lock(mGraphMutex); + while (!mMayHaveMainThreadTask) { + AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE); + mMainThreadCV.Wait(); + } +} + +void TaskController::ExecuteNextTaskOnlyMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mGraphMutex); + ExecuteNextTaskOnlyMainThreadInternal(lock); +} + +void TaskController::ProcessPendingMTTask(bool aMayWait) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mGraphMutex); + + for (;;) { + // We only ever process one event here. However we may sometimes + // not actually process a real event because of suspended tasks. + // This loop allows us to wait until we've processed something + // in that scenario. + + mMTTaskRunnableProcessedTask = ExecuteNextTaskOnlyMainThreadInternal(lock); + + if (mMTTaskRunnableProcessedTask || !aMayWait) { + break; + } + + BackgroundHangMonitor().NotifyWait(); + + { + // ProcessNextEvent will also have attempted to wait, however we may have + // given it a Runnable when all the tasks in our task graph were suspended + // but we weren't able to cheaply determine that. + AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE); + mMainThreadCV.Wait(); + } + + BackgroundHangMonitor().NotifyActivity(); + } + + if (mMayHaveMainThreadTask) { + EnsureMainThreadTasksScheduled(); + } +} + +void TaskController::ReprioritizeTask(Task* aTask, uint32_t aPriority) { + MutexAutoLock lock(mGraphMutex); + std::set<RefPtr<Task>, Task::PriorityCompare>* queue = &mMainThreadTasks; + if (!aTask->IsMainThreadOnly()) { + queue = &mThreadableTasks; + } + + MOZ_ASSERT(aTask->mIterator != queue->end()); + queue->erase(aTask->mIterator); + + aTask->mPriority = aPriority; + + auto insertion = queue->insert(aTask); + MOZ_ASSERT(insertion.second); + aTask->mIterator = insertion.first; + + MaybeInterruptTask(aTask); +} + +// Code supporting runnable compatibility. +// Task that wraps a runnable. +class RunnableTask : public Task { + public: + RunnableTask(already_AddRefed<nsIRunnable>&& aRunnable, int32_t aPriority, + bool aMainThread = true) + : Task(aMainThread, aPriority), mRunnable(aRunnable) {} + + virtual bool Run() override { +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + MOZ_ASSERT(NS_IsMainThread()); + // If we're on the main thread, we want to record our current + // runnable's name in a static so that BHR can record it. + Array<char, nsThread::kRunnableNameBufSize> restoreRunnableName; + restoreRunnableName[0] = '\0'; + auto clear = MakeScopeExit([&] { + MOZ_ASSERT(NS_IsMainThread()); + nsThread::sMainThreadRunnableName = restoreRunnableName; + }); + nsAutoCString name; + nsThread::GetLabeledRunnableName(mRunnable, name, + EventQueuePriority(GetPriority())); + + restoreRunnableName = nsThread::sMainThreadRunnableName; + + // Copy the name into sMainThreadRunnableName's buffer, and append a + // terminating null. + uint32_t length = std::min((uint32_t)nsThread::kRunnableNameBufSize - 1, + (uint32_t)name.Length()); + memcpy(nsThread::sMainThreadRunnableName.begin(), name.BeginReading(), + length); + nsThread::sMainThreadRunnableName[length] = '\0'; +#endif + + mRunnable->Run(); + mRunnable = nullptr; + return true; + } + + void SetIdleDeadline(TimeStamp aDeadline) override { + nsCOMPtr<nsIIdleRunnable> idleRunnable = do_QueryInterface(mRunnable); + if (idleRunnable) { + idleRunnable->SetDeadline(aDeadline); + } + } + + PerformanceCounter* GetPerformanceCounter() const override { + return nsThread::GetPerformanceCounterBase(mRunnable); + } + + virtual bool GetName(nsACString& aName) override { +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + nsThread::GetLabeledRunnableName(mRunnable, aName, + EventQueuePriority(GetPriority())); + return true; +#else + return false; +#endif + } + + private: + RefPtr<nsIRunnable> mRunnable; +}; + +void TaskController::DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aPriority, + TaskManager* aManager) { + RefPtr<RunnableTask> task = new RunnableTask(std::move(aRunnable), aPriority); + + task->SetManager(aManager); + TaskController::Get()->AddTask(task.forget()); +} + +nsIRunnable* TaskController::GetRunnableForMTTask(bool aReallyWait) { + MutexAutoLock lock(mGraphMutex); + + while (mMainThreadTasks.empty()) { + if (!aReallyWait) { + return nullptr; + } + + AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE); + mMainThreadCV.Wait(); + } + + return aReallyWait ? mMTBlockingProcessingRunnable : mMTProcessingRunnable; +} + +bool TaskController::HasMainThreadPendingTasks() { + auto resetIdleState = MakeScopeExit([&idleManager = mIdleTaskManager] { + if (idleManager) { + idleManager->State().ClearCachedIdleDeadline(); + } + }); + + for (bool considerIdle : {false, true}) { + if (considerIdle && !mIdleTaskManager) { + continue; + } + + MutexAutoLock lock(mGraphMutex); + + if (considerIdle) { + mIdleTaskManager->State().ForgetPendingTaskGuarantee(); + // Temporarily unlock so we can peek our idle deadline. + // XXX We could do this _before_ we take the lock if the API would let us. + // We do want to do this before looking at mMainThreadTasks, in case + // someone adds one while we're unlocked. + { + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().CachePeekedIdleDeadline(unlock); + } + } + + // Return early if there's no tasks at all. + if (mMainThreadTasks.empty()) { + return false; + } + + // We can cheaply count how many tasks are suspended. + uint64_t totalSuspended = 0; + for (TaskManager* manager : mTaskManagers) { + DebugOnly<bool> modifierChanged = + manager + ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + lock, TaskManager::IterationType::NOT_EVENT_LOOP_TURN); + MOZ_ASSERT(!modifierChanged); + + // The idle manager should be suspended unless we're doing the idle pass. + MOZ_ASSERT(manager != mIdleTaskManager || manager->mCurrentSuspended || + considerIdle, + "Why are idle tasks not suspended here?"); + + if (manager->mCurrentSuspended) { + // XXX - If managers manage off-main-thread tasks this breaks! This + // scenario is explicitly not supported. + // + // This is only incremented inside the lock -or- decremented on the main + // thread so this is safe. + totalSuspended += manager->mTaskCount; + } + } + + // Thi would break down if we have a non-suspended task depending on a + // suspended task. This is why for the moment we do not allow tasks + // to be dependent on tasks managed by another taskmanager. + if (mMainThreadTasks.size() > totalSuspended) { + // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended + // state of mIdleTaskManager above, hence shouldn't even check it here. + // But in that case idle tasks are not contributing to our suspended task + // count anyway. + if (mIdleTaskManager && mIdleTaskManager->mTaskCount && + !mIdleTaskManager->mCurrentSuspended) { + MOZ_ASSERT(considerIdle, "Why is mIdleTaskManager not suspended?"); + // Check whether the idle tasks were really needed to make our "we have + // an unsuspended task" decision. If they were, we need to force-enable + // idle tasks until we run our next task. + if (mMainThreadTasks.size() - mIdleTaskManager->mTaskCount <= + totalSuspended) { + mIdleTaskManager->State().EnforcePendingTaskGuarantee(); + } + } + return true; + } + } + return false; +} + +bool TaskController::ExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock) { + // Block to make it easier to jump to our cleanup. + bool taskRan = false; + do { + taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock); + if (taskRan) { + break; + } + + if (!mIdleTaskManager) { + break; + } + + if (mIdleTaskManager->mTaskCount) { + // We have idle tasks that we may not have gotten above because + // our idle state is not up to date. We need to update the idle state + // and try again. We need to temporarily release the lock while we do + // that. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().UpdateCachedIdleDeadline(unlock); + } else { + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RanOutOfTasks(unlock); + } + + // When we unlocked, someone may have queued a new task on us. So try to + // see whether we can run things again. + taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock); + } while (false); + + if (mIdleTaskManager) { + // The pending task guarantee is not needed anymore, since we just tried + // running a task + mIdleTaskManager->State().ForgetPendingTaskGuarantee(); + + if (mMainThreadTasks.empty()) { + // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it. + // Otherwise we could perhaps just do this after we exit the locked block, + // by pushing the lock down into this method. Though it's not clear that + // we could check mMainThreadTasks.size() once we unlock, and whether we + // could maybe substitute mMayHaveMainThreadTask for that check. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RanOutOfTasks(unlock); + } + } + + return taskRan; +} + +bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock) { + nsCOMPtr<nsIThread> mainIThread; + NS_GetMainThread(getter_AddRefs(mainIThread)); + nsThread* mainThread = static_cast<nsThread*>(mainIThread.get()); + mainThread->SetRunningEventDelay(TimeDuration(), TimeStamp()); + + uint32_t totalSuspended = 0; + for (TaskManager* manager : mTaskManagers) { + bool modifierChanged = + manager + ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + aProofOfLock, TaskManager::IterationType::EVENT_LOOP_TURN); + if (modifierChanged) { + ProcessUpdatedPriorityModifier(manager); + } + if (manager->mCurrentSuspended) { + totalSuspended += manager->mTaskCount; + } + } + + MOZ_ASSERT(mMainThreadTasks.size() >= totalSuspended); + + // This would break down if we have a non-suspended task depending on a + // suspended task. This is why for the moment we do not allow tasks + // to be dependent on tasks managed by another taskmanager. + if (mMainThreadTasks.size() > totalSuspended) { + for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end(); + iter++) { + Task* task = iter->get(); + + if (task->mTaskManager && task->mTaskManager->mCurrentSuspended) { + // Even though we may want to run some dependencies of this task, we + // will run them at their own priority level and not the priority + // level of their dependents. + continue; + } + + task = GetFinalDependency(task); + + if (!task->IsMainThreadOnly() || task->mInProgress || + (task->mTaskManager && task->mTaskManager->mCurrentSuspended)) { + continue; + } + + mCurrentTasksMT.push(task); + mMainThreadTasks.erase(task->mIterator); + task->mIterator = mMainThreadTasks.end(); + task->mInProgress = true; + TaskManager* manager = task->GetManager(); + bool result = false; + + { + MutexAutoUnlock unlock(mGraphMutex); + if (manager) { + manager->WillRunTask(); + if (manager != mIdleTaskManager) { + // Notify the idle period state that we're running a non-idle task. + // This needs to happen while our mutex is not locked! + mIdleTaskManager->State().FlagNotIdle(); + } else { + TimeStamp idleDeadline = + mIdleTaskManager->State().GetCachedIdleDeadline(); + MOZ_ASSERT( + idleDeadline, + "How can we not have a deadline if our manager is enabled?"); + task->SetIdleDeadline(idleDeadline); + } + } + if (mIdleTaskManager) { + // We found a task to run; we can clear the idle deadline on our idle + // task manager. This _must_ be done before we actually run the task, + // because running the task could reenter via spinning the event loop + // and we want to make sure there's no cached idle deadline at that + // point. But we have to make sure we do it after out SetIdleDeadline + // call above, in the case when the task is actually an idle task. + mIdleTaskManager->State().ClearCachedIdleDeadline(); + } + + TimeStamp now = TimeStamp::Now(); + +#ifdef MOZ_GECKO_PROFILER + if (task->GetPriority() < uint32_t(EventQueuePriority::InputHigh)) { + mainThread->SetRunningEventDelay(TimeDuration(), now); + } else { + mainThread->SetRunningEventDelay(now - task->mInsertionTime, now); + } +#endif + + PerformanceCounterState::Snapshot snapshot = + mPerformanceCounterState->RunnableWillRun( + task->GetPerformanceCounter(), now, + manager == mIdleTaskManager); + + { + LogTask::Run log(task); + result = task->Run(); + } + + // Task itself should keep manager alive. + if (manager) { + manager->DidRunTask(); + } + + mPerformanceCounterState->RunnableDidRun(std::move(snapshot)); + } + + // Task itself should keep manager alive. + if (manager && result && manager->mTaskCount == 0) { + mTaskManagers.erase(manager); + } + + task->mInProgress = false; + + if (!result) { + // Presumably this task was interrupted, leave its dependencies + // unresolved and reinsert into the queue. + auto insertion = + mMainThreadTasks.insert(std::move(mCurrentTasksMT.top())); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + manager->WillRunTask(); + } else { + task->mCompleted = true; +#ifdef DEBUG + task->mIsInGraph = false; +#endif + // Clear dependencies to release references. + task->mDependencies.clear(); + + if (!mThreadableTasks.empty()) { + // Since this could have multiple dependencies thare are not + // restricted to the main thread. Let's wake up our thread pool. + // There is a cost to this, it's possible we will want to wake up + // only as many threads as we have unblocked tasks, but we currently + // have no way to determine that easily. + mThreadPoolCV.NotifyAll(); + } + } + + mCurrentTasksMT.pop(); + return true; + } + } + + mMayHaveMainThreadTask = false; + if (mIdleTaskManager) { + // We did not find a task to run. We still need to clear the cached idle + // deadline on our idle state, because that deadline was only relevant to + // the execution of this function. Had we found a task, we would have + // cleared the deadline before running that task. + mIdleTaskManager->State().ClearCachedIdleDeadline(); + } + return false; +} + +Task* TaskController::GetFinalDependency(Task* aTask) { + Task* nextTask; + + while ((nextTask = aTask->GetHighestPriorityDependency())) { + aTask = nextTask; + } + + return aTask; +} + +void TaskController::MaybeInterruptTask(Task* aTask) { + mGraphMutex.AssertCurrentThreadOwns(); + + if (!aTask) { + return; + } + + // This optimization prevents many slow lookups in long chains of similar + // priority. + if (!aTask->mDependencies.empty()) { + Task* firstDependency = aTask->mDependencies.begin()->get(); + if (aTask->GetPriority() <= firstDependency->GetPriority() && + !firstDependency->mCompleted && + aTask->IsMainThreadOnly() == firstDependency->IsMainThreadOnly()) { + // This task has the same or a higher priority as one of its dependencies, + // never any need to interrupt. + return; + } + } + + Task* finalDependency = GetFinalDependency(aTask); + + if (finalDependency->mInProgress) { + // No need to wake anything, we can't schedule this task right now anyway. + return; + } + + if (aTask->IsMainThreadOnly()) { + mMayHaveMainThreadTask = true; + + EnsureMainThreadTasksScheduled(); + + if (mCurrentTasksMT.empty()) { + return; + } + + // We could go through the steps above here and interrupt an off main + // thread task in case it has a lower priority. + if (!finalDependency->IsMainThreadOnly()) { + return; + } + + if (mCurrentTasksMT.top()->GetPriority() < aTask->GetPriority()) { + mCurrentTasksMT.top()->RequestInterrupt(aTask->GetPriority()); + } + } else { + Task* lowestPriorityTask = nullptr; + for (PoolThread& thread : mPoolThreads) { + if (!thread.mCurrentTask) { + mThreadPoolCV.Notify(); + // There's a free thread, no need to interrupt anything. + return; + } + + if (!lowestPriorityTask) { + lowestPriorityTask = thread.mCurrentTask.get(); + continue; + } + + // This should possibly select the lowest priority task which was started + // the latest. But for now we ignore that optimization. + // This also doesn't guarantee a task is interruptable, so that's an + // avenue for improvements as well. + if (lowestPriorityTask->GetPriority() > thread.mEffectiveTaskPriority) { + lowestPriorityTask = thread.mCurrentTask.get(); + } + } + + if (lowestPriorityTask->GetPriority() < aTask->GetPriority()) { + lowestPriorityTask->RequestInterrupt(aTask->GetPriority()); + } + + // We choose not to interrupt main thread tasks for tasks which may be + // executed off the main thread. + } +} + +Task* TaskController::GetHighestPriorityMTTask() { + mGraphMutex.AssertCurrentThreadOwns(); + + if (!mMainThreadTasks.empty()) { + return mMainThreadTasks.begin()->get(); + } + return nullptr; +} + +void TaskController::EnsureMainThreadTasksScheduled() { + if (mObserver) { + mObserver->OnDispatchedEvent(); + } + if (mExternalCondVar) { + mExternalCondVar->Notify(); + } + mMainThreadCV.Notify(); +} + +void TaskController::ProcessUpdatedPriorityModifier(TaskManager* aManager) { + mGraphMutex.AssertCurrentThreadOwns(); + + MOZ_ASSERT(NS_IsMainThread()); + + int32_t modifier = aManager->mCurrentPriorityModifier; + + std::vector<RefPtr<Task>> storedTasks; + // Find all relevant tasks. + for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();) { + if ((*iter)->mTaskManager == aManager) { + storedTasks.push_back(*iter); + iter = mMainThreadTasks.erase(iter); + } else { + iter++; + } + } + + // Reinsert found tasks with their new priorities. + for (RefPtr<Task>& ref : storedTasks) { + // Kept alive at first by the vector and then by mMainThreadTasks. + Task* task = ref; + task->mPriorityModifier = modifier; + auto insertion = mMainThreadTasks.insert(std::move(ref)); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + } +} + +} // namespace mozilla diff --git a/xpcom/threads/TaskController.h b/xpcom/threads/TaskController.h new file mode 100644 index 0000000000..2efb45abcd --- /dev/null +++ b/xpcom/threads/TaskController.h @@ -0,0 +1,415 @@ +/* -*- 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/. */ + +#ifndef mozilla_TaskController_h +#define mozilla_TaskController_h + +#include "mozilla/CondVar.h" +#include "mozilla/IdlePeriodState.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/EventQueue.h" +#include "nsISupportsImpl.h" +#include "nsIEventTarget.h" + +#include <atomic> +#include <memory> +#include <vector> +#include <set> +#include <list> +#include <stack> + +class nsIRunnable; +class nsIThreadObserver; + +namespace mozilla { + +class Task; +class TaskController; +class PerformanceCounter; +class PerformanceCounterState; + +const EventQueuePriority kDefaultPriorityValue = EventQueuePriority::Normal; + +// This file contains the core classes to access the Gecko scheduler. The +// scheduler forms a graph of prioritize tasks, and is responsible for ensuring +// the execution of tasks or their dependencies in order of inherited priority. +// +// The core class is the 'Task' class. The task class describes a single unit of +// work. Users scheduling work implement this class and are required to +// reimplement the 'Run' function in order to do work. +// +// The TaskManager class is reimplemented by users that require +// the ability to reprioritize or suspend tasks. +// +// The TaskController is responsible for scheduling the work itself. The AddTask +// function is used to schedule work. The ReprioritizeTask function may be used +// to change the priority of a task already in the task graph, without +// unscheduling it. + +// The TaskManager is the baseclass used to atomically manage a large set of +// tasks. API users reimplementing TaskManager may reimplement a number of +// functions that they may use to indicate to the scheduler changes in the state +// for any tasks they manage. They may be used to reprioritize or suspend tasks +// under their control, and will also be notified before and after tasks under +// their control are executed. Their methods will only be called once per event +// loop turn, however they may still incur some performance overhead. In +// addition to this frequent reprioritizations may incur a significant +// performance overhead and are discouraged. A TaskManager may currently only be +// used to manage tasks that are bound to the Gecko Main Thread. +class TaskManager { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TaskManager) + + TaskManager() : mTaskCount(0) {} + + // Subclasses implementing task manager will have this function called to + // determine whether their associated tasks are currently suspended. This + // will only be called once per iteration of the task queue, this means that + // suspension of tasks managed by a single TaskManager may be assumed to + // occur atomically. + virtual bool IsSuspended(const MutexAutoLock& aProofOfLock) { return false; } + + // Subclasses may implement this in order to supply a priority adjustment + // to their managed tasks. This is called once per iteration of the task + // queue, and may be assumed to occur atomically for all managed tasks. + virtual int32_t GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) { + return 0; + } + + void DidQueueTask() { ++mTaskCount; } + // This is called when a managed task is about to be executed by the + // scheduler. Anyone reimplementing this should ensure to call the parent or + // decrement mTaskCount. + virtual void WillRunTask() { --mTaskCount; } + // This is called when a managed task has finished being executed by the + // scheduler. + virtual void DidRunTask() {} + uint32_t PendingTaskCount() { return mTaskCount; } + + protected: + virtual ~TaskManager() {} + + private: + friend class TaskController; + + enum class IterationType { NOT_EVENT_LOOP_TURN, EVENT_LOOP_TURN }; + bool UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + const MutexAutoLock& aProofOfLock, IterationType aIterationType); + + bool mCurrentSuspended = false; + int32_t mCurrentPriorityModifier = 0; + + std::atomic<uint32_t> mTaskCount; +}; + +// A Task is the the base class for any unit of work that may be scheduled. +// Subclasses may specify their priority and whether they should be bound to +// the Gecko Main thread. When not bound to the main thread tasks may be +// executed on any available thread (including the main thread), but they may +// also be executed in parallel to any other task they do not have a dependency +// relationship with. Tasks will be run in order of object creation. +class Task { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Task) + + bool IsMainThreadOnly() { return mMainThreadOnly; } + + // This returns the current task priority with its modifier applied. + uint32_t GetPriority() { return mPriority + mPriorityModifier; } + uint64_t GetSeqNo() { return mSeqNo; } + + // Callee needs to assume this may be called on any thread. + // aInterruptPriority passes the priority of the higher priority task that + // is ready to be executed. The task may safely ignore this function, or + // interrupt any work being done. It may return 'false' from its run function + // in order to be run automatically in the future, or true if it will + // reschedule incomplete work manually. + virtual void RequestInterrupt(uint32_t aInterruptPriority) {} + + // At the moment this -must- be called before the task is added to the + // controller. Calling this after tasks have been added to the controller + // results in undefined behavior! + // At submission, tasks must depend only on tasks managed by the same, or + // no idle manager. + void AddDependency(Task* aTask) { + MOZ_ASSERT(aTask); + MOZ_ASSERT(!mIsInGraph); + mDependencies.insert(aTask); + } + + // This sets the TaskManager for the current task. Calling this after the + // task has been added to the TaskController results in undefined behavior. + void SetManager(TaskManager* aManager) { + MOZ_ASSERT(mMainThreadOnly); + MOZ_ASSERT(!mIsInGraph); + mTaskManager = aManager; + } + TaskManager* GetManager() { return mTaskManager; } + + struct PriorityCompare { + bool operator()(const RefPtr<Task>& aTaskA, + const RefPtr<Task>& aTaskB) const { + uint32_t prioA = aTaskA->GetPriority(); + uint32_t prioB = aTaskB->GetPriority(); + return (prioA > prioB) || + (prioA == prioB && (aTaskA->GetSeqNo() < aTaskB->GetSeqNo())); + } + }; + + // Tell the task about its idle deadline. Will only be called for + // tasks managed by an IdleTaskManager, right before the task runs. + virtual void SetIdleDeadline(TimeStamp aDeadline) {} + + virtual PerformanceCounter* GetPerformanceCounter() const { return nullptr; } + + // Get a name for this task. This returns false if the task has no name. + virtual bool GetName(nsACString& aName) { return false; } + + protected: + Task(bool aMainThreadOnly, + uint32_t aPriority = static_cast<uint32_t>(kDefaultPriorityValue)) + : mMainThreadOnly(aMainThreadOnly), + mSeqNo(sCurrentTaskSeqNo++), + mPriority(aPriority) {} + + Task(bool aMainThreadOnly, + EventQueuePriority aPriority = kDefaultPriorityValue) + : mMainThreadOnly(aMainThreadOnly), + mSeqNo(sCurrentTaskSeqNo++), + mPriority(static_cast<uint32_t>(aPriority)) {} + + virtual ~Task() {} + + friend class TaskController; + + // When this returns false, the task is considered incomplete and will be + // rescheduled at the current 'mPriority' level. + virtual bool Run() = 0; + + private: + Task* GetHighestPriorityDependency(); + + // Iterator pointing to this task's position in + // mThreadableTasks/mMainThreadTasks if, and only if this task is currently + // scheduled to be executed. This allows fast access to the task's position + // in the set, allowing for fast removal. + // This is safe, and remains valid unless the task is removed from the set. + // See also iterator invalidation in: + // https://en.cppreference.com/w/cpp/container + // + // Or the spec: + // "All Associative Containers: The insert and emplace members shall not + // affect the validity of iterators and references to the container + // [26.2.6/9]" "All Associative Containers: The erase members shall invalidate + // only iterators and references to the erased elements [26.2.6/9]" + std::set<RefPtr<Task>, PriorityCompare>::iterator mIterator; + std::set<RefPtr<Task>, PriorityCompare> mDependencies; + + RefPtr<TaskManager> mTaskManager; + + // Access to these variables is protected by the GraphMutex. + bool mMainThreadOnly; + bool mCompleted = false; + bool mInProgress = false; +#ifdef DEBUG + bool mIsInGraph = false; +#endif + + static std::atomic<uint64_t> sCurrentTaskSeqNo; + int64_t mSeqNo; + uint32_t mPriority; + // Modifier currently being applied to this task by its taskmanager. + int32_t mPriorityModifier = 0; +#ifdef MOZ_GECKO_PROFILER + // Time this task was inserted into the task graph, this is used by the + // profiler. + mozilla::TimeStamp mInsertionTime; +#endif +}; + +struct PoolThread { + PRThread* mThread; + RefPtr<Task> mCurrentTask; + // This may be higher than mCurrentTask's priority due to priority + // propagation. This is -only- valid when mCurrentTask != nullptr. + uint32_t mEffectiveTaskPriority; +}; + +// A task manager implementation for priority levels that should only +// run during idle periods. +class IdleTaskManager : public TaskManager { + public: + explicit IdleTaskManager(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod) + : mIdlePeriodState(std::move(aIdlePeriod)) {} + + IdlePeriodState& State() { return mIdlePeriodState; } + + bool IsSuspended(const MutexAutoLock& aProofOfLock) override { + TimeStamp idleDeadline = State().GetCachedIdleDeadline(); + return !idleDeadline; + } + + private: + // Tracking of our idle state of various sorts. + IdlePeriodState mIdlePeriodState; +}; + +// The TaskController is the core class of the scheduler. It is used to +// schedule tasks to be executed, as well as to reprioritize tasks that have +// already been scheduled. The core functions to do this are AddTask and +// ReprioritizeTask. +class TaskController { + public: + TaskController() + : mGraphMutex("TaskController::mGraphMutex"), + mThreadPoolCV(mGraphMutex, "TaskController::mThreadPoolCV"), + mMainThreadCV(mGraphMutex, "TaskController::mMainThreadCV") {} + + static TaskController* Get(); + + static bool Initialize(); + + void SetThreadObserver(nsIThreadObserver* aObserver) { + mObserver = aObserver; + } + void SetConditionVariable(CondVar* aExternalCondVar) { + mExternalCondVar = aExternalCondVar; + } + + void SetIdleTaskManager(IdleTaskManager* aIdleTaskManager) { + mIdleTaskManager = aIdleTaskManager; + } + IdleTaskManager* GetIdleTaskManager() { return mIdleTaskManager.get(); } + + // Initialization and shutdown code. + void SetPerformanceCounterState( + PerformanceCounterState* aPerformanceCounterState); + + static void Shutdown(); + + // This adds a task to the TaskController graph. + // This may be called on any thread. + void AddTask(already_AddRefed<Task>&& aTask); + + // This wait function is the theoretical function you would need if our main + // thread needs to also process OS messages or something along those lines. + void WaitForTaskOrMessage(); + + // This gets the next (highest priority) task that is only allowed to execute + // on the main thread. + void ExecuteNextTaskOnlyMainThread(); + + // Process all pending main thread tasks. + void ProcessPendingMTTask(bool aMayWait = false); + + // This allows reprioritization of a task already in the task graph. + // This may be called on any thread. + void ReprioritizeTask(Task* aTask, uint32_t aPriority); + + void DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aPriority, TaskManager* aManager = nullptr); + + nsIRunnable* GetRunnableForMTTask(bool aReallyWait); + + bool HasMainThreadPendingTasks(); + + // Let users know whether the last main thread task runnable did work. + bool MTTaskRunnableProcessedTask() { return mMTTaskRunnableProcessedTask; } + + private: + friend void ThreadFuncPoolThread(void* aIndex); + + bool InitializeInternal(); + + void InitializeThreadPool(); + + // This gets the next (highest priority) task that is only allowed to execute + // on the main thread, if any, and executes it. + // Returns true if it succeeded. + bool ExecuteNextTaskOnlyMainThreadInternal(const MutexAutoLock& aProofOfLock); + + // The guts of ExecuteNextTaskOnlyMainThreadInternal, which get idle handling + // wrapped around them. Returns whether a task actually ran. + bool DoExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock); + + Task* GetFinalDependency(Task* aTask); + void MaybeInterruptTask(Task* aTask); + Task* GetHighestPriorityMTTask(); + + void EnsureMainThreadTasksScheduled(); + + void ProcessUpdatedPriorityModifier(TaskManager* aManager); + + void ShutdownThreadPoolInternal(); + void ShutdownInternal(); + + void RunPoolThread(); + + static std::unique_ptr<TaskController> sSingleton; + static StaticMutex sSingletonMutex; + + // This protects access to the task graph. + Mutex mGraphMutex; + + // This protects thread pool initialization. We cannot do this from within + // the GraphMutex, since thread creation on Windows can generate events on + // the main thread that need to be handled. + Mutex mPoolInitializationMutex = + Mutex("TaskController::mPoolInitializationMutex"); + + CondVar mThreadPoolCV; + CondVar mMainThreadCV; + + // Variables below are protected by mGraphMutex. + + std::vector<PoolThread> mPoolThreads; + std::stack<RefPtr<Task>> mCurrentTasksMT; + + // A list of all tasks ordered by priority. + std::set<RefPtr<Task>, Task::PriorityCompare> mThreadableTasks; + std::set<RefPtr<Task>, Task::PriorityCompare> mMainThreadTasks; + + // TaskManagers currently active. + // We can use a raw pointer since tasks always hold on to their TaskManager. + std::set<TaskManager*> mTaskManagers; + + // This ensures we keep running the main thread if we processed a task there. + bool mMayHaveMainThreadTask = true; + bool mShuttingDown = false; + + // This stores whether the last main thread task runnable did work. + bool mMTTaskRunnableProcessedTask = false; + + // Whether our thread pool is initialized. We use this currently to avoid + // starting the threads in processes where it's never used. This is protected + // by mPoolInitializationMutex. + bool mThreadPoolInitialized = false; + + // Whether we have scheduled a runnable on the main thread event loop. + // This is used for nsIRunnable compatibility. + RefPtr<nsIRunnable> mMTProcessingRunnable; + RefPtr<nsIRunnable> mMTBlockingProcessingRunnable; + + // XXX - Thread observer to notify when a new event has been dispatched + nsIThreadObserver* mObserver = nullptr; + // XXX - External condvar to notify when we have received an event + CondVar* mExternalCondVar = nullptr; + // Idle task manager so we can properly do idle state stuff. + RefPtr<IdleTaskManager> mIdleTaskManager; + + // Our tracking of our performance counter and long task state, + // shared with nsThread. + PerformanceCounterState* mPerformanceCounterState = nullptr; +}; + +} // namespace mozilla + +#endif // mozilla_TaskController_h diff --git a/xpcom/threads/TaskDispatcher.h b/xpcom/threads/TaskDispatcher.h new file mode 100644 index 0000000000..558221aa95 --- /dev/null +++ b/xpcom/threads/TaskDispatcher.h @@ -0,0 +1,301 @@ +/* -*- 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/. */ + +#if !defined(TaskDispatcher_h_) +# define TaskDispatcher_h_ + +# include <queue> + +# include "mozilla/AbstractThread.h" +# include "mozilla/Maybe.h" +# include "mozilla/UniquePtr.h" +# include "nsIDirectTaskDispatcher.h" +# include "nsISupportsImpl.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +namespace mozilla { + +class SimpleTaskQueue { + public: + SimpleTaskQueue() = default; + virtual ~SimpleTaskQueue() = default; + + void AddTask(already_AddRefed<nsIRunnable> aRunnable) { + if (!mTasks) { + mTasks.emplace(); + } + mTasks->push(std::move(aRunnable)); + } + + void DrainTasks() { + if (!mTasks) { + return; + } + auto& queue = mTasks.ref(); + while (!queue.empty()) { + nsCOMPtr<nsIRunnable> r = std::move(queue.front()); + queue.pop(); + r->Run(); + } + } + + bool HaveTasks() const { return mTasks && !mTasks->empty(); } + + private: + // We use a Maybe<> because (a) when used for DirectTasks it often doesn't get + // anything put into it, and (b) the std::queue implementation in GNU + // libstdc++ does two largish heap allocations when creating a new std::queue. + Maybe<std::queue<nsCOMPtr<nsIRunnable>>> mTasks; +}; + +/* + * A classic approach to cross-thread communication is to dispatch asynchronous + * runnables to perform updates on other threads. This generally works well, but + * there are sometimes reasons why we might want to delay the actual dispatch of + * these tasks until a specified moment. At present, this is primarily useful to + * ensure that mirrored state gets updated atomically - but there may be other + * applications as well. + * + * TaskDispatcher is a general abstract class that accepts tasks and dispatches + * them at some later point. These groups of tasks are per-target-thread, and + * contain separate queues for several kinds of tasks (see comments below). - + * "state change tasks" (which run first, and are intended to be used to update + * the value held by mirrors), and regular tasks, which are other arbitrary + * operations that the are gated to run after all the state changes have + * completed. + */ +class TaskDispatcher { + public: + TaskDispatcher() = default; + virtual ~TaskDispatcher() = default; + + // Direct tasks are run directly (rather than dispatched asynchronously) when + // the tail dispatcher fires. A direct task may cause other tasks to be added + // to the tail dispatcher. + virtual void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) = 0; + + // State change tasks are dispatched asynchronously always run before regular + // tasks. They are intended to be used to update the value held by mirrors + // before any other dispatched tasks are run on the target thread. + virtual void AddStateChangeTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) = 0; + + // Regular tasks are dispatched asynchronously, and run after state change + // tasks. + virtual nsresult AddTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) = 0; + + virtual nsresult DispatchTasksFor(AbstractThread* aThread) = 0; + virtual bool HasTasksFor(AbstractThread* aThread) = 0; + virtual void DrainDirectTasks() = 0; +}; + +/* + * AutoTaskDispatcher is a stack-scoped TaskDispatcher implementation that fires + * its queued tasks when it is popped off the stack. + */ +class AutoTaskDispatcher : public TaskDispatcher { + public: + explicit AutoTaskDispatcher(nsIDirectTaskDispatcher* aDirectTaskDispatcher, + bool aIsTailDispatcher = false) + : mDirectTaskDispatcher(aDirectTaskDispatcher), + mIsTailDispatcher(aIsTailDispatcher) {} + + ~AutoTaskDispatcher() { + // Given that direct tasks may trigger other code that uses the tail + // dispatcher, it's better to avoid processing them in the tail dispatcher's + // destructor. So we require TailDispatchers to manually invoke + // DrainDirectTasks before the AutoTaskDispatcher gets destroyed. In truth, + // this is only necessary in the case where this AutoTaskDispatcher can be + // accessed by the direct tasks it dispatches (true for TailDispatchers, but + // potentially not true for other hypothetical AutoTaskDispatchers). Feel + // free to loosen this restriction to apply only to mIsTailDispatcher if a + // use-case requires it. + MOZ_ASSERT(!HaveDirectTasks()); + + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + DispatchTaskGroup(std::move(mTaskGroups[i])); + } + } + + bool HaveDirectTasks() { + return mDirectTaskDispatcher && mDirectTaskDispatcher->HaveDirectTasks(); + } + + void DrainDirectTasks() override { + if (mDirectTaskDispatcher) { + mDirectTaskDispatcher->DrainDirectTasks(); + } + } + + void AddDirectTask(already_AddRefed<nsIRunnable> aRunnable) override { + MOZ_ASSERT(mDirectTaskDispatcher); + mDirectTaskDispatcher->DispatchDirectTask(std::move(aRunnable)); + } + + void AddStateChangeTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) override { + nsCOMPtr<nsIRunnable> r = aRunnable; + MOZ_RELEASE_ASSERT(r); + EnsureTaskGroup(aThread).mStateChangeTasks.AppendElement(r.forget()); + } + + nsresult AddTask(AbstractThread* aThread, + already_AddRefed<nsIRunnable> aRunnable) override { + nsCOMPtr<nsIRunnable> r = aRunnable; + MOZ_RELEASE_ASSERT(r); + // To preserve the event order, we need to append a new group if the last + // group is not targeted for |aThread|. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318226&mark=0-3#c0 + // for the details of the issue. + if (mTaskGroups.Length() == 0 || + mTaskGroups.LastElement()->mThread != aThread) { + mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread)); + } + + PerThreadTaskGroup& group = *mTaskGroups.LastElement(); + group.mRegularTasks.AppendElement(r.forget()); + + return NS_OK; + } + + bool HasTasksFor(AbstractThread* aThread) override { + return !!GetTaskGroup(aThread) || + (aThread == AbstractThread::GetCurrent() && HaveDirectTasks()); + } + + nsresult DispatchTasksFor(AbstractThread* aThread) override { + nsresult rv = NS_OK; + + // Dispatch all groups that match |aThread|. + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + if (mTaskGroups[i]->mThread == aThread) { + nsresult rv2 = DispatchTaskGroup(std::move(mTaskGroups[i])); + + if (NS_WARN_IF(NS_FAILED(rv2)) && NS_SUCCEEDED(rv)) { + // We should try our best to call DispatchTaskGroup() as much as + // possible and return an error if any of DispatchTaskGroup() calls + // failed. + rv = rv2; + } + + mTaskGroups.RemoveElementAt(i--); + } + } + + return rv; + } + + private: + struct PerThreadTaskGroup { + public: + explicit PerThreadTaskGroup(AbstractThread* aThread) : mThread(aThread) { + MOZ_COUNT_CTOR(PerThreadTaskGroup); + } + + MOZ_COUNTED_DTOR(PerThreadTaskGroup) + + RefPtr<AbstractThread> mThread; + nsTArray<nsCOMPtr<nsIRunnable>> mStateChangeTasks; + nsTArray<nsCOMPtr<nsIRunnable>> mRegularTasks; + }; + + class TaskGroupRunnable : public Runnable { + public: + explicit TaskGroupRunnable(UniquePtr<PerThreadTaskGroup>&& aTasks) + : Runnable("AutoTaskDispatcher::TaskGroupRunnable"), + mTasks(std::move(aTasks)) {} + + NS_IMETHOD Run() override { + // State change tasks get run all together before any code is run, so + // that all state changes are made in an atomic unit. + for (size_t i = 0; i < mTasks->mStateChangeTasks.Length(); ++i) { + mTasks->mStateChangeTasks[i]->Run(); + } + + // Once the state changes have completed, drain any direct tasks + // generated by those state changes (i.e. watcher notification tasks). + // This needs to be outside the loop because we don't want to run code + // that might observe intermediate states. + MaybeDrainDirectTasks(); + + for (size_t i = 0; i < mTasks->mRegularTasks.Length(); ++i) { + mTasks->mRegularTasks[i]->Run(); + + // Scope direct tasks tightly to the task that generated them. + MaybeDrainDirectTasks(); + } + + return NS_OK; + } + + private: + void MaybeDrainDirectTasks() { + AbstractThread* currentThread = AbstractThread::GetCurrent(); + if (currentThread && currentThread->MightHaveTailTasks()) { + currentThread->TailDispatcher().DrainDirectTasks(); + } + } + + UniquePtr<PerThreadTaskGroup> mTasks; + }; + + PerThreadTaskGroup& EnsureTaskGroup(AbstractThread* aThread) { + PerThreadTaskGroup* existing = GetTaskGroup(aThread); + if (existing) { + return *existing; + } + + mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread)); + return *mTaskGroups.LastElement(); + } + + PerThreadTaskGroup* GetTaskGroup(AbstractThread* aThread) { + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + if (mTaskGroups[i]->mThread == aThread) { + return mTaskGroups[i].get(); + } + } + + // Not found. + return nullptr; + } + + nsresult DispatchTaskGroup(UniquePtr<PerThreadTaskGroup> aGroup) { + RefPtr<AbstractThread> thread = aGroup->mThread; + + AbstractThread::DispatchReason reason = + mIsTailDispatcher ? AbstractThread::TailDispatch + : AbstractThread::NormalDispatch; + nsCOMPtr<nsIRunnable> r = new TaskGroupRunnable(std::move(aGroup)); + return thread->Dispatch(r.forget(), reason); + } + + // Task groups, organized by thread. + nsTArray<UniquePtr<PerThreadTaskGroup>> mTaskGroups; + + nsCOMPtr<nsIDirectTaskDispatcher> mDirectTaskDispatcher; + // True if this TaskDispatcher represents the tail dispatcher for the thread + // upon which it runs. + const bool mIsTailDispatcher; +}; + +// Little utility class to allow declaring AutoTaskDispatcher as a default +// parameter for methods that take a TaskDispatcher&. +template <typename T> +class PassByRef { + public: + PassByRef() = default; + operator T&() { return mVal; } + + private: + T mVal; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/TaskQueue.cpp b/xpcom/threads/TaskQueue.cpp new file mode 100644 index 0000000000..3236137bec --- /dev/null +++ b/xpcom/threads/TaskQueue.cpp @@ -0,0 +1,233 @@ +/* -*- 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/TaskQueue.h" + +#include "nsThreadUtils.h" + +namespace mozilla { + +TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget, + const char* aName, bool aRequireTailDispatch) + : AbstractThread(aRequireTailDispatch), + mTarget(aTarget), + mQueueMonitor("TaskQueue::Queue"), + mTailDispatcher(nullptr), + mIsRunning(false), + mIsShutdown(false), + mName(aName) {} + +TaskQueue::TaskQueue(already_AddRefed<nsIEventTarget> aTarget, + bool aSupportsTailDispatch) + : TaskQueue(std::move(aTarget), "Unnamed", aSupportsTailDispatch) {} + +TaskQueue::~TaskQueue() { + // No one is referencing this TaskQueue anymore, meaning no tasks can be + // pending as all Runner hold a reference to this TaskQueue. +} + +NS_IMPL_ISUPPORTS_INHERITED(TaskQueue, AbstractThread, nsIDirectTaskDispatcher); + +TaskDispatcher& TaskQueue::TailDispatcher() { + MOZ_ASSERT(IsCurrentThreadIn()); + MOZ_ASSERT(mTailDispatcher); + return *mTailDispatcher; +} + +// Note aRunnable is passed by ref to support conditional ownership transfer. +// See Dispatch() in TaskQueue.h for more details. +nsresult TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable, + uint32_t aFlags, DispatchReason aReason) { + mQueueMonitor.AssertCurrentThreadOwns(); + if (mIsShutdown) { + return NS_ERROR_FAILURE; + } + + AbstractThread* currentThread; + if (aReason != TailDispatch && (currentThread = GetCurrent()) && + RequiresTailDispatch(currentThread) && + currentThread->IsTailDispatcherAvailable()) { + MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL, + "Tail dispatch doesn't support flags"); + return currentThread->TailDispatcher().AddTask(this, aRunnable.forget()); + } + + LogRunnable::LogDispatch(aRunnable); + mTasks.push({std::move(aRunnable), aFlags}); + + if (mIsRunning) { + return NS_OK; + } + RefPtr<nsIRunnable> runner(new Runner(this)); + nsresult rv = mTarget->Dispatch(runner.forget(), aFlags); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch runnable to run TaskQueue"); + return rv; + } + mIsRunning = true; + + return NS_OK; +} + +void TaskQueue::AwaitIdle() { + MonitorAutoLock mon(mQueueMonitor); + AwaitIdleLocked(); +} + +void TaskQueue::AwaitIdleLocked() { + // Make sure there are no tasks for this queue waiting in the caller's tail + // dispatcher. + MOZ_ASSERT_IF(AbstractThread::GetCurrent(), + !AbstractThread::GetCurrent()->HasTailTasksFor(this)); + + mQueueMonitor.AssertCurrentThreadOwns(); + MOZ_ASSERT(mIsRunning || mTasks.empty()); + while (mIsRunning) { + mQueueMonitor.Wait(); + } +} + +void TaskQueue::AwaitShutdownAndIdle() { + MOZ_ASSERT(!IsCurrentThreadIn()); + // Make sure there are no tasks for this queue waiting in the caller's tail + // dispatcher. + MOZ_ASSERT_IF(AbstractThread::GetCurrent(), + !AbstractThread::GetCurrent()->HasTailTasksFor(this)); + + MonitorAutoLock mon(mQueueMonitor); + while (!mIsShutdown) { + mQueueMonitor.Wait(); + } + AwaitIdleLocked(); +} + +RefPtr<ShutdownPromise> TaskQueue::BeginShutdown() { + // Dispatch any tasks for this queue waiting in the caller's tail dispatcher, + // since this is the last opportunity to do so. + if (AbstractThread* currentThread = AbstractThread::GetCurrent()) { + currentThread->TailDispatchTasksFor(this); + } + MonitorAutoLock mon(mQueueMonitor); + mIsShutdown = true; + RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__); + MaybeResolveShutdown(); + mon.NotifyAll(); + return p; +} + +bool TaskQueue::IsEmpty() { + MonitorAutoLock mon(mQueueMonitor); + return mTasks.empty(); +} + +bool TaskQueue::IsCurrentThreadIn() const { + bool in = mRunningThread == PR_GetCurrentThread(); + return in; +} + +nsresult TaskQueue::Runner::Run() { + TaskStruct event; + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + MOZ_ASSERT(mQueue->mIsRunning); + if (mQueue->mTasks.empty()) { + mQueue->mIsRunning = false; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + return NS_OK; + } + event = std::move(mQueue->mTasks.front()); + mQueue->mTasks.pop(); + } + MOZ_ASSERT(event.event); + + // Note that dropping the queue monitor before running the task, and + // taking the monitor again after the task has run ensures we have memory + // fences enforced. This means that if the object we're calling wasn't + // designed to be threadsafe, it will be, provided we're only calling it + // in this task queue. + { + AutoTaskGuard g(mQueue); + SerialEventTargetGuard tg(mQueue); + { + LogRunnable::Run log(event.event); + + event.event->Run(); + + // Drop the reference to event. The event will hold a reference to the + // object it's calling, and we don't want to keep it alive, it may be + // making assumptions what holds references to it. This is especially + // the case if the object is waiting for us to shutdown, so that it + // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case). + event.event = nullptr; + } + } + + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + if (mQueue->mTasks.empty()) { + // No more events to run. Exit the task runner. + mQueue->mIsRunning = false; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + return NS_OK; + } + } + + // There's at least one more event that we can run. Dispatch this Runner + // to the target again to ensure it runs again. Note that we don't just + // run in a loop here so that we don't hog the target. This means we may + // run on another thread next time, but we rely on the memory fences from + // mQueueMonitor for thread safety of non-threadsafe tasks. + nsresult rv; + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + rv = mQueue->mTarget->Dispatch( + this, mQueue->mTasks.front().flags | NS_DISPATCH_AT_END); + } + if (NS_FAILED(rv)) { + // Failed to dispatch, shutdown! + MonitorAutoLock mon(mQueue->mQueueMonitor); + mQueue->mIsRunning = false; + mQueue->mIsShutdown = true; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +TaskQueue::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.AddTask(std::move(aEvent)); + return NS_OK; +} + +NS_IMETHODIMP TaskQueue::DrainDirectTasks() { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.DrainTasks(); + return NS_OK; +} + +NS_IMETHODIMP TaskQueue::HaveDirectTasks(bool* aValue) { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + + *aValue = mDirectTasks.HaveTasks(); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/threads/TaskQueue.h b/xpcom/threads/TaskQueue.h new file mode 100644 index 0000000000..5b9837ecbd --- /dev/null +++ b/xpcom/threads/TaskQueue.h @@ -0,0 +1,224 @@ +/* -*- 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/. */ + +#ifndef TaskQueue_h_ +#define TaskQueue_h_ + +#include <queue> + +#include "mozilla/AbstractThread.h" +#include "mozilla/Maybe.h" +#include "mozilla/Monitor.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +typedef MozPromise<bool, bool, false> ShutdownPromise; + +// Abstracts executing runnables in order on an arbitrary event target. The +// runnables dispatched to the TaskQueue will be executed in the order in which +// they're received, and are guaranteed to not be executed concurrently. +// They may be executed on different threads, and a memory barrier is used +// to make this threadsafe for objects that aren't already threadsafe. +// +// Note, since a TaskQueue can also be converted to an nsIEventTarget using +// WrapAsEventTarget() its possible to construct a hierarchy of TaskQueues. +// Consider these three TaskQueues: +// +// TQ1 dispatches to the main thread +// TQ2 dispatches to TQ1 +// TQ3 dispatches to TQ1 +// +// This ensures there is only ever a single runnable from the entire chain on +// the main thread. It also ensures that TQ2 and TQ3 only have a single +// runnable in TQ1 at any time. +// +// This arrangement lets you prioritize work by dispatching runnables directly +// to TQ1. You can issue many runnables for important work. Meanwhile the TQ2 +// and TQ3 work will always execute at most one runnable and then yield. +// +// A TaskQueue does not require explicit shutdown, however it provides a +// BeginShutdown() method that places TaskQueue in a shut down state and returns +// a promise that gets resolved once all pending tasks have completed +class TaskQueue : public AbstractThread, public nsIDirectTaskDispatcher { + class EventTargetWrapper; + + public: + explicit TaskQueue(already_AddRefed<nsIEventTarget> aTarget, + bool aSupportsTailDispatch = false); + + TaskQueue(already_AddRefed<nsIEventTarget> aTarget, const char* aName, + bool aSupportsTailDispatch = false); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIDIRECTTASKDISPATCHER + + TaskDispatcher& TailDispatcher() override; + + NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) override { + nsCOMPtr<nsIRunnable> runnable = aEvent; + { + MonitorAutoLock mon(mQueueMonitor); + return DispatchLocked(/* passed by ref */ runnable, aFlags, + NormalDispatch); + } + // If the ownership of |r| is not transferred in DispatchLocked() due to + // dispatch failure, it will be deleted here outside the lock. We do so + // since the destructor of the runnable might access TaskQueue and result + // in deadlocks. + } + + [[nodiscard]] nsresult Dispatch( + already_AddRefed<nsIRunnable> aRunnable, + DispatchReason aReason = NormalDispatch) override { + nsCOMPtr<nsIRunnable> r = aRunnable; + { + MonitorAutoLock mon(mQueueMonitor); + return DispatchLocked(/* passed by ref */ r, NS_DISPATCH_NORMAL, aReason); + } + // If the ownership of |r| is not transferred in DispatchLocked() due to + // dispatch failure, it will be deleted here outside the lock. We do so + // since the destructor of the runnable might access TaskQueue and result + // in deadlocks. + } + + // So we can access nsIEventTarget::Dispatch(nsIRunnable*, uint32_t aFlags) + using nsIEventTarget::Dispatch; + + // Puts the queue in a shutdown state and returns immediately. The queue will + // remain alive at least until all the events are drained, because the Runners + // hold a strong reference to the task queue, and one of them is always held + // by the target event queue when the task queue is non-empty. + // + // The returned promise is resolved when the queue goes empty. + RefPtr<ShutdownPromise> BeginShutdown(); + + // Blocks until all task finish executing. + void AwaitIdle(); + + // Blocks until the queue is flagged for shutdown and all tasks have finished + // executing. + void AwaitShutdownAndIdle(); + + bool IsEmpty(); + + // Returns true if the current thread is currently running a Runnable in + // the task queue. + bool IsCurrentThreadIn() const override; + using nsISerialEventTarget::IsOnCurrentThread; + + protected: + virtual ~TaskQueue(); + + // Blocks until all task finish executing. Called internally by methods + // that need to wait until the task queue is idle. + // mQueueMonitor must be held. + void AwaitIdleLocked(); + + nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable, uint32_t aFlags, + DispatchReason aReason = NormalDispatch); + + void MaybeResolveShutdown() { + mQueueMonitor.AssertCurrentThreadOwns(); + if (mIsShutdown && !mIsRunning) { + mShutdownPromise.ResolveIfExists(true, __func__); + mTarget = nullptr; + } + } + + nsCOMPtr<nsIEventTarget> mTarget; + + // Monitor that protects the queue and mIsRunning; + Monitor mQueueMonitor; + + typedef struct TaskStruct { + nsCOMPtr<nsIRunnable> event; + uint32_t flags; + } TaskStruct; + + // Queue of tasks to run. + std::queue<TaskStruct> mTasks; + + // The thread currently running the task queue. We store a reference + // to this so that IsCurrentThreadIn() can tell if the current thread + // is the thread currently running in the task queue. + // + // This may be read on any thread, but may only be written on mRunningThread. + // The thread can't die while we're running in it, and we only use it for + // pointer-comparison with the current thread anyway - so we make it atomic + // and don't refcount it. + Atomic<PRThread*> mRunningThread; + + // RAII class that gets instantiated for each dispatched task. + class AutoTaskGuard { + public: + explicit AutoTaskGuard(TaskQueue* aQueue) + : mQueue(aQueue), mLastCurrentThread(nullptr) { + // NB: We don't hold the lock to aQueue here. Don't do anything that + // might require it. + MOZ_ASSERT(!mQueue->mTailDispatcher); + mTaskDispatcher.emplace(aQueue, + /* aIsTailDispatcher = */ true); + mQueue->mTailDispatcher = mTaskDispatcher.ptr(); + + mLastCurrentThread = sCurrentThreadTLS.get(); + sCurrentThreadTLS.set(aQueue); + + MOZ_ASSERT(mQueue->mRunningThread == nullptr); + mQueue->mRunningThread = PR_GetCurrentThread(); + } + + ~AutoTaskGuard() { + mTaskDispatcher->DrainDirectTasks(); + mTaskDispatcher.reset(); + + MOZ_ASSERT(mQueue->mRunningThread == PR_GetCurrentThread()); + mQueue->mRunningThread = nullptr; + + sCurrentThreadTLS.set(mLastCurrentThread); + mQueue->mTailDispatcher = nullptr; + } + + private: + Maybe<AutoTaskDispatcher> mTaskDispatcher; + TaskQueue* mQueue; + AbstractThread* mLastCurrentThread; + }; + + TaskDispatcher* mTailDispatcher; + + // True if we've dispatched an event to the target to execute events from + // the queue. + bool mIsRunning; + + // True if we've started our shutdown process. + bool mIsShutdown; + MozPromiseHolder<ShutdownPromise> mShutdownPromise; + + // The name of this TaskQueue. Useful when debugging dispatch failures. + const char* const mName; + + SimpleTaskQueue mDirectTasks; + + class Runner : public Runnable { + public: + explicit Runner(TaskQueue* aQueue) + : Runnable("TaskQueue::Runner"), mQueue(aQueue) {} + NS_IMETHOD Run() override; + + private: + RefPtr<TaskQueue> mQueue; + }; +}; + +} // namespace mozilla + +#endif // TaskQueue_h_ diff --git a/xpcom/threads/ThreadBound.h b/xpcom/threads/ThreadBound.h new file mode 100644 index 0000000000..4d5e0088b5 --- /dev/null +++ b/xpcom/threads/ThreadBound.h @@ -0,0 +1,143 @@ +/* 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/. */ + +// A class for values only accessible from a single designated thread. + +#ifndef mozilla_ThreadBound_h +#define mozilla_ThreadBound_h + +#include "mozilla/Atomics.h" +#include "prthread.h" + +#include <type_traits> + +namespace mozilla { + +template <typename T> +class ThreadBound; + +namespace detail { + +template <bool Condition, typename T> +struct AddConstIf { + using type = T; +}; + +template <typename T> +struct AddConstIf<true, T> { + using type = typename std::add_const<T>::type; +}; + +} // namespace detail + +// A ThreadBound<T> is a T that can only be accessed by a specific +// thread. To enforce this rule, the inner T is only accessible +// through a non-copyable, immovable accessor object. +// Given a ThreadBound<T> threadBoundData, it can be accessed like so: +// +// auto innerData = threadBoundData.Access(); +// innerData->DoStuff(); +// +// Trying to access a ThreadBound<T> from a different thread will +// trigger a MOZ_DIAGNOSTIC_ASSERT. +// The encapsulated T is constructed during the construction of the +// enclosing ThreadBound<T> by forwarding all of the latter's +// constructor parameters to the former. A newly constructed +// ThreadBound<T> is bound to the thread it's constructed in. It's +// possible to rebind the data to some otherThread by calling +// +// threadBoundData.Transfer(otherThread); +// +// on the thread that threadBoundData is currently bound to, as long +// as it's not currently being accessed. (Trying to rebind from +// another thread or while an accessor exists will trigger an +// assertion.) +// +// Note: A ThreadBound<T> may be destructed from any thread, not just +// its designated thread at the time the destructor is invoked. +template <typename T> +class ThreadBound final { + public: + template <typename... Args> + explicit ThreadBound(Args&&... aArgs) + : mData(std::forward<Args>(aArgs)...), + mThread(PR_GetCurrentThread()), + mAccessCount(0) {} + + ~ThreadBound() { AssertIsNotCurrentlyAccessed(); } + + void Transfer(const PRThread* const aDest) { + AssertIsCorrectThread(); + AssertIsNotCurrentlyAccessed(); + mThread = aDest; + } + + private: + T mData; + + // This member is (potentially) accessed by multiple threads and is + // thus the first point of synchronization between them. + Atomic<const PRThread*, ReleaseAcquire> mThread; + + // In order to support nested accesses (e.g. from different stack + // frames) it's necessary to maintain a counter of the existing + // accessor. Since it's possible to access a const ThreadBound, the + // counter is mutable. It's atomic because accessing it synchronizes + // access to mData (see comment in Accessor's constructor). + using AccessCountType = Atomic<int, ReleaseAcquire>; + mutable AccessCountType mAccessCount; + + public: + template <bool IsConst> + class MOZ_STACK_CLASS Accessor final { + using DataType = typename detail::AddConstIf<IsConst, T>::type; + + public: + explicit Accessor( + typename detail::AddConstIf<IsConst, ThreadBound>::type& aThreadBound) + : mData(aThreadBound.mData), mAccessCount(aThreadBound.mAccessCount) { + aThreadBound.AssertIsCorrectThread(); + + // This load/store serves as a memory fence that guards mData + // against accesses that would trip the thread assertion. + // (Otherwise one of the loads in the caller's instruction + // stream might be scheduled before the assertion.) + ++mAccessCount; + } + + Accessor(const Accessor&) = delete; + Accessor(Accessor&&) = delete; + Accessor& operator=(const Accessor&) = delete; + Accessor& operator=(Accessor&&) = delete; + + ~Accessor() { --mAccessCount; } + + DataType* operator->() { return &mData; } + + private: + DataType& mData; + AccessCountType& mAccessCount; + }; + + auto Access() { return Accessor<false>{*this}; } + + auto Access() const { return Accessor<true>{*this}; } + + private: + bool IsCorrectThread() const { return mThread == PR_GetCurrentThread(); } + + bool IsNotCurrentlyAccessed() const { return mAccessCount == 0; } + +#define MOZ_DEFINE_THREAD_BOUND_ASSERT(predicate) \ + void Assert##predicate() const { MOZ_DIAGNOSTIC_ASSERT(predicate()); } + + MOZ_DEFINE_THREAD_BOUND_ASSERT(IsCorrectThread) + MOZ_DEFINE_THREAD_BOUND_ASSERT(IsNotCurrentlyAccessed) + +#undef MOZ_DEFINE_THREAD_BOUND_ASSERT +}; + +} // namespace mozilla + +#endif // mozilla_ThreadBound_h diff --git a/xpcom/threads/ThreadDelay.cpp b/xpcom/threads/ThreadDelay.cpp new file mode 100644 index 0000000000..1c38e25510 --- /dev/null +++ b/xpcom/threads/ThreadDelay.cpp @@ -0,0 +1,38 @@ +/* -*- 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 "ThreadDelay.h" +#include "mozilla/Assertions.h" +#include "mozilla/ChaosMode.h" + +#if defined(XP_WIN) +# include <windows.h> +#else +# include <unistd.h> +#endif + +namespace mozilla { + +void DelayForChaosMode(ChaosFeature aFeature, + const uint32_t aMicrosecondLimit) { + if (!ChaosMode::isActive(aFeature)) { + return; + } + + MOZ_ASSERT(aMicrosecondLimit <= 1000); +#if defined(XP_WIN) + // Windows doesn't support sleeping at less than millisecond resolution. + // We could spin here, or we could just sleep for one millisecond. + // Sleeping for a full millisecond causes heavy delays, so we don't do + // anything here for now until we have found a good way to sleep more + // precisely here. +#else + const uint32_t duration = ChaosMode::randomUint32LessThan(aMicrosecondLimit); + ::usleep(duration); +#endif +} + +} // namespace mozilla diff --git a/xpcom/threads/ThreadDelay.h b/xpcom/threads/ThreadDelay.h new file mode 100644 index 0000000000..5cfc116e4d --- /dev/null +++ b/xpcom/threads/ThreadDelay.h @@ -0,0 +1,16 @@ +/* -*- 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/ChaosMode.h" + +namespace mozilla { + +// Sleep for a random number of microseconds less than aMicrosecondLimit +// if aFeature is enabled. On Windows, the sleep will always be 1 millisecond +// due to platform limitations. +void DelayForChaosMode(ChaosFeature aFeature, const uint32_t aMicrosecondLimit); + +} // namespace mozilla diff --git a/xpcom/threads/ThreadEventQueue.cpp b/xpcom/threads/ThreadEventQueue.cpp new file mode 100644 index 0000000000..22d8ec3a70 --- /dev/null +++ b/xpcom/threads/ThreadEventQueue.cpp @@ -0,0 +1,262 @@ +/* -*- 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/ThreadEventQueue.h" +#include "mozilla/EventQueue.h" + +#include "GeckoProfiler.h" +#include "LeakRefPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIThreadInternal.h" +#include "nsThreadUtils.h" +#include "nsThread.h" +#include "ThreadEventTarget.h" +#include "mozilla/TaskController.h" + +using namespace mozilla; + +class ThreadEventQueue::NestedSink : public ThreadTargetSink { + public: + NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner) + : mQueue(aQueue), mOwner(aOwner) {} + + bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) final { + return mOwner->PutEventInternal(std::move(aEvent), aPriority, this); + } + + void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + if (mQueue) { + return mQueue->SizeOfIncludingThis(aMallocSizeOf); + } + return 0; + } + + private: + friend class ThreadEventQueue; + + // This is a non-owning reference. It must live at least until Disconnect is + // called to clear it out. + EventQueue* mQueue; + RefPtr<ThreadEventQueue> mOwner; +}; + +ThreadEventQueue::ThreadEventQueue(UniquePtr<EventQueue> aQueue, + bool aIsMainThread) + : mBaseQueue(std::move(aQueue)), + mLock("ThreadEventQueue"), + mEventsAvailable(mLock, "EventsAvail"), + mIsMainThread(aIsMainThread) { + if (aIsMainThread) { + TaskController::Get()->SetConditionVariable(&mEventsAvailable); + } +} + +ThreadEventQueue::~ThreadEventQueue() { MOZ_ASSERT(mNestedQueues.IsEmpty()); } + +bool ThreadEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) { + return PutEventInternal(std::move(aEvent), aPriority, nullptr); +} + +bool ThreadEventQueue::PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority, + NestedSink* aSink) { + // We want to leak the reference when we fail to dispatch it, so that + // we won't release the event in a wrong thread. + LeakRefPtr<nsIRunnable> event(std::move(aEvent)); + nsCOMPtr<nsIThreadObserver> obs; + + { + // Check if the runnable wants to override the passed-in priority. + // Do this outside the lock, so runnables implemented in JS can QI + // (and possibly GC) outside of the lock. + if (mIsMainThread) { + auto* e = event.get(); // can't do_QueryInterface on LeakRefPtr. + if (nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(e)) { + uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL; + runnablePrio->GetPriority(&prio); + if (prio == nsIRunnablePriority::PRIORITY_HIGH) { + aPriority = EventQueuePriority::High; + } else if (prio == nsIRunnablePriority::PRIORITY_INPUT_HIGH) { + aPriority = EventQueuePriority::InputHigh; + } else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) { + aPriority = EventQueuePriority::MediumHigh; + } else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) { + aPriority = EventQueuePriority::DeferredTimers; + } else if (prio == nsIRunnablePriority::PRIORITY_IDLE) { + aPriority = EventQueuePriority::Idle; + } + } + } + + MutexAutoLock lock(mLock); + + if (mEventsAreDoomed) { + return false; + } + + if (aSink) { + if (!aSink->mQueue) { + return false; + } + + aSink->mQueue->PutEvent(event.take(), aPriority, lock); + } else { + mBaseQueue->PutEvent(event.take(), aPriority, lock); + } + + mEventsAvailable.Notify(); + + // Make sure to grab the observer before dropping the lock, otherwise the + // event that we just placed into the queue could run and eventually delete + // this nsThread before the calling thread is scheduled again. We would then + // crash while trying to access a dead nsThread. + obs = mObserver; + } + + if (obs) { + obs->OnDispatchedEvent(); + } + + return true; +} + +already_AddRefed<nsIRunnable> ThreadEventQueue::GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay) { + nsCOMPtr<nsIRunnable> event; + { + // Scope for lock. When we are about to return, we will exit this + // scope so we can do some work after releasing the lock but + // before returning. + MutexAutoLock lock(mLock); + + for (;;) { + const bool noNestedQueue = mNestedQueues.IsEmpty(); + if (noNestedQueue) { + event = mBaseQueue->GetEvent(lock, aLastEventDelay); + } else { + // We always get events from the topmost queue when there are nested + // queues. + event = + mNestedQueues.LastElement().mQueue->GetEvent(lock, aLastEventDelay); + } + + if (event) { + break; + } + + // No runnable available. Sleep waiting for one if if we're supposed to. + // Otherwise just go ahead and return null. + if (!aMayWait) { + break; + } + + AUTO_PROFILER_LABEL("ThreadEventQueue::GetEvent::Wait", IDLE); + mEventsAvailable.Wait(); + } + } + + return event.forget(); +} + +bool ThreadEventQueue::HasPendingEvent() { + MutexAutoLock lock(mLock); + + // We always get events from the topmost queue when there are nested queues. + if (mNestedQueues.IsEmpty()) { + return mBaseQueue->HasReadyEvent(lock); + } else { + return mNestedQueues.LastElement().mQueue->HasReadyEvent(lock); + } +} + +bool ThreadEventQueue::ShutdownIfNoPendingEvents() { + MutexAutoLock lock(mLock); + if (mNestedQueues.IsEmpty() && mBaseQueue->IsEmpty(lock)) { + mEventsAreDoomed = true; + return true; + } + return false; +} + +already_AddRefed<nsISerialEventTarget> ThreadEventQueue::PushEventQueue() { + auto queue = MakeUnique<EventQueue>(); + RefPtr<NestedSink> sink = new NestedSink(queue.get(), this); + RefPtr<ThreadEventTarget> eventTarget = + new ThreadEventTarget(sink, NS_IsMainThread()); + + MutexAutoLock lock(mLock); + + mNestedQueues.AppendElement(NestedQueueItem(std::move(queue), eventTarget)); + return eventTarget.forget(); +} + +void ThreadEventQueue::PopEventQueue(nsIEventTarget* aTarget) { + MutexAutoLock lock(mLock); + + MOZ_ASSERT(!mNestedQueues.IsEmpty()); + + NestedQueueItem& item = mNestedQueues.LastElement(); + + MOZ_ASSERT(aTarget == item.mEventTarget); + + // Disconnect the event target that will be popped. + item.mEventTarget->Disconnect(lock); + + EventQueue* prevQueue = + mNestedQueues.Length() == 1 + ? mBaseQueue.get() + : mNestedQueues[mNestedQueues.Length() - 2].mQueue.get(); + + // Move events from the old queue to the new one. + nsCOMPtr<nsIRunnable> event; + TimeDuration delay; + while ((event = item.mQueue->GetEvent(lock, &delay))) { + // preserve the event delay so far + prevQueue->PutEvent(event.forget(), EventQueuePriority::Normal, lock, + &delay); + } + + mNestedQueues.RemoveLastElement(); +} + +size_t ThreadEventQueue::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + + n += mBaseQueue->SizeOfIncludingThis(aMallocSizeOf); + + n += mNestedQueues.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto& queue : mNestedQueues) { + n += queue.mEventTarget->SizeOfIncludingThis(aMallocSizeOf); + } + + return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf) + n; +} + +already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserver() { + MutexAutoLock lock(mLock); + return do_AddRef(mObserver); +} + +already_AddRefed<nsIThreadObserver> ThreadEventQueue::GetObserverOnThread() { + return do_AddRef(mObserver); +} + +void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) { + MutexAutoLock lock(mLock); + mObserver = aObserver; + if (NS_IsMainThread()) { + TaskController::Get()->SetThreadObserver(aObserver); + } +} + +ThreadEventQueue::NestedQueueItem::NestedQueueItem( + UniquePtr<EventQueue> aQueue, ThreadEventTarget* aEventTarget) + : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {} diff --git a/xpcom/threads/ThreadEventQueue.h b/xpcom/threads/ThreadEventQueue.h new file mode 100644 index 0000000000..995441e43c --- /dev/null +++ b/xpcom/threads/ThreadEventQueue.h @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +#ifndef mozilla_ThreadEventQueue_h +#define mozilla_ThreadEventQueue_h + +#include "mozilla/EventQueue.h" +#include "mozilla/CondVar.h" +#include "mozilla/SynchronizedEventQueue.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThreadObserver; + +namespace mozilla { + +class EventQueue; +class ThreadEventTarget; + +// A ThreadEventQueue implements normal monitor-style synchronization over the +// EventQueue. It also implements PushEventQueue and PopEventQueue for workers +// (see the documentation below for an explanation of those). All threads use a +// ThreadEventQueue as their event queue. Although for the main thread this +// simply forwards events to the TaskController. +class ThreadEventQueue final : public SynchronizedEventQueue { + public: + explicit ThreadEventQueue(UniquePtr<EventQueue> aQueue, + bool aIsMainThread = false); + + bool PutEvent(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority) final; + + already_AddRefed<nsIRunnable> GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) final; + bool HasPendingEvent() final; + + bool ShutdownIfNoPendingEvents() final; + + void Disconnect(const MutexAutoLock& aProofOfLock) final {} + + already_AddRefed<nsISerialEventTarget> PushEventQueue() final; + void PopEventQueue(nsIEventTarget* aTarget) final; + + already_AddRefed<nsIThreadObserver> GetObserver() final; + already_AddRefed<nsIThreadObserver> GetObserverOnThread() final; + void SetObserver(nsIThreadObserver* aObserver) final; + + Mutex& MutexRef() { return mLock; } + + size_t SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const override; + + private: + class NestedSink; + + virtual ~ThreadEventQueue(); + + bool PutEventInternal(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aPriority, NestedSink* aQueue); + + UniquePtr<EventQueue> mBaseQueue; + + struct NestedQueueItem { + UniquePtr<EventQueue> mQueue; + RefPtr<ThreadEventTarget> mEventTarget; + + NestedQueueItem(UniquePtr<EventQueue> aQueue, + ThreadEventTarget* aEventTarget); + }; + + nsTArray<NestedQueueItem> mNestedQueues; + + Mutex mLock; + CondVar mEventsAvailable; + + bool mEventsAreDoomed = false; + nsCOMPtr<nsIThreadObserver> mObserver; + bool mIsMainThread; +}; + +} // namespace mozilla + +#endif // mozilla_ThreadEventQueue_h diff --git a/xpcom/threads/ThreadEventTarget.cpp b/xpcom/threads/ThreadEventTarget.cpp new file mode 100644 index 0000000000..75a109cceb --- /dev/null +++ b/xpcom/threads/ThreadEventTarget.cpp @@ -0,0 +1,135 @@ +/* -*- 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 "ThreadEventTarget.h" +#include "mozilla/ThreadEventQueue.h" + +#include "LeakRefPtr.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" +#include "nsITimer.h" +#include "nsThreadManager.h" +#include "nsThreadSyncDispatch.h" +#include "nsThreadUtils.h" +#include "nsXPCOMPrivate.h" // for gXPCOMThreadsShutDown +#include "ThreadDelay.h" + +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracer.h" +# include "TracedTaskCommon.h" +using namespace mozilla::tasktracer; +#endif + +using namespace mozilla; + +ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink, + bool aIsMainThread) + : mSink(aSink), mIsMainThread(aIsMainThread) { + mThread = PR_GetCurrentThread(); +} + +void ThreadEventTarget::SetCurrentThread() { mThread = PR_GetCurrentThread(); } + +void ThreadEventTarget::ClearCurrentThread() { mThread = nullptr; } + +NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget) + +NS_IMETHODIMP +ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) { + return Dispatch(do_AddRef(aRunnable), aFlags); +} + +NS_IMETHODIMP +ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + // We want to leak the reference when we fail to dispatch it, so that + // we won't release the event in a wrong thread. + LeakRefPtr<nsIRunnable> event(std::move(aEvent)); + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + if (gXPCOMThreadsShutDown && !mIsMainThread) { + NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads"); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + +#ifdef MOZ_TASK_TRACER + nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take()); + (static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask(); + // XXX tracedRunnable will always leaked when we fail to disptch. + event = tracedRunnable.forget(); +#endif + + LogRunnable::LogDispatch(event.get()); + + if (aFlags & DISPATCH_SYNC) { + nsCOMPtr<nsIEventTarget> current = GetCurrentEventTarget(); + if (NS_WARN_IF(!current)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // XXX we should be able to do something better here... we should + // be able to monitor the slot occupied by this event and use + // that to tell us when the event has been processed. + + RefPtr<nsThreadSyncDispatch> wrapper = + new nsThreadSyncDispatch(current.forget(), event.take()); + bool success = mSink->PutEvent(do_AddRef(wrapper), + EventQueuePriority::Normal); // hold a ref + if (!success) { + // PutEvent leaked the wrapper runnable object on failure, so we + // explicitly release this object once for that. Note that this + // object will be released again soon because it exits the scope. + wrapper.get()->Release(); + return NS_ERROR_UNEXPECTED; + } + + // Allows waiting; ensure no locks are held that would deadlock us! + SpinEventLoopUntil( + [&, wrapper]() -> bool { return !wrapper->IsPending(); }); + + return NS_OK; + } + + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END, + "unexpected dispatch flags"); + if (!mSink->PutEvent(event.take(), EventQueuePriority::Normal)) { + return NS_ERROR_UNEXPECTED; + } + // Delay to encourage the receiving task to run before we do work. + DelayForChaosMode(ChaosFeature::TaskDispatching, 1000); + return NS_OK; +} + +NS_IMETHODIMP +ThreadEventTarget::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); +} + +NS_IMETHODIMP +ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { + *aIsOnCurrentThread = IsOnCurrentThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +ThreadEventTarget::IsOnCurrentThreadInfallible() { + // This method is only going to be called if `mThread` is null, which + // only happens when the thread has exited the event loop. Therefore, when + // we are called, we can never be on this thread. + return false; +} diff --git a/xpcom/threads/ThreadEventTarget.h b/xpcom/threads/ThreadEventTarget.h new file mode 100644 index 0000000000..1b298521cd --- /dev/null +++ b/xpcom/threads/ThreadEventTarget.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +#ifndef mozilla_ThreadEventTarget_h +#define mozilla_ThreadEventTarget_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/SynchronizedEventQueue.h" // for ThreadTargetSink +#include "nsISerialEventTarget.h" + +namespace mozilla { + +// ThreadEventTarget handles the details of posting an event to a thread. It can +// be used with any ThreadTargetSink implementation. +class ThreadEventTarget final : public nsISerialEventTarget { + public: + ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + // Disconnects the target so that it can no longer post events. + void Disconnect(const MutexAutoLock& aProofOfLock) { + mSink->Disconnect(aProofOfLock); + } + + // Sets the thread for which IsOnCurrentThread returns true to the current + // thread. + void SetCurrentThread(); + // Call ClearCurrentThread() before the PRThread is deleted on thread join. + void ClearCurrentThread(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mSink) { + n += mSink->SizeOfIncludingThis(aMallocSizeOf); + } + return aMallocSizeOf(this) + n; + } + + private: + ~ThreadEventTarget() = default; + + RefPtr<ThreadTargetSink> mSink; + bool mIsMainThread; +}; + +} // namespace mozilla + +#endif // mozilla_ThreadEventTarget_h diff --git a/xpcom/threads/ThreadLocalVariables.cpp b/xpcom/threads/ThreadLocalVariables.cpp new file mode 100644 index 0000000000..606c0c6384 --- /dev/null +++ b/xpcom/threads/ThreadLocalVariables.cpp @@ -0,0 +1,16 @@ +/* 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/ThreadLocal.h" + +// This variable is used to ensure creating new URI doesn't put us in an +// infinite loop +MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount; + +void InitThreadLocalVariables() { + if (!gTlsURLRecursionCount.init()) { + MOZ_CRASH("Could not init gTlsURLRecursionCount"); + } + gTlsURLRecursionCount.set(0); +} diff --git a/xpcom/threads/ThrottledEventQueue.cpp b/xpcom/threads/ThrottledEventQueue.cpp new file mode 100644 index 0000000000..578d7a4cd7 --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.cpp @@ -0,0 +1,439 @@ +/* -*- 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 "ThrottledEventQueue.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CondVar.h" +#include "mozilla/EventQueue.h" +#include "mozilla/Mutex.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +namespace {} // anonymous namespace + +// The ThrottledEventQueue is designed with inner and outer objects: +// +// XPCOM code base event target +// | | +// v v +// +-------+ +--------+ +// | Outer | +-->|executor| +// +-------+ | +--------+ +// | | | +// | +-------+ | +// +-->| Inner |<--+ +// +-------+ +// +// Client code references the outer nsIEventTarget which in turn references +// an inner object, which actually holds the queue of runnables. +// +// Whenever the queue is non-empty (and not paused), it keeps an "executor" +// runnable dispatched to the base event target. Each time the executor is run, +// it draws the next event from Inner's queue and runs it. If that queue has +// more events, the executor is dispatched to the base again. +// +// The executor holds a strong reference to the Inner object. This means that if +// the outer object is dereferenced and destroyed, the Inner object will remain +// live for as long as the executor exists - that is, until the Inner's queue is +// empty. +// +// A Paused ThrottledEventQueue does not enqueue an executor when new events are +// added. Any executor previously queued on the base event target draws no +// events from a Paused ThrottledEventQueue, and returns without re-enqueueing +// itself. Since there is no executor keeping the Inner object alive until its +// queue is empty, dropping a Paused ThrottledEventQueue may drop the Inner +// while it still owns events. This is the correct behavior: if there are no +// references to it, it will never be Resumed, and thus it will never dispatch +// events again. +// +// Resuming a ThrottledEventQueue must dispatch an executor, so calls to Resume +// are fallible for the same reasons as calls to Dispatch. +// +// The xpcom shutdown process drains the main thread's event queue several +// times, so if a ThrottledEventQueue is being driven by the main thread, it +// should get emptied out by the time we reach the "eventq shutdown" phase. +class ThrottledEventQueue::Inner final : public nsISupports { + // The runnable which is dispatched to the underlying base target. Since + // we only execute one event at a time we just re-use a single instance + // of this class while there are events left in the queue. + class Executor final : public Runnable, public nsIRunnablePriority { + // The Inner whose runnables we execute. mInner->mExecutor points + // to this executor, forming a reference loop. + RefPtr<Inner> mInner; + + ~Executor() = default; + + public: + explicit Executor(Inner* aInner) + : Runnable("ThrottledEventQueue::Inner::Executor"), mInner(aInner) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHODIMP + Run() override { + mInner->ExecuteRunnable(); + return NS_OK; + } + + NS_IMETHODIMP + GetPriority(uint32_t* aPriority) override { + *aPriority = mInner->mPriority; + return NS_OK; + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHODIMP + GetName(nsACString& aName) override { return mInner->CurrentName(aName); } +#endif + }; + + mutable Mutex mMutex; + mutable CondVar mIdleCondVar; + + // As-of-yet unexecuted runnables queued on this ThrottledEventQueue. + // + // Used from any thread; protected by mMutex. Signals mIdleCondVar when + // emptied. + EventQueueSized<64> mEventQueue; + + // The event target we dispatch our events (actually, just our Executor) to. + // + // Written only during construction. Readable by any thread without locking. + nsCOMPtr<nsISerialEventTarget> mBaseTarget; + + // The Executor that we dispatch to mBaseTarget to draw runnables from our + // queue. mExecutor->mInner points to this Inner, forming a reference loop. + // + // Used from any thread; protected by mMutex. + nsCOMPtr<nsIRunnable> mExecutor; + + const char* mName; + + const uint32_t mPriority; + + // True if this queue is currently paused. + // Used from any thread; protected by mMutex. + bool mIsPaused; + + explicit Inner(nsISerialEventTarget* aBaseTarget, const char* aName, + uint32_t aPriority) + : mMutex("ThrottledEventQueue"), + mIdleCondVar(mMutex, "ThrottledEventQueue:Idle"), + mBaseTarget(aBaseTarget), + mName(aName), + mPriority(aPriority), + mIsPaused(false) { + MOZ_ASSERT(mName, "Must pass a valid name!"); + } + + ~Inner() { +#ifdef DEBUG + MutexAutoLock lock(mMutex); + + // As long as an executor exists, it had better keep us alive, since it's + // going to call ExecuteRunnable on us. + MOZ_ASSERT(!mExecutor); + + // If we have any events in our queue, there should be an executor queued + // for them, and that should have kept us alive. The exception is that, if + // we're paused, we don't enqueue an executor. + MOZ_ASSERT(mEventQueue.IsEmpty(lock) || IsPaused(lock)); + + // Some runnables are only safe to drop on the main thread, so if our queue + // isn't empty, we'd better be on the main thread. + MOZ_ASSERT_IF(!mEventQueue.IsEmpty(lock), NS_IsMainThread()); +#endif + } + + // Make sure an executor has been queued on our base target. If we already + // have one, do nothing; otherwise, create and dispatch it. + nsresult EnsureExecutor(MutexAutoLock& lock) { + if (mExecutor) return NS_OK; + + // Note, this creates a ref cycle keeping the inner alive + // until the queue is drained. + mExecutor = new Executor(this); + nsresult rv = mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + mExecutor = nullptr; + return rv; + } + + return NS_OK; + } + + nsresult CurrentName(nsACString& aName) { + nsCOMPtr<nsIRunnable> event; + +#ifdef DEBUG + bool currentThread = false; + mBaseTarget->IsOnCurrentThread(¤tThread); + MOZ_ASSERT(currentThread); +#endif + + { + MutexAutoLock lock(mMutex); + event = mEventQueue.PeekEvent(lock); + // It is possible that mEventQueue wasn't empty when the executor + // was added to the queue, but someone processed events from mEventQueue + // before the executor, this is why mEventQueue is empty here + if (!event) { + aName.AssignLiteral("no runnables left in the ThrottledEventQueue"); + return NS_OK; + } + } + + if (nsCOMPtr<nsINamed> named = do_QueryInterface(event)) { + nsresult rv = named->GetName(aName); + return rv; + } + + aName.AssignASCII(mName); + return NS_OK; + } + + void ExecuteRunnable() { + // Any thread + nsCOMPtr<nsIRunnable> event; + +#ifdef DEBUG + bool currentThread = false; + mBaseTarget->IsOnCurrentThread(¤tThread); + MOZ_ASSERT(currentThread); +#endif + + { + MutexAutoLock lock(mMutex); + + // Normally, a paused queue doesn't dispatch any executor, but we might + // have been paused after the executor was already in flight. There's no + // way to yank the executor out of the base event target, so we just check + // for a paused queue here and return without running anything. We'll + // create a new executor when we're resumed. + if (IsPaused(lock)) { + // Note, this breaks a ref cycle. + mExecutor = nullptr; + return; + } + + // We only dispatch an executor runnable when we know there is something + // in the queue, so this should never fail. + event = mEventQueue.GetEvent(lock); + MOZ_ASSERT(event); + + // If there are more events in the queue, then dispatch the next + // executor. We do this now, before running the event, because + // the event might spin the event loop and we don't want to stall + // the queue. + if (mEventQueue.HasReadyEvent(lock)) { + // Dispatch the next base target runnable to attempt to execute + // the next throttled event. We must do this before executing + // the event in case the event spins the event loop. + MOZ_ALWAYS_SUCCEEDS( + mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL)); + } + + // Otherwise the queue is empty and we can stop dispatching the + // executor. + else { + // Break the Executor::mInner / Inner::mExecutor reference loop. + mExecutor = nullptr; + mIdleCondVar.NotifyAll(); + } + } + + // Execute the event now that we have unlocked. + LogRunnable::Run log(event); + Unused << event->Run(); + + // To cover the event's destructor code in the LogRunnable log + event = nullptr; + } + + public: + static already_AddRefed<Inner> Create(nsISerialEventTarget* aBaseTarget, + const char* aName, uint32_t aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + // FIXME: This assertion only worked when `sCurrentShutdownPhase` was not + // being updated. + // MOZ_ASSERT(ClearOnShutdown_Internal::sCurrentShutdownPhase == + // ShutdownPhase::NotInShutdown); + + RefPtr<Inner> ref = new Inner(aBaseTarget, aName, aPriority); + return ref.forget(); + } + + bool IsEmpty() const { + // Any thread + return Length() == 0; + } + + uint32_t Length() const { + // Any thread + MutexAutoLock lock(mMutex); + return mEventQueue.Count(lock); + } + + already_AddRefed<nsIRunnable> GetEvent() { + MutexAutoLock lock(mMutex); + return mEventQueue.GetEvent(lock); + } + + void AwaitIdle() const { + // Any thread, except the main thread or our base target. Blocking the + // main thread is forbidden. Blocking the base target is guaranteed to + // produce a deadlock. + MOZ_ASSERT(!NS_IsMainThread()); +#ifdef DEBUG + bool onBaseTarget = false; + Unused << mBaseTarget->IsOnCurrentThread(&onBaseTarget); + MOZ_ASSERT(!onBaseTarget); +#endif + + MutexAutoLock lock(mMutex); + while (mExecutor || IsPaused(lock)) { + mIdleCondVar.Wait(); + } + } + + bool IsPaused() const { + MutexAutoLock lock(mMutex); + return IsPaused(lock); + } + + bool IsPaused(const MutexAutoLock& aProofOfLock) const { return mIsPaused; } + + nsresult SetIsPaused(bool aIsPaused) { + MutexAutoLock lock(mMutex); + + // If we will be unpaused, and we have events in our queue, make sure we + // have an executor queued on the base event target to run them. Do this + // before we actually change mIsPaused, since this is fallible. + if (!aIsPaused && !mEventQueue.IsEmpty(lock)) { + nsresult rv = EnsureExecutor(lock); + if (NS_FAILED(rv)) { + return rv; + } + } + + mIsPaused = aIsPaused; + return NS_OK; + } + + nsresult DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + // Any thread + nsCOMPtr<nsIRunnable> r = aEvent; + return Dispatch(r.forget(), aFlags); + } + + nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) { + MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END); + + // Any thread + MutexAutoLock lock(mMutex); + + if (!IsPaused(lock)) { + // Make sure we have an executor in flight to process events. This is + // fallible, so do it first. Our lock will prevent the executor from + // accessing the event queue before we add the event below. + nsresult rv = EnsureExecutor(lock); + if (NS_FAILED(rv)) return rv; + } + + // Only add the event to the underlying queue if are able to + // dispatch to our base target. + nsCOMPtr<nsIRunnable> event(aEvent); + LogRunnable::LogDispatch(event); + mEventQueue.PutEvent(event.forget(), EventQueuePriority::Normal, lock); + return NS_OK; + } + + nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelay) { + // The base target may implement this, but we don't. Always fail + // to provide consistent behavior. + return NS_ERROR_NOT_IMPLEMENTED; + } + + bool IsOnCurrentThread() { return mBaseTarget->IsOnCurrentThread(); } + + NS_DECL_THREADSAFE_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsISupports); + +NS_IMPL_ISUPPORTS_INHERITED(ThrottledEventQueue::Inner::Executor, Runnable, + nsIRunnablePriority) + +NS_IMPL_ISUPPORTS(ThrottledEventQueue, ThrottledEventQueue, nsIEventTarget, + nsISerialEventTarget); + +ThrottledEventQueue::ThrottledEventQueue(already_AddRefed<Inner> aInner) + : mInner(aInner) { + MOZ_ASSERT(mInner); +} + +already_AddRefed<ThrottledEventQueue> ThrottledEventQueue::Create( + nsISerialEventTarget* aBaseTarget, const char* aName, uint32_t aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBaseTarget); + + RefPtr<Inner> inner = Inner::Create(aBaseTarget, aName, aPriority); + + RefPtr<ThrottledEventQueue> ref = new ThrottledEventQueue(inner.forget()); + return ref.forget(); +} + +bool ThrottledEventQueue::IsEmpty() const { return mInner->IsEmpty(); } + +uint32_t ThrottledEventQueue::Length() const { return mInner->Length(); } + +// Get the next runnable from the queue +already_AddRefed<nsIRunnable> ThrottledEventQueue::GetEvent() { + return mInner->GetEvent(); +} + +void ThrottledEventQueue::AwaitIdle() const { return mInner->AwaitIdle(); } + +nsresult ThrottledEventQueue::SetIsPaused(bool aIsPaused) { + return mInner->SetIsPaused(aIsPaused); +} + +bool ThrottledEventQueue::IsPaused() const { return mInner->IsPaused(); } + +NS_IMETHODIMP +ThrottledEventQueue::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + return mInner->DispatchFromScript(aEvent, aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + return mInner->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + return mInner->DelayedDispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::IsOnCurrentThread(bool* aResult) { + *aResult = mInner->IsOnCurrentThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +ThrottledEventQueue::IsOnCurrentThreadInfallible() { + return mInner->IsOnCurrentThread(); +} + +} // namespace mozilla diff --git a/xpcom/threads/ThrottledEventQueue.h b/xpcom/threads/ThrottledEventQueue.h new file mode 100644 index 0000000000..cf37a10a6d --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.h @@ -0,0 +1,118 @@ +/* -*- 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/. */ + +// nsIEventTarget wrapper for throttling event dispatch. + +#ifndef mozilla_ThrottledEventQueue_h +#define mozilla_ThrottledEventQueue_h + +#include "nsISerialEventTarget.h" + +#define NS_THROTTLEDEVENTQUEUE_IID \ + { \ + 0x8f3cf7dc, 0xfc14, 0x4ad5, { \ + 0x9f, 0xd5, 0xdb, 0x79, 0xbc, 0xe6, 0xd5, 0x08 \ + } \ + } + +namespace mozilla { + +// A ThrottledEventQueue is an event target that can be used to throttle +// events being dispatched to another base target. It maintains its +// own queue of events and only dispatches one at a time to the wrapped +// target. This can be used to avoid flooding the base target. +// +// Flooding is avoided via a very simple principle. Runnables dispatched +// to the ThrottledEventQueue are only dispatched to the base target +// one at a time. Only once that runnable has executed will we dispatch +// the next runnable to the base target. This in effect makes all +// runnables passing through the ThrottledEventQueue yield to other work +// on the base target. +// +// ThrottledEventQueue keeps runnables waiting to be dispatched to the +// base in its own internal queue. Code can query the length of this +// queue using IsEmpty() and Length(). Further, code implement back +// pressure by checking the depth of the queue and deciding to stop +// issuing runnables if they see the ThrottledEventQueue is backed up. +// Code running on other threads could even use AwaitIdle() to block +// all operation until the ThrottledEventQueue drains. +// +// Note, this class is similar to TaskQueue, but also differs in a few +// ways. First, it is a very simple nsIEventTarget implementation. It +// does not use the AbstractThread API. +// +// In addition, ThrottledEventQueue currently dispatches its next +// runnable to the base target *before* running the current event. This +// allows the event code to spin the event loop without stalling the +// ThrottledEventQueue. In contrast, TaskQueue only dispatches its next +// runnable after running the current event. That approach is necessary +// for TaskQueue in order to work with thread pool targets. +// +// So, if you are targeting a thread pool you probably want a TaskQueue. +// If you are targeting a single thread or other non-concurrent event +// target, you probably want a ThrottledEventQueue. +// +// If you drop a ThrottledEventQueue while its queue still has events to be run, +// they will continue to be dispatched as usual to the base. Only once the last +// event has run will all the ThrottledEventQueue's memory be freed. +class ThrottledEventQueue final : public nsISerialEventTarget { + class Inner; + RefPtr<Inner> mInner; + + explicit ThrottledEventQueue(already_AddRefed<Inner> aInner); + ~ThrottledEventQueue() = default; + + public: + // Create a ThrottledEventQueue for the given target. + static already_AddRefed<ThrottledEventQueue> Create( + nsISerialEventTarget* aBaseTarget, const char* aName, + uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL); + + // Determine if there are any events pending in the queue. + bool IsEmpty() const; + + // Determine how many events are pending in the queue. + uint32_t Length() const; + + already_AddRefed<nsIRunnable> GetEvent(); + + // Block the current thread until the queue is empty. This may not be called + // on the main thread or the base target. The ThrottledEventQueue must not be + // paused. + void AwaitIdle() const; + + // If |aIsPaused| is true, pause execution of events from this queue. No + // events from this queue will be run until this is called with |aIsPaused| + // false. + // + // To un-pause a ThrottledEventQueue, we need to dispatch a runnable to the + // underlying event target. That operation may fail, so this method is + // fallible as well. + // + // Note that, although ThrottledEventQueue's behavior is descibed as queueing + // events on the base target, an event queued on a TEQ is never actually moved + // to any other queue. What is actually dispatched to the base is an + // "executor" event which, when run, removes an event from the TEQ and runs it + // immediately. This means that you can pause a TEQ even after the executor + // has been queued on the base target, and even so, no events from the TEQ + // will run. When the base target gets around to running the executor, the + // executor will see that the TEQ is paused, and do nothing. + [[nodiscard]] nsresult SetIsPaused(bool aIsPaused); + + // Return true if this ThrottledEventQueue is paused. + bool IsPaused() const; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_THROTTLEDEVENTQUEUE_IID); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ThrottledEventQueue, NS_THROTTLEDEVENTQUEUE_IID); + +} // namespace mozilla + +#endif // mozilla_ThrottledEventQueue_h diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp new file mode 100644 index 0000000000..9cd60e1bae --- /dev/null +++ b/xpcom/threads/TimerThread.cpp @@ -0,0 +1,779 @@ +/* -*- 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 "nsTimerImpl.h" +#include "TimerThread.h" + +#include "nsThreadUtils.h" +#include "pratom.h" + +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/OperatorNewExtensions.h" + +#include <math.h> + +using namespace mozilla; +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracerImpl.h" +using namespace mozilla::tasktracer; +#endif + +NS_IMPL_ISUPPORTS_INHERITED(TimerThread, Runnable, nsIObserver) + +TimerThread::TimerThread() + : Runnable("TimerThread"), + mInitialized(false), + mMonitor("TimerThread.mMonitor"), + mShutdown(false), + mWaiting(false), + mNotified(false), + mSleeping(false), + mAllowedEarlyFiringMicroseconds(0) {} + +TimerThread::~TimerThread() { + mThread = nullptr; + + NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread"); +} + +nsresult TimerThread::InitLocks() { return NS_OK; } + +namespace { + +class TimerObserverRunnable : public Runnable { + public: + explicit TimerObserverRunnable(nsIObserver* aObserver) + : mozilla::Runnable("TimerObserverRunnable"), mObserver(aObserver) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsIObserver> mObserver; +}; + +NS_IMETHODIMP +TimerObserverRunnable::Run() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(mObserver, "sleep_notification", false); + observerService->AddObserver(mObserver, "wake_notification", false); + observerService->AddObserver(mObserver, "suspend_process_notification", + false); + observerService->AddObserver(mObserver, "resume_process_notification", + false); + } + return NS_OK; +} + +} // namespace + +namespace { + +// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. +// It's needed to avoid contention over the default allocator lock when +// firing timer events (see bug 733277). The thread-safety is required because +// nsTimerEvent objects are allocated on the timer thread, and freed on another +// thread. Because TimerEventAllocator has its own lock, contention over that +// lock is limited to the allocation and deallocation of nsTimerEvent objects. +// +// Because this is layered over ArenaAllocator, it never shrinks -- even +// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list +// for later recycling. So the amount of memory consumed will always be equal +// to the high-water mark consumption. But nsTimerEvents are small and it's +// unusual to have more than a few hundred of them, so this shouldn't be a +// problem in practice. + +class TimerEventAllocator { + private: + struct FreeEntry { + FreeEntry* mNext; + }; + + ArenaAllocator<4096> mPool; + FreeEntry* mFirstFree; + mozilla::Monitor mMonitor; + + public: + TimerEventAllocator() + : mPool(), mFirstFree(nullptr), mMonitor("TimerEventAllocator") {} + + ~TimerEventAllocator() = default; + + void* Alloc(size_t aSize); + void Free(void* aPtr); +}; + +} // namespace + +// This is a nsICancelableRunnable because we can dispatch it to Workers and +// those can be shut down at any time, and in these cases, Cancel() is called +// instead of Run(). +class nsTimerEvent final : public CancelableRunnable { + public: + NS_IMETHOD Run() override; + + nsresult Cancel() override { + mTimer->Cancel(); + return NS_OK; + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +#endif + + explicit nsTimerEvent(already_AddRefed<nsTimerImpl> aTimer) + : mozilla::CancelableRunnable("nsTimerEvent"), + mTimer(aTimer), + mGeneration(mTimer->GetGeneration()) { + // Note: We override operator new for this class, and the override is + // fallible! + sAllocatorUsers++; + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + mInitTime = TimeStamp::Now(); + } + } + + static void Init(); + static void Shutdown(); + static void DeleteAllocatorIfNeeded(); + + static void* operator new(size_t aSize) noexcept(true) { + return sAllocator->Alloc(aSize); + } + void operator delete(void* aPtr) { + sAllocator->Free(aPtr); + DeleteAllocatorIfNeeded(); + } + + already_AddRefed<nsTimerImpl> ForgetTimer() { return mTimer.forget(); } + + private: + nsTimerEvent(const nsTimerEvent&) = delete; + nsTimerEvent& operator=(const nsTimerEvent&) = delete; + nsTimerEvent& operator=(const nsTimerEvent&&) = delete; + + ~nsTimerEvent() { + MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, + "This will result in us attempting to deallocate the " + "nsTimerEvent allocator twice"); + sAllocatorUsers--; + } + + TimeStamp mInitTime; + RefPtr<nsTimerImpl> mTimer; + const int32_t mGeneration; + + static TimerEventAllocator* sAllocator; + + static Atomic<int32_t, SequentiallyConsistent> sAllocatorUsers; + static Atomic<bool, SequentiallyConsistent> sCanDeleteAllocator; +}; + +TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; +Atomic<int32_t, SequentiallyConsistent> nsTimerEvent::sAllocatorUsers; +Atomic<bool, SequentiallyConsistent> nsTimerEvent::sCanDeleteAllocator; + +namespace { + +void* TimerEventAllocator::Alloc(size_t aSize) { + MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); + + mozilla::MonitorAutoLock lock(mMonitor); + + void* p; + if (mFirstFree) { + p = mFirstFree; + mFirstFree = mFirstFree->mNext; + } else { + p = mPool.Allocate(aSize, fallible); + } + + return p; +} + +void TimerEventAllocator::Free(void* aPtr) { + mozilla::MonitorAutoLock lock(mMonitor); + + FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr); + + entry->mNext = mFirstFree; + mFirstFree = entry; +} + +} // namespace + +void nsTimerEvent::Init() { sAllocator = new TimerEventAllocator(); } + +void nsTimerEvent::Shutdown() { + sCanDeleteAllocator = true; + DeleteAllocatorIfNeeded(); +} + +void nsTimerEvent::DeleteAllocatorIfNeeded() { + if (sCanDeleteAllocator && sAllocatorUsers == 0) { + delete sAllocator; + sAllocator = nullptr; + } +} + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +nsTimerEvent::GetName(nsACString& aName) { + bool current; + MOZ_RELEASE_ASSERT( + NS_SUCCEEDED(mTimer->mEventTarget->IsOnCurrentThread(¤t)) && + current); + + mTimer->GetName(aName); + return NS_OK; +} +#endif + +NS_IMETHODIMP +nsTimerEvent::Run() { + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + TimeStamp now = TimeStamp::Now(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", this, + (now - mInitTime).ToMilliseconds())); + } + + mTimer->Fire(mGeneration); + + return NS_OK; +} + +nsresult TimerThread::Init() { + mMonitor.AssertCurrentThreadOwns(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("TimerThread::Init [%d]\n", mInitialized)); + + if (!mInitialized) { + nsTimerEvent::Init(); + + // We hold on to mThread to keep the thread alive. + nsresult rv = + NS_NewNamedThread("Timer Thread", getter_AddRefs(mThread), this); + if (NS_FAILED(rv)) { + mThread = nullptr; + } else { + RefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this); + if (NS_IsMainThread()) { + r->Run(); + } else { + NS_DispatchToMainThread(r); + } + } + + mInitialized = true; + } + + if (!mThread) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult TimerThread::Shutdown() { + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown begin\n")); + + if (!mThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsTArray<RefPtr<nsTimerImpl>> timers; + { + // lock scope + MonitorAutoLock lock(mMonitor); + + mShutdown = true; + + // notify the cond var so that Run() can return + if (mWaiting) { + mNotified = true; + mMonitor.Notify(); + } + + // Need to copy content of mTimers array to a local array + // because call to timers' Cancel() (and release its self) + // must not be done under the lock. Destructor of a callback + // might potentially call some code reentering the same lock + // that leads to unexpected behavior or deadlock. + // See bug 422472. + for (const UniquePtr<Entry>& entry : mTimers) { + timers.AppendElement(entry->Take()); + } + + mTimers.Clear(); + } + + for (const RefPtr<nsTimerImpl>& timer : timers) { + if (timer) { + timer->Cancel(); + } + } + + mThread->Shutdown(); // wait for the thread to die + + nsTimerEvent::Shutdown(); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n")); + return NS_OK; +} + +namespace { + +struct MicrosecondsToInterval { + PRIntervalTime operator[](size_t aMs) const { + return PR_MicrosecondsToInterval(aMs); + } +}; + +struct IntervalComparator { + int operator()(PRIntervalTime aInterval) const { + return (0 < aInterval) ? -1 : 1; + } +}; + +} // namespace + +NS_IMETHODIMP +TimerThread::Run() { + NS_SetCurrentThreadName("Timer"); + + MonitorAutoLock lock(mMonitor); + + // We need to know how many microseconds give a positive PRIntervalTime. This + // is platform-dependent and we calculate it at runtime, finding a value |v| + // such that |PR_MicrosecondsToInterval(v) > 0| and then binary-searching in + // the range [0, v) to find the ms-to-interval scale. + uint32_t usForPosInterval = 1; + while (PR_MicrosecondsToInterval(usForPosInterval) == 0) { + usForPosInterval <<= 1; + } + + size_t usIntervalResolution; + BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval, + IntervalComparator(), &usIntervalResolution); + MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution - 1) == 0); + MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution) == 1); + + // Half of the amount of microseconds needed to get positive PRIntervalTime. + // We use this to decide how to round our wait times later + mAllowedEarlyFiringMicroseconds = usIntervalResolution / 2; + bool forceRunNextTimer = false; + + while (!mShutdown) { + // Have to use PRIntervalTime here, since PR_WaitCondVar takes it + TimeDuration waitFor; + bool forceRunThisTimer = forceRunNextTimer; + forceRunNextTimer = false; + + if (mSleeping) { + // Sleep for 0.1 seconds while not firing timers. + uint32_t milliseconds = 100; + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + milliseconds = ChaosMode::randomUint32LessThan(200); + } + waitFor = TimeDuration::FromMilliseconds(milliseconds); + } else { + waitFor = TimeDuration::Forever(); + TimeStamp now = TimeStamp::Now(); + + RemoveLeadingCanceledTimersInternal(); + + if (!mTimers.IsEmpty()) { + if (now >= mTimers[0]->Value()->mTimeout || forceRunThisTimer) { + next: + // NB: AddRef before the Release under RemoveTimerInternal to avoid + // mRefCnt passing through zero, in case all other refs than the one + // from mTimers have gone away (the last non-mTimers[i]-ref's Release + // must be racing with us, blocked in gThread->RemoveTimer waiting + // for TimerThread::mMonitor, under nsTimerImpl::Release. + + RefPtr<nsTimerImpl> timerRef(mTimers[0]->Take()); + RemoveFirstTimerInternal(); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("Timer thread woke up %fms from when it was supposed to\n", + fabs((now - timerRef->mTimeout).ToMilliseconds()))); + + // We are going to let the call to PostTimerEvent here handle the + // release of the timer so that we don't end up releasing the timer + // on the TimerThread instead of on the thread it targets. + { + LogTimerEvent::Run run(timerRef.get()); + timerRef = PostTimerEvent(timerRef.forget()); + } + + if (timerRef) { + // We got our reference back due to an error. + // Unhook the nsRefPtr, and release manually so we can get the + // refcount. + nsrefcnt rc = timerRef.forget().take()->Release(); + (void)rc; + + // The nsITimer interface requires that its users keep a reference + // to the timers they use while those timers are initialized but + // have not yet fired. If this ever happens, it is a bug in the + // code that created and used the timer. + // + // Further, note that this should never happen even with a + // misbehaving user, because nsTimerImpl::Release checks for a + // refcount of 1 with an armed timer (a timer whose only reference + // is from the timer thread) and when it hits this will remove the + // timer from the timer thread and thus destroy the last reference, + // preventing this situation from occurring. + MOZ_ASSERT(rc != 0, "destroyed timer off its target thread!"); + } + + if (mShutdown) { + break; + } + + // Update now, as PostTimerEvent plus the locking may have taken a + // tick or two, and we may goto next below. + now = TimeStamp::Now(); + } + } + + RemoveLeadingCanceledTimersInternal(); + + if (!mTimers.IsEmpty()) { + TimeStamp timeout = mTimers[0]->Value()->mTimeout; + + // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer + // is due now or overdue. + // + // Note that we can only sleep for integer values of a certain + // resolution. We use mAllowedEarlyFiringMicroseconds, calculated + // before, to do the optimal rounding (i.e., of how to decide what + // interval is so small we should not wait at all). + double microseconds = (timeout - now).ToMilliseconds() * 1000; + + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + // The mean value of sFractions must be 1 to ensure that + // the average of a long sequence of timeouts converges to the + // actual sum of their times. + static const float sFractions[] = {0.0f, 0.25f, 0.5f, 0.75f, + 1.0f, 1.75f, 2.75f}; + microseconds *= sFractions[ChaosMode::randomUint32LessThan( + ArrayLength(sFractions))]; + forceRunNextTimer = true; + } + + if (microseconds < mAllowedEarlyFiringMicroseconds) { + forceRunNextTimer = false; + goto next; // round down; execute event now + } + waitFor = TimeDuration::FromMicroseconds(microseconds); + if (waitFor.IsZero()) { + // round up, wait the minimum time we can wait + waitFor = TimeDuration::FromMicroseconds(1); + } + } + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + if (waitFor == TimeDuration::Forever()) + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("waiting forever\n")); + else + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("waiting for %f\n", waitFor.ToMilliseconds())); + } + } + + mWaiting = true; + mNotified = false; + mMonitor.Wait(waitFor); + if (mNotified) { + forceRunNextTimer = false; + } + mWaiting = false; + } + + return NS_OK; +} + +nsresult TimerThread::AddTimer(nsTimerImpl* aTimer) { + MonitorAutoLock lock(mMonitor); + + if (!aTimer->mEventTarget) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = Init(); + if (NS_FAILED(rv)) { + return rv; + } + + // Add the timer to our list. + if (!AddTimerInternal(aTimer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Awaken the timer thread. + if (mWaiting && mTimers[0]->Value() == aTimer) { + mNotified = true; + mMonitor.Notify(); + } + + return NS_OK; +} + +nsresult TimerThread::RemoveTimer(nsTimerImpl* aTimer) { + MonitorAutoLock lock(mMonitor); + + // Remove the timer from our array. Tell callers that aTimer was not found + // by returning NS_ERROR_NOT_AVAILABLE. + + if (!RemoveTimerInternal(aTimer)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Awaken the timer thread. + if (mWaiting) { + mNotified = true; + mMonitor.Notify(); + } + + return NS_OK; +} + +TimeStamp TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound) { + MonitorAutoLock lock(mMonitor); + TimeStamp timeStamp = aDefault; + uint32_t index = 0; + +#ifdef DEBUG + TimeStamp firstTimeStamp; + Entry* initialFirstEntry = nullptr; + if (!mTimers.IsEmpty()) { + initialFirstEntry = mTimers[0].get(); + firstTimeStamp = mTimers[0]->Timeout(); + } +#endif + + auto end = mTimers.end(); + while (end != mTimers.begin()) { + nsTimerImpl* timer = mTimers[0]->Value(); + if (timer) { + if (timer->mTimeout > aDefault) { + timeStamp = aDefault; + break; + } + + // Don't yield to timers created with the *_LOW_PRIORITY type. + if (!timer->IsLowPriority()) { + bool isOnCurrentThread = false; + nsresult rv = + timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread); + if (NS_SUCCEEDED(rv) && isOnCurrentThread) { + timeStamp = timer->mTimeout; + break; + } + } + + if (++index > aSearchBound) { + // Track the currently highest timeout so that we can bail out when we + // reach the bound or when we find a timer for the current thread. + // This won't give accurate information if we stop before finding + // any timer for the current thread, but at least won't report too + // long idle period. + timeStamp = timer->mTimeout; + break; + } + } + + std::pop_heap(mTimers.begin(), end, Entry::UniquePtrLessThan); + --end; + } + + while (end != mTimers.end()) { + ++end; + std::push_heap(mTimers.begin(), end, Entry::UniquePtrLessThan); + } + +#ifdef DEBUG + if (!mTimers.IsEmpty()) { + if (firstTimeStamp != mTimers[0]->Timeout()) { + TimeStamp now = TimeStamp::Now(); + printf_stderr( + "firstTimeStamp %f, mTimers[0]->Timeout() %f, " + "initialFirstTimer %p, current first %p\n", + (firstTimeStamp - now).ToMilliseconds(), + (mTimers[0]->Timeout() - now).ToMilliseconds(), initialFirstEntry, + mTimers[0].get()); + } + } + MOZ_ASSERT_IF(!mTimers.IsEmpty(), firstTimeStamp == mTimers[0]->Timeout()); +#endif + + return timeStamp; +} + +// This function must be called from within a lock +bool TimerThread::AddTimerInternal(nsTimerImpl* aTimer) { + mMonitor.AssertCurrentThreadOwns(); + if (mShutdown) { + return false; + } + + TimeStamp now = TimeStamp::Now(); + + LogTimerEvent::LogDispatch(aTimer); + + UniquePtr<Entry>* entry = mTimers.AppendElement( + MakeUnique<Entry>(now, aTimer->mTimeout, aTimer), mozilla::fallible); + if (!entry) { + return false; + } + + std::push_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan); + +#ifdef MOZ_TASK_TRACER + // Caller of AddTimer is the parent task of its timer event, so we store the + // TraceInfo here for later used. + aTimer->GetTLSTraceInfo(); +#endif + + return true; +} + +bool TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer) { + mMonitor.AssertCurrentThreadOwns(); + if (!aTimer || !aTimer->mHolder) { + return false; + } + aTimer->mHolder->Forget(aTimer); + return true; +} + +void TimerThread::RemoveLeadingCanceledTimersInternal() { + mMonitor.AssertCurrentThreadOwns(); + + // Move all canceled timers from the front of the list to + // the back of the list using std::pop_heap(). We do this + // without actually removing them from the list so we can + // modify the nsTArray in a single bulk operation. + auto sortedEnd = mTimers.end(); + while (sortedEnd != mTimers.begin() && !mTimers[0]->Value()) { + std::pop_heap(mTimers.begin(), sortedEnd, Entry::UniquePtrLessThan); + --sortedEnd; + } + + // If there were no canceled timers then we are done. + if (sortedEnd == mTimers.end()) { + return; + } + + // Finally, remove the canceled timers from the back of the + // nsTArray. + mTimers.RemoveLastElements(mTimers.end() - sortedEnd); +} + +void TimerThread::RemoveFirstTimerInternal() { + mMonitor.AssertCurrentThreadOwns(); + MOZ_ASSERT(!mTimers.IsEmpty()); + std::pop_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan); + mTimers.RemoveLastElement(); +} + +already_AddRefed<nsTimerImpl> TimerThread::PostTimerEvent( + already_AddRefed<nsTimerImpl> aTimerRef) { + mMonitor.AssertCurrentThreadOwns(); + + RefPtr<nsTimerImpl> timer(aTimerRef); + if (!timer->mEventTarget) { + NS_ERROR("Attempt to post timer event to NULL event target"); + return timer.forget(); + } + + // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. + + // Since we already addref'd 'timer', we don't need to addref here. + // We will release either in ~nsTimerEvent(), or pass the reference back to + // the caller. We need to copy the generation number from this timer into the + // event, so we can avoid firing a timer that was re-initialized after being + // canceled. + +#ifdef MOZ_TASK_TRACER + // During the dispatch of TimerEvent, we overwrite the current TraceInfo + // partially with the info saved in timer earlier, and restore it back by + // AutoSaveCurTraceInfo. + AutoSaveCurTraceInfo saveCurTraceInfo; + (timer->GetTracedTask()).SetTLSTraceInfo(); +#endif + + nsCOMPtr<nsIEventTarget> target = timer->mEventTarget; + + void* p = nsTimerEvent::operator new(sizeof(nsTimerEvent)); + if (!p) { + return timer.forget(); + } + RefPtr<nsTimerEvent> event = + ::new (KnownNotNull, p) nsTimerEvent(timer.forget()); + + nsresult rv; + { + // We release mMonitor around the Dispatch because if this timer is targeted + // at the TimerThread we'll deadlock. + MonitorAutoUnlock unlock(mMonitor); + rv = target->Dispatch(event, NS_DISPATCH_NORMAL); + } + + if (NS_FAILED(rv)) { + timer = event->ForgetTimer(); + RemoveTimerInternal(timer); + return timer.forget(); + } + + return nullptr; +} + +void TimerThread::DoBeforeSleep() { + // Mainthread + MonitorAutoLock lock(mMonitor); + mSleeping = true; +} + +// Note: wake may be notified without preceding sleep notification +void TimerThread::DoAfterSleep() { + // Mainthread + MonitorAutoLock lock(mMonitor); + mSleeping = false; + + // Wake up the timer thread to re-process the array to ensure the sleep delay + // is correct, and fire any expired timers (perhaps quite a few) + mNotified = true; + mMonitor.Notify(); +} + +NS_IMETHODIMP +TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) { + if (strcmp(aTopic, "sleep_notification") == 0 || + strcmp(aTopic, "suspend_process_notification") == 0) { + DoBeforeSleep(); + } else if (strcmp(aTopic, "wake_notification") == 0 || + strcmp(aTopic, "resume_process_notification") == 0) { + DoAfterSleep(); + } + + return NS_OK; +} + +uint32_t TimerThread::AllowedEarlyFiringMicroseconds() const { + return mAllowedEarlyFiringMicroseconds; +} diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h new file mode 100644 index 0000000000..1a843e4d5f --- /dev/null +++ b/xpcom/threads/TimerThread.h @@ -0,0 +1,115 @@ +/* -*- 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/. */ + +#ifndef TimerThread_h___ +#define TimerThread_h___ + +#include "nsIObserver.h" +#include "nsIRunnable.h" +#include "nsIThread.h" + +#include "nsTimerImpl.h" +#include "nsThreadUtils.h" + +#include "nsTArray.h" + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Monitor.h" +#include "mozilla/UniquePtr.h" + +#include <algorithm> + +namespace mozilla { +class TimeStamp; +} // namespace mozilla + +class TimerThread final : public mozilla::Runnable, public nsIObserver { + public: + typedef mozilla::Monitor Monitor; + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + + TimerThread(); + nsresult InitLocks(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + + nsresult Shutdown(); + + nsresult AddTimer(nsTimerImpl* aTimer); + nsresult RemoveTimer(nsTimerImpl* aTimer); + TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound); + + void DoBeforeSleep(); + void DoAfterSleep(); + + bool IsOnTimerThread() const { + return mThread->SerialEventTarget()->IsOnCurrentThread(); + } + + uint32_t AllowedEarlyFiringMicroseconds() const; + + private: + ~TimerThread(); + + bool mInitialized; + + // These internal helper methods must be called while mMonitor is held. + // AddTimerInternal returns false if the insertion failed. + bool AddTimerInternal(nsTimerImpl* aTimer); + bool RemoveTimerInternal(nsTimerImpl* aTimer); + void RemoveLeadingCanceledTimersInternal(); + void RemoveFirstTimerInternal(); + nsresult Init(); + + already_AddRefed<nsTimerImpl> PostTimerEvent( + already_AddRefed<nsTimerImpl> aTimerRef); + + nsCOMPtr<nsIThread> mThread; + Monitor mMonitor; + + bool mShutdown; + bool mWaiting; + bool mNotified; + bool mSleeping; + + class Entry final : public nsTimerImplHolder { + const TimeStamp mTimeout; + + public: + Entry(const TimeStamp& aMinTimeout, const TimeStamp& aTimeout, + nsTimerImpl* aTimerImpl) + : nsTimerImplHolder(aTimerImpl), + mTimeout(std::max(aMinTimeout, aTimeout)) {} + + nsTimerImpl* Value() const { return mTimerImpl; } + + already_AddRefed<nsTimerImpl> Take() { + if (mTimerImpl) { + mTimerImpl->SetHolder(nullptr); + } + return mTimerImpl.forget(); + } + + static bool UniquePtrLessThan(mozilla::UniquePtr<Entry>& aLeft, + mozilla::UniquePtr<Entry>& aRight) { + // This is reversed because std::push_heap() sorts the "largest" to + // the front of the heap. We want that to be the earliest timer. + return aRight->mTimeout < aLeft->mTimeout; + } + + TimeStamp Timeout() const { return mTimeout; } + }; + + nsTArray<mozilla::UniquePtr<Entry>> mTimers; + uint32_t mAllowedEarlyFiringMicroseconds; +}; + +#endif /* TimerThread_h___ */ diff --git a/xpcom/threads/components.conf b/xpcom/threads/components.conf new file mode 100644 index 0000000000..c8b5c4912a --- /dev/null +++ b/xpcom/threads/components.conf @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{03d68f92-9513-4e25-9be9-7cb239874172}', + 'contract_ids': ['@mozilla.org/process/environment;1'], + 'legacy_constructor': 'nsEnvironment::Create', + 'headers': ['/xpcom/threads/nsEnvironment.h'], + }, + { + 'cid': '{5ff24248-1dd2-11b2-8427-fbab44f29bc8}', + 'contract_ids': ['@mozilla.org/timer;1'], + 'legacy_constructor': 'nsTimer::XPCOMConstructor', + 'headers': ['/xpcom/threads/nsTimerImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS, + }, +] diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build new file mode 100644 index 0000000000..ed89ab0150 --- /dev/null +++ b/xpcom/threads/moz.build @@ -0,0 +1,129 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsIDirectTaskDispatcher.idl", + "nsIEnvironment.idl", + "nsIEventTarget.idl", + "nsIIdlePeriod.idl", + "nsINamed.idl", + "nsIProcess.idl", + "nsIRunnable.idl", + "nsISerialEventTarget.idl", + "nsISupportsPriority.idl", + "nsIThread.idl", + "nsIThreadInternal.idl", + "nsIThreadManager.idl", + "nsIThreadPool.idl", + "nsITimer.idl", +] + +XPIDL_MODULE = "xpcom_threads" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "MainThreadUtils.h", + "nsICancelableRunnable.h", + "nsIDiscardableRunnable.h", + "nsIIdleRunnable.h", + "nsMemoryPressure.h", + "nsProcess.h", + "nsProxyRelease.h", + "nsThread.h", + "nsThreadManager.h", + "nsThreadPool.h", + "nsThreadUtils.h", +] + +EXPORTS.mozilla += [ + "AbstractThread.h", + "BlockingResourceBase.h", + "CondVar.h", + "CPUUsageWatcher.h", + "DataMutex.h", + "DeadlockDetector.h", + "EventQueue.h", + "IdlePeriodState.h", + "IdleTaskRunner.h", + "InputTaskManager.h", + "LazyIdleThread.h", + "MainThreadIdlePeriod.h", + "Monitor.h", + "MozPromise.h", + "MozPromiseInlines.h", + "Mutex.h", + "PerformanceCounter.h", + "Queue.h", + "RecursiveMutex.h", + "ReentrantMonitor.h", + "RWLock.h", + "SchedulerGroup.h", + "SharedThreadPool.h", + "SpinEventLoopUntil.h", + "StateMirroring.h", + "StateWatching.h", + "SynchronizedEventQueue.h", + "SyncRunnable.h", + "TaskCategory.h", + "TaskController.h", + "TaskDispatcher.h", + "TaskQueue.h", + "ThreadBound.h", + "ThreadEventQueue.h", + "ThrottledEventQueue.h", +] + +SOURCES += [ + "IdleTaskRunner.cpp", + "ThreadDelay.cpp", +] + +UNIFIED_SOURCES += [ + "AbstractThread.cpp", + "BlockingResourceBase.cpp", + "CPUUsageWatcher.cpp", + "EventQueue.cpp", + "IdlePeriodState.cpp", + "InputEventStatistics.cpp", + "InputTaskManager.cpp", + "LazyIdleThread.cpp", + "MainThreadIdlePeriod.cpp", + "nsEnvironment.cpp", + "nsMemoryPressure.cpp", + "nsProcessCommon.cpp", + "nsProxyRelease.cpp", + "nsThread.cpp", + "nsThreadManager.cpp", + "nsThreadPool.cpp", + "nsThreadUtils.cpp", + "nsTimerImpl.cpp", + "PerformanceCounter.cpp", + "RecursiveMutex.cpp", + "RWLock.cpp", + "SchedulerGroup.cpp", + "SharedThreadPool.cpp", + "SynchronizedEventQueue.cpp", + "TaskController.cpp", + "TaskQueue.cpp", + "ThreadEventQueue.cpp", + "ThreadEventTarget.cpp", + "ThreadLocalVariables.cpp", + "ThrottledEventQueue.cpp", + "TimerThread.cpp", +] + +LOCAL_INCLUDES += [ + "../build", + "/caps", + "/tools/profiler", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/xpcom/threads/nsEnvironment.cpp b/xpcom/threads/nsEnvironment.cpp new file mode 100644 index 0000000000..99aae49bd5 --- /dev/null +++ b/xpcom/threads/nsEnvironment.cpp @@ -0,0 +1,152 @@ +/* -*- 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 "nsEnvironment.h" +#include "prenv.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsPromiseFlatString.h" +#include "nsDependentString.h" +#include "nsNativeCharsetUtils.h" +#include "mozilla/Printf.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsEnvironment, nsIEnvironment) + +nsresult nsEnvironment::Create(nsISupports* aOuter, REFNSIID aIID, + void** aResult) { + nsresult rv; + *aResult = nullptr; + + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + nsEnvironment* obj = new nsEnvironment(); + + rv = obj->QueryInterface(aIID, aResult); + if (NS_FAILED(rv)) { + delete obj; + } + return rv; +} + +nsEnvironment::~nsEnvironment() = default; + +NS_IMETHODIMP +nsEnvironment::Exists(const nsAString& aName, bool* aOutValue) { + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString nativeVal; +#if defined(XP_UNIX) + /* For Unix/Linux platforms we follow the Unix definition: + * An environment variable exists when |getenv()| returns a non-nullptr + * value. An environment variable does not exist when |getenv()| returns + * nullptr. + */ + const char* value = PR_GetEnv(nativeName.get()); + *aOutValue = value && *value; +#else + /* For non-Unix/Linux platforms we have to fall back to a + * "portable" definition (which is incorrect for Unix/Linux!!!!) + * which simply checks whether the string returned by |Get()| is empty + * or not. + */ + nsAutoString value; + Get(aName, value); + *aOutValue = !value.IsEmpty(); +#endif /* XP_UNIX */ + + return NS_OK; +} + +NS_IMETHODIMP +nsEnvironment::Get(const nsAString& aName, nsAString& aOutValue) { + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString nativeVal; + const char* value = PR_GetEnv(nativeName.get()); + if (value && *value) { + rv = NS_CopyNativeToUnicode(nsDependentCString(value), aOutValue); + } else { + aOutValue.Truncate(); + rv = NS_OK; + } + + return rv; +} + +/* Environment strings must have static duration; We're gonna leak all of this + * at shutdown: this is by design, caused how Unix/Linux implement environment + * vars. + */ + +typedef nsBaseHashtableET<nsCharPtrHashKey, char*> EnvEntryType; +typedef nsTHashtable<EnvEntryType> EnvHashType; + +static EnvHashType* gEnvHash = nullptr; + +static bool EnsureEnvHash() { + if (gEnvHash) { + return true; + } + + gEnvHash = new EnvHashType; + if (!gEnvHash) { + return false; + } + + return true; +} + +NS_IMETHODIMP +nsEnvironment::Set(const nsAString& aName, const nsAString& aValue) { + nsAutoCString nativeName; + nsAutoCString nativeVal; + + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = NS_CopyUnicodeToNative(aValue, nativeVal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MutexAutoLock lock(mLock); + + if (!EnsureEnvHash()) { + return NS_ERROR_UNEXPECTED; + } + + EnvEntryType* entry = gEnvHash->PutEntry(nativeName.get()); + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + + SmprintfPointer newData = + mozilla::Smprintf("%s=%s", nativeName.get(), nativeVal.get()); + if (!newData) { + return NS_ERROR_OUT_OF_MEMORY; + } + + PR_SetEnv(newData.get()); + if (entry->GetData()) { + free(entry->GetData()); + } + entry->SetData(newData.release()); + return NS_OK; +} diff --git a/xpcom/threads/nsEnvironment.h b/xpcom/threads/nsEnvironment.h new file mode 100644 index 0000000000..acc9200bac --- /dev/null +++ b/xpcom/threads/nsEnvironment.h @@ -0,0 +1,36 @@ +/* -*- 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/. */ + +#ifndef nsEnvironment_h__ +#define nsEnvironment_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsIEnvironment.h" + +#define NS_ENVIRONMENT_CID \ + { \ + 0X3D68F92UL, 0X9513, 0X4E25, { \ + 0X9B, 0XE9, 0X7C, 0XB2, 0X39, 0X87, 0X41, 0X72 \ + } \ + } +#define NS_ENVIRONMENT_CONTRACTID "@mozilla.org/process/environment;1" + +class nsEnvironment final : public nsIEnvironment { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIENVIRONMENT + + static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + private: + nsEnvironment() : mLock("nsEnvironment.mLock") {} + ~nsEnvironment(); + + mozilla::Mutex mLock; +}; + +#endif /* !nsEnvironment_h__ */ diff --git a/xpcom/threads/nsICancelableRunnable.h b/xpcom/threads/nsICancelableRunnable.h new file mode 100644 index 0000000000..7aa98c86b6 --- /dev/null +++ b/xpcom/threads/nsICancelableRunnable.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#ifndef nsICancelableRunnable_h__ +#define nsICancelableRunnable_h__ + +#include "nsISupports.h" + +#define NS_ICANCELABLERUNNABLE_IID \ + { \ + 0xde93dc4c, 0x5eea, 0x4eb7, { \ + 0xb6, 0xd1, 0xdb, 0xf1, 0xe0, 0xce, 0xf6, 0x5c \ + } \ + } + +class nsICancelableRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANCELABLERUNNABLE_IID) + + /* + * Cancels a pending task, so that calling run() on the task is a no-op. + * Calling cancel after the task execution has begun will be a no-op. + * Calling this method twice is considered an error. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that the runnable has already been canceled. + */ + virtual nsresult Cancel() = 0; + + protected: + nsICancelableRunnable() = default; + virtual ~nsICancelableRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICancelableRunnable, NS_ICANCELABLERUNNABLE_IID) + +#endif // nsICancelableRunnable_h__ diff --git a/xpcom/threads/nsIDirectTaskDispatcher.idl b/xpcom/threads/nsIDirectTaskDispatcher.idl new file mode 100644 index 0000000000..2729ddb7dc --- /dev/null +++ b/xpcom/threads/nsIDirectTaskDispatcher.idl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIRunnable.idl" + +%{C++ +#include "mozilla/AlreadyAddRefed.h" +%} + +native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>); + +/* + * The primary use of this interface is to allow any nsISerialEventTarget to + * provide Direct Task dispatching which is similar (but not identical to) the + * microtask semantics of JS promises. + * New direct task may be dispatched when a current direct task is running. In + * which case they will be run in FIFO order. + */ +[builtinclass, uuid(e05bf0fe-94b7-4e28-8462-a8368da9c136)] +interface nsIDirectTaskDispatcher : nsISupports +{ + /** + * Dispatch an event for the nsISerialEventTarget, using the direct task + * queue. + * + * This function must be called from the same nsISerialEventTarget + * implementing direct task dispatching. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * + */ + [noscript] void dispatchDirectTask(in alreadyAddRefed_nsIRunnable event); + + /** + * Synchronously run any pending direct tasks queued. + */ + [noscript] void drainDirectTasks(); + + /** + * Returns true if any direct tasks are pending. + */ + [noscript] bool haveDirectTasks(); + + %{C++ + // Infallible version of the above. Will assert that it is successful. + bool HaveDirectTasks() { + bool value = false; + MOZ_ALWAYS_SUCCEEDS(HaveDirectTasks(&value)); + return value; + } + %} + +}; diff --git a/xpcom/threads/nsIDiscardableRunnable.h b/xpcom/threads/nsIDiscardableRunnable.h new file mode 100644 index 0000000000..873b1f5d93 --- /dev/null +++ b/xpcom/threads/nsIDiscardableRunnable.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#ifndef XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ +#define XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ + +#include "nsISupports.h" + +/** + * An interface implemented by nsIRunnable tasks for which nsIRunnable::Run() + * might not be called. + */ +#define NS_IDISCARDABLERUNNABLE_IID \ + { \ + 0xde93dc4c, 0x755c, 0x4cdc, { \ + 0x96, 0x76, 0x35, 0xc6, 0x48, 0x81, 0x59, 0x78 \ + } \ + } + +class NS_NO_VTABLE nsIDiscardableRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDISCARDABLERUNNABLE_IID) + + /** + * Called exactly once on a queued task only if nsIRunnable::Run() is not + * called. + */ + virtual void OnDiscard() = 0; + + protected: + nsIDiscardableRunnable() = default; + virtual ~nsIDiscardableRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIDiscardableRunnable, + NS_IDISCARDABLERUNNABLE_IID) + +#endif // XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ diff --git a/xpcom/threads/nsIEnvironment.idl b/xpcom/threads/nsIEnvironment.idl new file mode 100644 index 0000000000..aad03191d7 --- /dev/null +++ b/xpcom/threads/nsIEnvironment.idl @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * Scriptable access to the current process environment. + * + */ +[scriptable, uuid(101d5941-d820-4e85-a266-9a3469940807)] +interface nsIEnvironment : nsISupports +{ + /** + * Set the value of an environment variable. + * + * @param aName the variable name to set. + * @param aValue the value to set. + */ + void set(in AString aName, in AString aValue); + + /** + * Get the value of an environment variable. + * + * @param aName the variable name to retrieve. + * @return returns the value of the env variable. An empty string + * will be returned when the env variable does not exist or + * when the value itself is an empty string - please use + * |exists()| to probe whether the env variable exists + * or not. + */ + AString get(in AString aName); + + /** + * Check the existence of an environment variable. + * This method checks whether an environment variable is present in + * the environment or not. + * + * - For Unix/Linux platforms we follow the Unix definition: + * An environment variable exists when |getenv()| returns a non-NULL value. + * An environment variable does not exist when |getenv()| returns NULL. + * - For non-Unix/Linux platforms we have to fall back to a + * "portable" definition (which is incorrect for Unix/Linux!!!!) + * which simply checks whether the string returned by |Get()| is empty + * or not. + * + * @param aName the variable name to probe. + * @return if the variable has been set, the value returned is + * PR_TRUE. If the variable was not defined in the + * environment PR_FALSE will be returned. + */ + boolean exists(in AString aName); +}; + diff --git a/xpcom/threads/nsIEventTarget.idl b/xpcom/threads/nsIEventTarget.idl new file mode 100644 index 0000000000..c0d7bc2272 --- /dev/null +++ b/xpcom/threads/nsIEventTarget.idl @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" +#include "nsIRunnable.idl" +%{C++ +#include "nsCOMPtr.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" +%} + +native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>); + +[builtinclass, scriptable, uuid(a03b8b63-af8b-4164-b0e5-c41e8b2b7cfa)] +interface nsIEventTarget : nsISupports +{ + /* until we can get rid of all uses, keep the non-alreadyAddRefed<> version */ +%{C++ + nsresult Dispatch(nsIRunnable* aEvent, uint32_t aFlags) { + return Dispatch(nsCOMPtr<nsIRunnable>(aEvent).forget(), aFlags); + } +%} + + /** + * This flag specifies the default mode of event dispatch, whereby the event + * is simply queued for later processing. When this flag is specified, + * dispatch returns immediately after the event is queued. + */ + const unsigned long DISPATCH_NORMAL = 0; + + /** + * This flag specifies the synchronous mode of event dispatch, in which the + * dispatch method does not return until the event has been processed. + * + * NOTE: passing this flag to dispatch may have the side-effect of causing + * other events on the current thread to be processed while waiting for the + * given event to be processed. + */ + const unsigned long DISPATCH_SYNC = 1; + + /** + * This flag specifies that the dispatch is occurring from a running event + * that was dispatched to the same event target, and that event is about to + * finish. + * + * A thread pool can use this as an optimization hint to not spin up + * another thread, since the current thread is about to become idle. + * + * These events are always async. + */ + const unsigned long DISPATCH_AT_END = 2; + + /** + * This flag specifies that the dispatched event may block the thread on + * which it executes, usually by doing some sort of I/O. This information + * may be used by the event target to execute the job on a thread + * specifically dedicated to doing I/O, leaving other threads available for + * CPU-intensive work. + */ + const unsigned long DISPATCH_EVENT_MAY_BLOCK = 4; + + /** + * IsOnCurrentThread() should return true if events dispatched to this target + * can possibly run on the current thread, and false otherwise. In the case + * of an nsIEventTarget for a thread pool, it should return true on all + * threads in the pool. In the case of a non-thread nsIEventTarget such as + * ThrottledEventQueue, it should return true on the thread where events are + * expected to be processed, even if no events from the queue are actually + * being processed right now. + * + * When called on an nsISerialEventTarget, IsOnCurrentThread can be used to + * ensure that no other thread has "ownership" of the event target. As such, + * it's useful for asserting that an object is only used on a particular + * thread. IsOnCurrentThread can't guarantee that the current event has been + * dispatched through a particular event target. + * + * The infallible version of IsOnCurrentThread() is optimized to avoid a + * virtual call for non-thread event targets. Thread targets should set + * mThread to their virtual PRThread. Non-thread targets should leave + * mThread null and implement IsOnCurrentThreadInfallible() to + * return the correct answer. + * + * The fallible version of IsOnCurrentThread may return errors, such as during + * shutdown. If it does not return an error, it should return the same result + * as the infallible version. The infallible method should return the correct + * result regardless of whether the fallible method returns an error. + */ + %{C++ +public: + // Infallible. Defined in nsThreadUtils.cpp. Delegates to + // IsOnCurrentThreadInfallible when mThread is null. + bool IsOnCurrentThread(); + +protected: + mozilla::Atomic<PRThread*> mThread; + + nsIEventTarget() : mThread(nullptr) {} + %} + // Note that this method is protected. We define it through IDL, rather than + // in a %{C++ block, to ensure that the correct method indices are recorded + // for XPConnect purposes. + [noscript,notxpcom] boolean isOnCurrentThreadInfallible(); + %{C++ +public: + %} + + // Fallible version of IsOnCurrentThread. + boolean isOnCurrentThread(); + + /** + * Dispatch an event to this event target. This function may be called from + * any thread, and it may be called re-entrantly. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * NOTE that the event will be leaked if it fails to dispatch. + * @param flags + * The flags modifying event dispatch. The flags are described in detail + * below. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [noscript, binaryname(Dispatch)] void dispatchFromC(in alreadyAddRefed_nsIRunnable event, + [default(DISPATCH_NORMAL)] in unsigned long flags); + /** + * Version of Dispatch to expose to JS, which doesn't require an alreadyAddRefed<> + * (it will be converted to that internally) + * + * @param event + * The (raw) event to dispatch. + * @param flags + * The flags modifying event dispatch. The flags are described in detail + * below. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [binaryname(DispatchFromScript)] void dispatch(in nsIRunnable event, in unsigned long flags); + /** + * Dispatch an event to this event target, but do not run it before delay + * milliseconds have passed. This function may be called from any thread. + * + * @param event + * The alreadyAddrefed<> event to dispatch. + * @param delay + * The delay (in ms) before running the event. If event does not rise to + * the top of the event queue before the delay has passed, it will be set + * aside to execute once the delay has passed. Otherwise, it will be + * executed immediately. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched, or + * that delay is zero. + */ + [noscript] void delayedDispatch(in alreadyAddRefed_nsIRunnable event, in unsigned long delay); +}; + +%{C++ +// convenient aliases: +#define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL +#define NS_DISPATCH_SYNC nsIEventTarget::DISPATCH_SYNC +#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END +#define NS_DISPATCH_EVENT_MAY_BLOCK nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK + +// Convenient NS_DECL variant that includes some C++-only methods. +#define NS_DECL_NSIEVENTTARGET_FULL \ + NS_DECL_NSIEVENTTARGET \ + /* Avoid hiding these methods */ \ + using nsIEventTarget::Dispatch; \ + using nsIEventTarget::IsOnCurrentThread; +%} diff --git a/xpcom/threads/nsIIdlePeriod.idl b/xpcom/threads/nsIIdlePeriod.idl new file mode 100644 index 0000000000..aa72b21711 --- /dev/null +++ b/xpcom/threads/nsIIdlePeriod.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +%{C++ +namespace mozilla { +class TimeStamp; +} +%} + +native TimeStamp(mozilla::TimeStamp); + +/** + * An instance implementing nsIIdlePeriod is used by an associated + * nsIThread to estimate when it is likely that it will receive an + * event. + */ +[builtinclass, uuid(21dd35a2-eae9-4bd8-b470-0dfa35a0e3b9)] +interface nsIIdlePeriod : nsISupports +{ + /** + * Return an estimate of a point in time in the future when we + * think that the associated thread will become busy. Should + * return TimeStamp() (i.e. the null time) or a time less than + * TimeStamp::Now() if the thread is currently busy or will become + * busy very soon. + */ + TimeStamp getIdlePeriodHint(); +}; diff --git a/xpcom/threads/nsIIdleRunnable.h b/xpcom/threads/nsIIdleRunnable.h new file mode 100644 index 0000000000..7fe6149154 --- /dev/null +++ b/xpcom/threads/nsIIdleRunnable.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef nsIIdleRunnable_h__ +#define nsIIdleRunnable_h__ + +#include "nsISupports.h" +#include "mozilla/TimeStamp.h" + +#define NS_IIDLERUNNABLE_IID \ + { \ + 0x688be92e, 0x7ade, 0x4fdc, { \ + 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c \ + } \ + } + +class nsIEventTarget; + +/** + * A task interface for tasks that can schedule their work to happen + * in increments bounded by a deadline. + */ +class nsIIdleRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIDLERUNNABLE_IID) + + /** + * Notify the task of a point in time in the future when the task + * should stop executing. + */ + virtual void SetDeadline(mozilla::TimeStamp aDeadline){}; + virtual void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT_UNREACHABLE( + "The nsIIdleRunnable instance does not support " + "idle dispatch with timeout!"); + }; + + protected: + nsIIdleRunnable() = default; + virtual ~nsIIdleRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIdleRunnable, NS_IIDLERUNNABLE_IID) + +#endif // nsIIdleRunnable_h__ diff --git a/xpcom/threads/nsINamed.idl b/xpcom/threads/nsINamed.idl new file mode 100644 index 0000000000..cdb7d88f30 --- /dev/null +++ b/xpcom/threads/nsINamed.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * Represents an object with a name, such as a runnable or a timer. + */ + +[scriptable, uuid(0c5fe7de-7e83-4d0d-a8a6-4a6518b9a7b3)] +interface nsINamed : nsISupports +{ + /* + * A string describing the purpose of the runnable/timer/whatever. Useful + * for debugging. This attribute is read-only, but you can change it to a + * compile-time string literal with setName. + * + * WARNING: This attribute will be included in telemetry, so it should + * never contain privacy sensitive information. + */ + readonly attribute AUTF8String name; +}; diff --git a/xpcom/threads/nsIProcess.idl b/xpcom/threads/nsIProcess.idl new file mode 100644 index 0000000000..88c1d75d44 --- /dev/null +++ b/xpcom/threads/nsIProcess.idl @@ -0,0 +1,112 @@ +/* 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 "nsISupports.idl" + +interface nsIFile; +interface nsIObserver; + +[scriptable, uuid(609610de-9954-4a63-8a7c-346350a86403)] +interface nsIProcess : nsISupports +{ + /** + * Initialises the process with an executable to be run. Call the run method + * to run the executable. + * @param executable The executable to run. + */ + void init(in nsIFile executable); + + /** + * Kills the running process. After exiting the process will either have + * been killed or a failure will have been returned. + */ + void kill(); + + /** + * Executes the file this object was initialized with + * @param blocking Whether to wait until the process terminates before + returning or not. + * @param args An array of arguments to pass to the process in the + * native character set. + * @param count The length of the args array. + */ + void run(in boolean blocking, [array, size_is(count)] in string args, + in unsigned long count); + + /** + * Executes the file this object was initialized with optionally calling + * an observer after the process has finished running. + * @param args An array of arguments to pass to the process in the + * native character set. + * @param count The length of the args array. + * @param observer An observer to notify when the process has completed. It + * will receive this process instance as the subject and + * "process-finished" or "process-failed" as the topic. The + * observer will be notified on the main thread. + * @param holdWeak Whether to use a weak reference to hold the observer. + */ + void runAsync([array, size_is(count)] in string args, in unsigned long count, + [optional] in nsIObserver observer, [optional] in boolean holdWeak); + + /** + * Executes the file this object was initialized with + * @param blocking Whether to wait until the process terminates before + returning or not. + * @param args An array of arguments to pass to the process in UTF-16 + * @param count The length of the args array. + */ + void runw(in boolean blocking, [array, size_is(count)] in wstring args, + in unsigned long count); + + /** + * Executes the file this object was initialized with optionally calling + * an observer after the process has finished running. + * @param args An array of arguments to pass to the process in UTF-16 + * @param count The length of the args array. + * @param observer An observer to notify when the process has completed. It + * will receive this process instance as the subject and + * "process-finished" or "process-failed" as the topic. The + * observer will be notified on the main thread. + * @param holdWeak Whether to use a weak reference to hold the observer. + */ + void runwAsync([array, size_is(count)] in wstring args, + in unsigned long count, + [optional] in nsIObserver observer, [optional] in boolean holdWeak); + + /** + * When set to true the process will not open a new window when started and + * will run hidden from the user. This currently affects only the Windows + * platform. + */ + attribute boolean startHidden; + + /** + * When set to true the process will be launched directly without using the + * shell. This currently affects only the Windows platform. + */ + attribute boolean noShell; + + /** + * The process identifier of the currently running process. This will only + * be available after the process has started and may not be available on + * some platforms. + */ + readonly attribute unsigned long pid; + + /** + * The exit value of the process. This is only valid after the process has + * exited. + */ + readonly attribute long exitValue; + + /** + * Returns whether the process is currently running or not. + */ + readonly attribute boolean isRunning; +}; + +%{C++ + +#define NS_PROCESS_CONTRACTID "@mozilla.org/process/util;1" +%} diff --git a/xpcom/threads/nsIRunnable.idl b/xpcom/threads/nsIRunnable.idl new file mode 100644 index 0000000000..f7c7f4ffe6 --- /dev/null +++ b/xpcom/threads/nsIRunnable.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * Represents a task which can be dispatched to a thread for execution. + */ + +[scriptable, function, uuid(4a2abaf0-6886-11d3-9382-00104ba0fd40)] +interface nsIRunnable : nsISupports +{ + /** + * The function implementing the task to be run. + */ + void run(); +}; + +[scriptable, uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)] +interface nsIRunnablePriority : nsISupports +{ + const unsigned short PRIORITY_IDLE = 0; + const unsigned short PRIORITY_DEFERRED_TIMERS = 1; + // INPUT_LOW isn't supposed to be used directly. + // const unsigned short PRIORITY_INPUT_LOW = 2; + const unsigned short PRIORITY_NORMAL = 3; + const unsigned short PRIORITY_MEDIUMHIGH = 4; + const unsigned short PRIORITY_INPUT_HIGH = 5; + const unsigned short PRIORITY_HIGH = 6; + readonly attribute unsigned long priority; +}; + +[builtinclass, uuid(3114c36c-a482-4c6e-9523-1dcfc6f605b9)] +interface nsIRunnableIPCMessageType : nsISupports +{ + readonly attribute unsigned long type; +}; diff --git a/xpcom/threads/nsISerialEventTarget.idl b/xpcom/threads/nsISerialEventTarget.idl new file mode 100644 index 0000000000..9cf7768b37 --- /dev/null +++ b/xpcom/threads/nsISerialEventTarget.idl @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIEventTarget.idl" + +/** + * A serial event target is an event dispatching interface like + * nsIEventTarget. Runnables dispatched to an nsISerialEventTarget are required + * to execute serially. That is, two different runnables dispatched to the + * target should never be allowed to execute simultaneously. One exception to + * this rule is nested event loops. If a runnable spins a nested event loop, + * causing another runnable dispatched to the target to run, the target may + * still be considered "serial". + * + * Examples: + * - nsIThread is a serial event target. + * - Thread pools are not serial event targets. + * - However, one can "convert" a thread pool into an nsISerialEventTarget + * by putting a TaskQueue in front of it. + */ +[builtinclass, scriptable, uuid(9f982380-24b4-49f3-88f6-45e2952036c7)] +interface nsISerialEventTarget : nsIEventTarget +{ +}; diff --git a/xpcom/threads/nsISupportsPriority.idl b/xpcom/threads/nsISupportsPriority.idl new file mode 100644 index 0000000000..d0b8b9a3dd --- /dev/null +++ b/xpcom/threads/nsISupportsPriority.idl @@ -0,0 +1,45 @@ +/* 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 "nsISupports.idl" + +/** + * This interface exposes the general notion of a scheduled object with a + * integral priority value. Following UNIX conventions, smaller (and possibly + * negative) values have higher priority. + * + * This interface does not strictly define what happens when the priority of an + * object is changed. An implementation of this interface is free to define + * the side-effects of changing the priority of an object. In some cases, + * changing the priority of an object may be disallowed (resulting in an + * exception being thrown) or may simply be ignored. + */ +[scriptable, uuid(aa578b44-abd5-4c19-8b14-36d4de6fdc36)] +interface nsISupportsPriority : nsISupports +{ + /** + * Typical priority values. + */ + const long PRIORITY_HIGHEST = -20; + const long PRIORITY_HIGH = -10; + const long PRIORITY_NORMAL = 0; + const long PRIORITY_LOW = 10; + const long PRIORITY_LOWEST = 20; + + /** + * This attribute may be modified to change the priority of this object. The + * implementation of this interface is free to truncate a given priority + * value to whatever limits are appropriate. Typically, this attribute is + * initialized to PRIORITY_NORMAL, but implementations may choose to assign a + * different initial value. + */ + attribute long priority; + + /** + * This method adjusts the priority attribute by a given delta. It helps + * reduce the amount of coding required to increment or decrement the value + * of the priority attribute. + */ + void adjustPriority(in long delta); +}; diff --git a/xpcom/threads/nsIThread.idl b/xpcom/threads/nsIThread.idl new file mode 100644 index 0000000000..ef3fd3258d --- /dev/null +++ b/xpcom/threads/nsIThread.idl @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISerialEventTarget.idl" + +%{C++ +#include "mozilla/AlreadyAddRefed.h" +namespace mozilla { +class TimeStamp; +class TimeDurationValueCalculator; +template <typename T> class BaseTimeDuration; +typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration; +enum class EventQueuePriority; +} +%} + +[ptr] native PRThread(PRThread); +native EventQueuePriority(mozilla::EventQueuePriority); + +native nsIEventTargetPtr(nsIEventTarget*); +native nsISerialEventTargetPtr(nsISerialEventTarget*); +native TimeStamp(mozilla::TimeStamp); +native TimeDuration(mozilla::TimeDuration); + +/** + * This interface provides a high-level abstraction for an operating system + * thread. + * + * Threads have a built-in event queue, and a thread is an event target that + * can receive nsIRunnable objects (events) to be processed on the thread. + * + * See nsIThreadManager for the API used to create and locate threads. + */ +[builtinclass, scriptable, uuid(5801d193-29d1-4964-a6b7-70eb697ddf2b)] +interface nsIThread : nsISerialEventTarget +{ + /** + * @returns + * The NSPR thread object corresponding to this nsIThread. + */ + [noscript] readonly attribute PRThread PRThread; + + /** + * @returns + * Whether or not this thread may call into JS. Used in the profiler + * to avoid some unnecessary locking. + */ + [noscript] attribute boolean CanInvokeJS; + + + /** + * Shutdown the thread. This method prevents further dispatch of events to + * the thread, and it causes any pending events to run to completion before + * the thread joins (see PR_JoinThread) with the current thread. During this + * method call, events for the current thread may be processed. + * + * This method MAY NOT be executed from the thread itself. Instead, it is + * meant to be executed from another thread (usually the thread that created + * this thread or the main application thread). When this function returns, + * the thread will be shutdown, and it will no longer be possible to dispatch + * events to the thread. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * the current thread, that this thread was not created with a call to + * nsIThreadManager::NewThread, or if this method was called more than once + * on the thread object. + */ + void shutdown(); + + /** + * This method may be called to determine if there are any events ready to be + * processed. It may only be called when this thread is the current thread. + * + * Because events may be added to this thread by another thread, a "false" + * result does not mean that this thread has no pending events. It only + * means that there were no pending events when this method was called. + * + * @returns + * A boolean value that if "true" indicates that this thread has one or + * more pending events. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * not the current thread. + */ + boolean hasPendingEvents(); + + /** + * Similar to above, but checks only possible high priority queue. + */ + boolean hasPendingHighPriorityEvents(); + + /** + * Process the next event. If there are no pending events, then this method + * may wait -- depending on the value of the mayWait parameter -- until an + * event is dispatched to this thread. This method is re-entrant but may + * only be called if this thread is the current thread. + * + * @param mayWait + * A boolean parameter that if "true" indicates that the method may block + * the calling thread to wait for a pending event. + * + * @returns + * A boolean value that if "true" indicates that an event was processed. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * not the current thread. + */ + boolean processNextEvent(in boolean mayWait); + + /** + * Shutdown the thread asynchronously. This method immediately prevents + * further dispatch of events to the thread, and it causes any pending events + * to run to completion before this thread joins with the current thread. + * + * UNLIKE shutdown() this does not process events on the current thread. + * Instead it merely ensures that the current thread continues running until + * this thread has shut down. + * + * This method MAY NOT be executed from the thread itself. Instead, it is + * meant to be executed from another thread (usually the thread that created + * this thread or the main application thread). When this function returns, + * the thread will continue running until it exhausts its event queue. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * the current thread, that this thread was not created with a call to + * nsIThreadManager::NewThread, or if this method was called more than once + * on the thread object. + */ + void asyncShutdown(); + + /** + * Dispatch an event to a specified queue for the thread. This function + * may be called from any thread, and it may be called re-entrantly. + * Most users should use the NS_Dispatch*() functions in nsThreadUtils instead + * of calling this directly. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * NOTE that the event will be leaked if it fails to dispatch. + * @param queue + * Which event priority queue this should be added to + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [noscript] void dispatchToQueue(in alreadyAddRefed_nsIRunnable event, + in EventQueuePriority queue); + + /** + * Use this attribute to dispatch runnables to the thread. Eventually, the + * eventTarget attribute will be the only way to dispatch events to a + * thread--nsIThread will no longer inherit from nsIEventTarget. + */ + readonly attribute nsIEventTarget eventTarget; + + /** + * A fast C++ getter for the eventTarget. + */ + [noscript,notxpcom] nsIEventTargetPtr EventTarget(); + + /** + * A fast C++ getter for the eventTarget. It asserts that the thread's event + * target is an nsISerialEventTarget and then returns it. + */ + [noscript,notxpcom] nsISerialEventTargetPtr SerialEventTarget(); + + /** + * This is set to the end of the last 50+ms event that was executed on + * this thread (for MainThread only). Otherwise returns a null TimeStamp. + */ + [noscript] readonly attribute TimeStamp lastLongTaskEnd; + [noscript] readonly attribute TimeStamp lastLongNonIdleTaskEnd; + + /** + * Get information on the timing of the currently-running event. + * + * @param delay + * The amount of time the current running event in the specified queue waited + * to run. Will return TimeDuration() if the queue is empty or has not run any + * new events since event delay monitoring started. NOTE: delay will be + * TimeDuration() if this thread uses a PrioritizedEventQueue (i.e. MainThread) + * and the event priority is below Input. + * @param start + * The time the currently running event began to run, or TimeStamp() if no + * event is running. + */ + [noscript] void getRunningEventDelay(out TimeDuration delay, out TimeStamp start); + + /** + * Set information on the timing of the currently-running event. + * Overrides the values returned by getRunningEventDelay + * + * @param delay + * Delay the running event spent in queues, or TimeDuration() if + * there's no running event. + * @param start + * The time the currently running event began to run, or TimeStamp() if no + * event is running. + */ + [noscript] void setRunningEventDelay(in TimeDuration delay, in TimeStamp start); + + [noscript] void setNameForWakeupTelemetry(in ACString name); +}; diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl new file mode 100644 index 0000000000..fca9e711b0 --- /dev/null +++ b/xpcom/threads/nsIThreadInternal.idl @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIThread.idl" + +interface nsIRunnable; +interface nsIThreadObserver; + +/** + * The XPCOM thread object implements this interface, which allows a consumer + * to observe dispatch activity on the thread. + */ +[builtinclass, scriptable, uuid(a3a72e5f-71d9-4add-8f30-59a78fb6d5eb)] +interface nsIThreadInternal : nsIThread +{ + /** + * Get/set the current thread observer (may be null). This attribute may be + * read from any thread, but must only be set on the thread corresponding to + * this thread object. The observer will be released on the thread + * corresponding to this thread object after all other events have been + * processed during a call to Shutdown. + */ + attribute nsIThreadObserver observer; + + /** + * Add an observer that will *only* receive onProcessNextEvent, + * beforeProcessNextEvent. and afterProcessNextEvent callbacks. Always called + * on the target thread, and the implementation does not have to be + * threadsafe. Order of callbacks is not guaranteed (i.e. + * afterProcessNextEvent may be called first depending on whether or not the + * observer is added in a nested loop). Holds a strong ref. + */ + void addObserver(in nsIThreadObserver observer); + + /** + * Remove an observer added via the addObserver call. Once removed the + * observer will never be called again by the thread. + */ + void removeObserver(in nsIThreadObserver observer); +}; + +/** + * This interface provides the observer with hooks to implement a layered + * event queue. For example, it is possible to overlay processing events + * for a GUI toolkit on top of the events for a thread: + * + * var NativeQueue; + * Observer = { + * onDispatchedEvent() { + * NativeQueue.signal(); + * } + * onProcessNextEvent(thread, mayWait) { + * if (NativeQueue.hasNextEvent()) + * NativeQueue.processNextEvent(); + * while (mayWait && !thread.hasPendingEvent()) { + * NativeQueue.wait(); + * NativeQueue.processNextEvent(); + * } + * } + * }; + * + * NOTE: The implementation of this interface must be threadsafe. + * + * NOTE: It is valid to change the thread's observer during a call to an + * observer method. + * + * NOTE: Will be split into two interfaces soon: one for onProcessNextEvent and + * afterProcessNextEvent, then another that inherits the first and adds + * onDispatchedEvent. + */ +[uuid(cc8da053-1776-44c2-9199-b5a629d0a19d)] +interface nsIThreadObserver : nsISupports +{ + /** + * This method is called after an event has been dispatched to the thread. + * This method may be called from any thread. + */ + void onDispatchedEvent(); + + /** + * This method is called when nsIThread::ProcessNextEvent is called. It does + * not guarantee that an event is actually going to be processed. This method + * is only called on the target thread. + * + * @param thread + * The thread being asked to process another event. + * @param mayWait + * Indicates whether or not the method is allowed to block the calling + * thread. For example, this parameter is false during thread shutdown. + */ + void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait); + + /** + * This method is called (from nsIThread::ProcessNextEvent) after an event + * is processed. It does not guarantee that an event was actually processed + * (depends on the value of |eventWasProcessed|. This method is only called + * on the target thread. DO NOT EVER RUN SCRIPT FROM THIS CALLBACK!!! + * + * @param thread + * The thread that processed another event. + * @param eventWasProcessed + * Indicates whether an event was actually processed. May be false if the + * |mayWait| flag was false when calling nsIThread::ProcessNextEvent(). + */ + void afterProcessNextEvent(in nsIThreadInternal thread, + in bool eventWasProcessed); +}; diff --git a/xpcom/threads/nsIThreadManager.idl b/xpcom/threads/nsIThreadManager.idl new file mode 100644 index 0000000000..86f7db6e3c --- /dev/null +++ b/xpcom/threads/nsIThreadManager.idl @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" + +[ptr] native PRThread(PRThread); + +interface nsIEventTarget; +interface nsIRunnable; +interface nsIThread; + +[scriptable, function, uuid(039a227d-0cb7-44a5-a8f9-dbb7071979f2)] +interface nsINestedEventLoopCondition : nsISupports +{ + /** + * Returns true if the current nested event loop should stop spinning. + */ + bool isDone(); +}; + +/** + * An interface for creating and locating nsIThread instances. + */ +[scriptable, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)] +interface nsIThreadManager : nsISupports +{ + /** + * Default number of bytes reserved for a thread's stack, if no stack size + * is specified in newThread(). + * + * Defaults can be a little overzealous for many platforms. + * + * On Linux and OS X, for instance, the default thread stack size is whatever + * getrlimit(RLIMIT_STACK) returns, which is often set at 8MB. Or, on Linux, + * if the stack size is unlimited, we fall back to 2MB. This causes particular + * problems on Linux, which allocates 2MB huge VM pages, and will often + * immediately allocate them for any stacks which are 2MB or larger. + * + * The default on Windows is 1MB, which is a little more reasonable. But the + * vast majority of our threads don't need anywhere near that much space. + * + * ASan, TSan and non-opt builds, however, often need a bit more, so give + * them the platform default. + */ +%{C++ +#if defined(MOZ_ASAN) || defined(MOZ_TSAN) || !defined(__OPTIMIZE__) + static constexpr uint32_t DEFAULT_STACK_SIZE = 0; +#else + static constexpr uint32_t DEFAULT_STACK_SIZE = 256 * 1024; +#endif + + static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE; +%} + + /** + * Create a new thread (a global, user PRThread). + * + * @param creationFlags + * Reserved for future use. Pass 0. + * @param stackSize + * Number of bytes to reserve for the thread's stack. 0 means use platform + * default. + * + * @returns + * The newly created nsIThread object. + */ + nsIThread newThread(in unsigned long creationFlags, [optional] in unsigned long stackSize); + + /** + * Create a new thread (a global, user PRThread) with the specified name. + * + * @param name + * The name of the thread. Passing an empty name is equivalent to + * calling newThread(0, stackSize), i.e. the thread will not be named. + * @param stackSize + * Number of bytes to reserve for the thread's stack. 0 means use platform + * default. + * + * @returns + * The newly created nsIThread object. + */ + [noscript] nsIThread newNamedThread(in ACString name, [optional] in unsigned long stackSize); + + /** + * Get the main thread. + */ + readonly attribute nsIThread mainThread; + + /** + * Get the current thread. If the calling thread does not already have a + * nsIThread associated with it, then a new nsIThread will be created and + * associated with the current PRThread. + */ + readonly attribute nsIThread currentThread; + + /** + * This queues a runnable to the main thread. It's a shortcut for JS callers + * to be used instead of + * .mainThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL); + * or + * .currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL); + * C++ callers should instead use NS_DispatchToMainThread. + */ + [optional_argc] + void dispatchToMainThread(in nsIRunnable event, [optional] in uint32_t priority); + + /** + * This queues a runnable to the main thread's idle queue. + * + * @param event + * The event to dispatch. + * @param timeout + * The time in milliseconds until this event should be moved from the idle + * queue to the regular queue if it hasn't been executed by then. If not + * passed or a zero value is specified, the event will never be moved to + * the regular queue. + */ + void idleDispatchToMainThread(in nsIRunnable event, + [optional] in uint32_t timeout); + + /** + * Enter a nested event loop on the current thread, waiting on, and + * processing events until condition.isDone() returns true. + * + * If condition.isDone() throws, this function will throw as well. + * + * C++ code should not use this function, instead preferring + * mozilla::SpinEventLoopUntil. + */ + void spinEventLoopUntil(in nsINestedEventLoopCondition condition); + + /** + * Similar to the previous method, but the spinning of the event loop + * terminates when the shutting down starts. + * + * C++ code should not use this function, instead preferring + * mozilla::SpinEventLoopUntil. + */ + void spinEventLoopUntilOrShutdown(in nsINestedEventLoopCondition condition); + + /** + * Spin the current thread's event loop until there are no more pending + * events. This could be done with spinEventLoopUntil, but that would + * require access to the current thread from JavaScript, which we are + * moving away from. + */ + void spinEventLoopUntilEmpty(); + + /** + * Return the EventTarget for the main thread. + */ + readonly attribute nsIEventTarget mainThreadEventTarget; +}; diff --git a/xpcom/threads/nsIThreadPool.idl b/xpcom/threads/nsIThreadPool.idl new file mode 100644 index 0000000000..70922b7adf --- /dev/null +++ b/xpcom/threads/nsIThreadPool.idl @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIEventTarget.idl" + +[uuid(ef194cab-3f86-4b61-b132-e5e96a79e5d1)] +interface nsIThreadPoolListener : nsISupports +{ + /** + * Called when a new thread is created by the thread pool. The notification + * happens on the newly-created thread. + */ + void onThreadCreated(); + + /** + * Called when a thread is about to be destroyed by the thread pool. The + * notification happens on the thread that is about to be destroyed. + */ + void onThreadShuttingDown(); +}; + +/** + * An interface to a thread pool. A thread pool creates a limited number of + * anonymous (unnamed) worker threads. An event dispatched to the thread pool + * will be run on the next available worker thread. + */ +[uuid(76ce99c9-8e43-489a-9789-f27cc4424965)] +interface nsIThreadPool : nsIEventTarget +{ + /** + * Shutdown the thread pool. This method may not be executed from any thread + * in the thread pool. Instead, it is meant to be executed from another + * thread (usually the thread that created this thread pool). When this + * function returns, the thread pool and all of its threads will be shutdown, + * and it will no longer be possible to dispatch tasks to the thread pool. + * + * As a side effect, events on the current thread will be processed. + */ + void shutdown(); + + /** + * Shutdown the thread pool, but only wait for aTimeoutMs. After the timeout + * expires, any threads that have not shutdown yet are leaked and will not + * block shutdown. + * + * This method should only be used at during shutdown to cleanup threads that + * made blocking calls to code outside our control, and can't be safely + * terminated. We choose to leak them intentionally to avoid a shutdown hang. + */ + [noscript] void shutdownWithTimeout(in long aTimeoutMs); + + /** + * Get/set the maximum number of threads allowed at one time in this pool. + */ + attribute unsigned long threadLimit; + + /** + * Get/set the maximum number of idle threads kept alive. + */ + attribute unsigned long idleThreadLimit; + + /** + * Get/set the amount of time in milliseconds before an idle thread is + * destroyed. + */ + attribute unsigned long idleThreadTimeout; + + /** + * If set to true the idle timeout will be calculated as idleThreadTimeout + * divideded by the number of idle threads at the moment. This may help + * save memory allocations but still keep reasonable amount of idle threads. + * Default is false, use |idleThreadTimeout| for all threads. + */ + attribute boolean idleThreadTimeoutRegressive; + + /** + * Get/set the number of bytes reserved for the stack of all threads in + * the pool. By default this is nsIThreadManager::DEFAULT_STACK_SIZE. + */ + attribute unsigned long threadStackSize; + + /** + * An optional listener that will be notified when a thread is created or + * destroyed in the course of the thread pool's operation. + * + * A listener will only receive notifications about threads created after the + * listener is set so it is recommended that the consumer set the listener + * before dispatching the first event. A listener that receives an + * onThreadCreated() notification is guaranteed to always receive the + * corresponding onThreadShuttingDown() notification. + * + * The thread pool takes ownership of the listener and releases it when the + * shutdown() method is called. Threads created after the listener is set will + * also take ownership of the listener so that the listener will be kept alive + * long enough to receive the guaranteed onThreadShuttingDown() notification. + */ + attribute nsIThreadPoolListener listener; + + /** + * Set the label for threads in the pool. All threads will be named + * "<aName> #<n>", where <n> is a serial number. + */ + void setName(in ACString aName); +}; diff --git a/xpcom/threads/nsITimer.idl b/xpcom/threads/nsITimer.idl new file mode 100644 index 0000000000..96ea2943a5 --- /dev/null +++ b/xpcom/threads/nsITimer.idl @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "nsISupports.idl" + +interface nsIObserver; +interface nsIEventTarget; + +%{C++ +#include "mozilla/MemoryReporting.h" +#include "mozilla/TimeStamp.h" + +/** + * The signature of the timer callback function passed to initWithFuncCallback. + * This is the function that will get called when the timer expires if the + * timer is initialized via initWithFuncCallback. + * + * @param aTimer the timer which has expired + * @param aClosure opaque parameter passed to initWithFuncCallback + */ +class nsITimer; +typedef void (*nsTimerCallbackFunc) (nsITimer *aTimer, void *aClosure); + +/** + * The signature of the timer name callback function passed to + * initWithNameableFuncCallback. + * This is the function that will get called when timer profiling is enabled + * via the "TimerFirings" log module. + * + * @param aTimer the timer which has expired + * @param aAnonymize whether the name should avoid including privacy sensitive info + * @param aClosure opaque parameter passed to initWithFuncCallback + * @param aBuf a buffer in which to put the name + * @param aLen the length of the buffer + */ +typedef void (*nsTimerNameCallbackFunc) (nsITimer *aTimer, + bool aAnonymize, + void *aClosure, + char *aBuf, size_t aLen); +%} + +native nsTimerCallbackFunc(nsTimerCallbackFunc); +native nsTimerNameCallbackFunc(nsTimerNameCallbackFunc); +[ref] native TimeDuration(mozilla::TimeDuration); + +/** + * The callback interface for timers. + */ +interface nsITimer; + +[function, scriptable, uuid(a796816d-7d47-4348-9ab8-c7aeb3216a7d)] +interface nsITimerCallback : nsISupports +{ + /** + * @param aTimer the timer which has expired + */ + void notify(in nsITimer timer); +}; + +%{C++ +// Two timer deadlines must differ by less than half the PRIntervalTime domain. +#define DELAY_INTERVAL_LIMIT PR_BIT(8 * sizeof(PRIntervalTime) - 1) +%} + +/** + * nsITimer instances must be initialized by calling one of the "init" methods + * documented below. You may also re-initialize (using one of the init() + * methods) an existing instance to avoid the overhead of destroying and + * creating a timer. It is not necessary to cancel the timer in that case. + * + * By default a timer will fire on the thread that created it. Set the .target + * attribute to fire on a different thread. Once you have set a timer's .target + * and called one of its init functions, any further interactions with the timer + * (calling cancel(), changing member fields, etc) should only be done by the + * target thread, or races may occur with bad results like timers firing after + * they've been canceled, and/or not firing after re-initiatization. + */ +[scriptable, builtinclass, uuid(3de4b105-363c-482c-a409-baac83a01bfc)] +interface nsITimer : nsISupports +{ + /* Timer types */ + + /** + * Type of a timer that fires once only. + */ + const short TYPE_ONE_SHOT = 0; + + /** + * After firing, a TYPE_REPEATING_SLACK timer is stopped and not restarted + * until its callback completes. Specified timer period will be at least + * the time between when processing for last firing the callback completes + * and when the next firing occurs. + * + * This is the preferable repeating type for most situations. + */ + const short TYPE_REPEATING_SLACK = 1; + + /** + * TYPE_REPEATING_PRECISE is just a synonym for + * TYPE_REPEATING_PRECISE_CAN_SKIP. They used to be distinct, but the old + * TYPE_REPEATING_PRECISE kind was similar to TYPE_REPEATING_PRECISE_CAN_SKIP + * while also being less useful. So the distinction was removed. + */ + const short TYPE_REPEATING_PRECISE = 2; + + /** + * A TYPE_REPEATING_PRECISE_CAN_SKIP repeating timer aims to have constant + * period between firings. The processing time for each timer callback + * should not influence the timer period. However this timer type + * guarantees that it will not queue up new events to fire the callback + * until the previous callback event finishes firing. If the callback + * takes a long time, then the next callback will be scheduled immediately + * afterward, but only once. This is the only non-slack timer available. + */ + const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; + + /** + * Same as TYPE_REPEATING_SLACK with the exception that idle events + * won't yield to timers with this type. Use this when you want an + * idle callback to be scheduled to run even though this timer is + * about to fire. + */ + const short TYPE_REPEATING_SLACK_LOW_PRIORITY = 4; + + /** + * Same as TYPE_ONE_SHOT with the exception that idle events won't + * yield to timers with this type. Use this when you want an idle + * callback to be scheduled to run even though this timer is about + * to fire. + */ + const short TYPE_ONE_SHOT_LOW_PRIORITY = 5; + + /** + * Initialize a timer that will fire after the said delay. + * A user must keep a reference to this timer till it is + * is no longer needed or has been cancelled. + * + * @param aObserver the callback object that observes the + * ``timer-callback'' topic with the subject being + * the timer itself when the timer fires: + * + * observe(nsISupports aSubject, => nsITimer + * string aTopic, => ``timer-callback'' + * wstring data => null + * + * @param aDelayInMs delay in milliseconds for timer to fire + * @param aType timer type per TYPE* consts defined above + */ + void init(in nsIObserver aObserver, in unsigned long aDelayInMs, + in unsigned long aType); + + + /** + * Initialize a timer to fire after the given millisecond interval. + * This version takes a callback object. + * + * @param aFunc nsITimerCallback interface to call when timer expires + * @param aDelayInMs The millisecond interval + * @param aType Timer type per TYPE* consts defined above + */ + void initWithCallback(in nsITimerCallback aCallback, + in unsigned long aDelayInMs, + in unsigned long aType); + + /** + * Initialize a timer to fire after the high resolution TimeDuration. + * This version takes a callback object. + * + * @param aFunc nsITimerCallback interface to call when timer expires + * @param aDelay The high resolution interval + * @param aType Timer type per TYPE* consts defined above + */ + [noscript] void InitHighResolutionWithCallback(in nsITimerCallback aCallback, + [const] in TimeDuration aDelay, + in unsigned long aType); + + /** + * Cancel the timer. This method works on all types, not just on repeating + * timers -- you might want to cancel a TYPE_ONE_SHOT timer, and even reuse + * it by re-initializing it (to avoid object destruction and creation costs + * by conserving one timer instance). + */ + void cancel(); + + /** + * Like initWithFuncCallback, but also takes a name for the timer; the name + * will be used when timer profiling is enabled via the "TimerFirings" log + * module. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The millisecond interval + * @param aType Timer type per TYPE* consts defined above + * @param aName The timer's name + */ + [noscript] void initWithNamedFuncCallback(in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType, + in string aName); + + /** + * Like initWithNamedFuncCallback, but instead of a timer name it takes a + * callback that will provide a name when the timer fires. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The millisecond interval + * @param aType Timer type per TYPE* consts defined above + * @param aNameCallback The callback function + */ + [noscript] void initWithNameableFuncCallback( + in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType, + in nsTimerNameCallbackFunc aNameCallback); + + /** + * The millisecond delay of the timeout. + * + * NOTE: Re-setting the delay on a one-shot timer that has already fired + * doesn't restart the timer. Call one of the init() methods to restart + * a one-shot timer. + */ + attribute unsigned long delay; + + /** + * The timer type - one of the above TYPE_* constants. + */ + attribute unsigned long type; + + /** + * The opaque pointer pass to initWithFuncCallback. + */ + [noscript] readonly attribute voidPtr closure; + + /** + * The nsITimerCallback object passed to initWithCallback. + */ + readonly attribute nsITimerCallback callback; + + /** + * The nsIEventTarget where the callback will be dispatched. Note that this + * target may only be set before the call to one of the init methods above. + * + * By default the target is the thread that created the timer. + */ + attribute nsIEventTarget target; + + /** + * The number of microseconds this nsITimer implementation can possibly + * fire early. + */ + [noscript] readonly attribute unsigned long allowedEarlyFiringMicroseconds; + +%{C++ + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; +%} +}; + +%{C++ +#include "nsCOMPtr.h" + +already_AddRefed<nsITimer> NS_NewTimer(); + +already_AddRefed<nsITimer> NS_NewTimer(nsIEventTarget* aTarget); + +nsresult +NS_NewTimerWithObserver(nsITimer** aTimer, + nsIObserver* aObserver, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithObserver(nsIObserver* aObserver, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithCallback(nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithCallback(nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + nsTimerNameCallbackFunc aNameCallback, + nsIEventTarget* aTarget = nullptr); +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> +NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + nsTimerNameCallbackFunc aNameCallback, + nsIEventTarget* aTarget = nullptr); + +#define NS_TIMER_CONTRACTID "@mozilla.org/timer;1" +#define NS_TIMER_CALLBACK_TOPIC "timer-callback" +%} diff --git a/xpcom/threads/nsMemoryPressure.cpp b/xpcom/threads/nsMemoryPressure.cpp new file mode 100644 index 0000000000..4f0ee3a332 --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.cpp @@ -0,0 +1,49 @@ +/* -*- 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 "nsMemoryPressure.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" + +#include "nsThreadUtils.h" + +using namespace mozilla; + +static Atomic<int32_t, Relaxed> sMemoryPressurePending; +static_assert(MemPressure_None == 0, + "Bad static initialization with the default constructor."); + +MemoryPressureState NS_GetPendingMemoryPressure() { + int32_t value = sMemoryPressurePending.exchange(MemPressure_None); + return MemoryPressureState(value); +} + +void NS_DispatchEventualMemoryPressure(MemoryPressureState aState) { + /* + * A new memory pressure event erases an ongoing (or stop of) memory pressure, + * but an existing "new" memory pressure event takes precedence over a new + * "ongoing" or "stop" memory pressure event. + */ + switch (aState) { + case MemPressure_None: + sMemoryPressurePending = MemPressure_None; + break; + case MemPressure_New: + sMemoryPressurePending = MemPressure_New; + break; + case MemPressure_Ongoing: + case MemPressure_Stopping: + sMemoryPressurePending.compareExchange(MemPressure_None, aState); + break; + } +} + +nsresult NS_DispatchMemoryPressure(MemoryPressureState aState) { + NS_DispatchEventualMemoryPressure(aState); + nsCOMPtr<nsIRunnable> event = + new Runnable("NS_DispatchEventualMemoryPressure"); + return NS_DispatchToMainThread(event); +} diff --git a/xpcom/threads/nsMemoryPressure.h b/xpcom/threads/nsMemoryPressure.h new file mode 100644 index 0000000000..eadde20bb3 --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.h @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +#ifndef nsMemoryPressure_h__ +#define nsMemoryPressure_h__ + +#include "nscore.h" + +enum MemoryPressureState { + /* + * No memory pressure. + */ + MemPressure_None = 0, + + /* + * New memory pressure deteced. + * + * On a new memory pressure, we stop everything to start cleaning + * aggresively the memory used, in order to free as much memory as + * possible. + */ + MemPressure_New, + + /* + * Repeated memory pressure. + * + * A repeated memory pressure implies to clean softly recent allocations. + * It is supposed to happen after a new memory pressure which already + * cleaned aggressivley. So there is no need to damage the reactivity of + * Gecko by stopping the world again. + * + * In case of conflict with an new memory pressue, the new memory pressure + * takes precedence over an ongoing memory pressure. The reason being + * that if no events are processed between 2 notifications (new followed + * by ongoing, or ongoing followed by a new) we want to be as aggresive as + * possible on the clean-up of the memory. After all, we are trying to + * keep Gecko alive as long as possible. + */ + MemPressure_Ongoing, + + /* + * Memory pressure stopped. + * + * We're no longer under acute memory pressure, so we might want to have a + * chance of (cautiously) re-enabling some things we previously turned off. + * As above, an already enqueued new memory pressure event takes precedence. + * The priority ordering between concurrent attempts to queue both stopped + * and ongoing memory pressure is currently not defined. + */ + MemPressure_Stopping +}; + +/** + * Return and erase the latest state of the memory pressure event set by any of + * the corresponding dispatch functions. + * + * This is called when processing events on the main thread to check whether to + * fire a memory pressure notification. + */ +MemoryPressureState NS_GetPendingMemoryPressure(); + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event, but if there are no events pending in + * the main thread's event queue, the memory pressure event would not be + * dispatched until one is enqueued. It is infallible and does not allocate + * any memory. + * + * You may call this function from any thread. + */ +void NS_DispatchEventualMemoryPressure(MemoryPressureState aState); + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event. We wake up the main thread by adding a + * dummy event to its event loop, so, unlike with + * NS_DispatchEventualMemoryPressure, this memory-pressure event is always + * fired relatively quickly, even if the event loop is otherwise empty. + * + * You may call this function from any thread. + */ +nsresult NS_DispatchMemoryPressure(MemoryPressureState aState); + +#endif // nsMemoryPressure_h__ diff --git a/xpcom/threads/nsProcess.h b/xpcom/threads/nsProcess.h new file mode 100644 index 0000000000..c7cd0a3d05 --- /dev/null +++ b/xpcom/threads/nsProcess.h @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +#ifndef _nsPROCESSWIN_H_ +#define _nsPROCESSWIN_H_ + +#if defined(XP_WIN) +# define PROCESSMODEL_WINAPI +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsIProcess.h" +#include "nsIObserver.h" +#include "nsMaybeWeakPtr.h" +#include "nsString.h" +#ifndef XP_UNIX +# include "prproces.h" +#endif +#if defined(PROCESSMODEL_WINAPI) +# include <windows.h> +# include <shellapi.h> +#endif + +#define NS_PROCESS_CID \ + { \ + 0x7b4eeb20, 0xd781, 0x11d4, { \ + 0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca \ + } \ + } + +class nsIFile; + +class nsProcess final : public nsIProcess, public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROCESS + NS_DECL_NSIOBSERVER + + nsProcess(); + + private: + ~nsProcess(); + static void Monitor(void* aArg); + void ProcessComplete(); + nsresult CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); + nsresult CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); + // The 'args' array is null-terminated. + nsresult RunProcess(bool aBlocking, char** aArgs, nsIObserver* aObserver, + bool aHoldWeak, bool aArgsUTF8); + + PRThread* mThread; + mozilla::Mutex mLock; + bool mShutdown; + bool mBlocking; + bool mStartHidden; + bool mNoShell; + + nsCOMPtr<nsIFile> mExecutable; + nsString mTargetPath; + int32_t mPid; + nsMaybeWeakPtr<nsIObserver> mObserver; + + // These members are modified by multiple threads, any accesses should be + // protected with mLock. + int32_t mExitValue; +#if defined(PROCESSMODEL_WINAPI) + HANDLE mProcess; +#elif !defined(XP_UNIX) + PRProcess* mProcess; +#endif +}; + +#endif diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp new file mode 100644 index 0000000000..10da15fd7e --- /dev/null +++ b/xpcom/threads/nsProcessCommon.cpp @@ -0,0 +1,566 @@ +/* -*- 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/. */ + +/***************************************************************************** + * + * nsProcess is used to execute new processes and specify if you want to + * wait (blocking) or continue (non-blocking). + * + ***************************************************************************** + */ + +#include "mozilla/ArrayUtils.h" + +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsMemory.h" +#include "nsProcess.h" +#include "prio.h" +#include "prenv.h" +#include "nsCRT.h" +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include "mozilla/Services.h" +#include "GeckoProfiler.h" + +#include <stdlib.h> + +#if defined(PROCESSMODEL_WINAPI) +# include "nsString.h" +# include "nsLiteralString.h" +# include "nsReadableUtils.h" +# include "mozilla/AssembleCmdLine.h" +# include "mozilla/UniquePtrExtensions.h" +#else +# ifdef XP_MACOSX +# include <crt_externs.h> +# include <spawn.h> +# endif +# ifdef XP_UNIX +# ifndef XP_MACOSX +# include "base/process_util.h" +# endif +# include <sys/wait.h> +# include <sys/errno.h> +# endif +# include <sys/types.h> +# include <signal.h> +#endif + +using namespace mozilla; + +//-------------------------------------------------------------------// +// nsIProcess implementation +//-------------------------------------------------------------------// +NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, nsIObserver) + +// Constructor +nsProcess::nsProcess() + : mThread(nullptr), + mLock("nsProcess.mLock"), + mShutdown(false), + mBlocking(false), + mStartHidden(false), + mNoShell(false), + mPid(-1), + mExitValue(-1) +#if !defined(XP_UNIX) + , + mProcess(nullptr) +#endif +{ +} + +// Destructor +nsProcess::~nsProcess() = default; + +NS_IMETHODIMP +nsProcess::Init(nsIFile* aExecutable) { + if (mExecutable) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (NS_WARN_IF(!aExecutable)) { + return NS_ERROR_INVALID_ARG; + } + bool isFile; + + // First make sure the file exists + nsresult rv = aExecutable->IsFile(&isFile); + if (NS_FAILED(rv)) { + return rv; + } + if (!isFile) { + return NS_ERROR_FAILURE; + } + + // Store the nsIFile in mExecutable + mExecutable = aExecutable; + // Get the path because it is needed by the NSPR process creation +#ifdef XP_WIN + rv = mExecutable->GetTarget(mTargetPath); + if (NS_FAILED(rv) || mTargetPath.IsEmpty()) +#endif + rv = mExecutable->GetPath(mTargetPath); + + return rv; +} + +void nsProcess::Monitor(void* aArg) { + RefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(aArg)); + +#ifdef MOZ_GECKO_PROFILER + Maybe<AutoProfilerRegisterThread> registerThread; + if (!process->mBlocking) { + registerThread.emplace("RunProcess"); + } +#endif + if (!process->mBlocking) { + NS_SetCurrentThreadName("RunProcess"); + } + +#if defined(PROCESSMODEL_WINAPI) + DWORD dwRetVal; + unsigned long exitCode = -1; + + dwRetVal = WaitForSingleObject(process->mProcess, INFINITE); + if (dwRetVal != WAIT_FAILED) { + if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE) { + exitCode = -1; + } + } + + // Lock in case Kill or GetExitCode are called during this + { + MutexAutoLock lock(process->mLock); + CloseHandle(process->mProcess); + process->mProcess = nullptr; + process->mExitValue = exitCode; + if (process->mShutdown) { + return; + } + } +#else +# ifdef XP_UNIX + int exitCode = -1; + int status = 0; + pid_t result; + do { + result = waitpid(process->mPid, &status, 0); + } while (result == -1 && errno == EINTR); + if (result == process->mPid) { + if (WIFEXITED(status)) { + exitCode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + exitCode = 256; // match NSPR's signal exit status + } + } +# else + int32_t exitCode = -1; + if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) { + exitCode = -1; + } +# endif + + // Lock in case Kill or GetExitCode are called during this + { + MutexAutoLock lock(process->mLock); +# if !defined(XP_UNIX) + process->mProcess = nullptr; +# endif + process->mExitValue = exitCode; + if (process->mShutdown) { + return; + } + } +#endif + + // If we ran a background thread for the monitor then notify on the main + // thread + if (NS_IsMainThread()) { + process->ProcessComplete(); + } else { + NS_DispatchToMainThread(NewRunnableMethod( + "nsProcess::ProcessComplete", process, &nsProcess::ProcessComplete)); + } +} + +void nsProcess::ProcessComplete() { + if (mThread) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + PR_JoinThread(mThread); + mThread = nullptr; + } + + const char* topic; + if (mExitValue != 0) { + topic = "process-failed"; + } else { + topic = "process-finished"; + } + + mPid = -1; + nsCOMPtr<nsIObserver> observer = mObserver.GetValue(); + mObserver = nullptr; + + if (observer) { + observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr); + } +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount) { + return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false); +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunAsync(const char** aArgs, uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak) { + return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak); +} + +nsresult nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, + nsIObserver* aObserver, + bool aHoldWeak) { + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2)); + + my_argv[0] = ToNewUTF8String(mTargetPath); + + for (uint32_t i = 0; i < aCount; ++i) { + my_argv[i + 1] = const_cast<char*>(aArgs[i]); + } + + my_argv[aCount + 1] = nullptr; + + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false); + + free(my_argv[0]); + free(my_argv); + return rv; +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount) { + return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false); +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunwAsync(const char16_t** aArgs, uint32_t aCount, + nsIObserver* aObserver, bool aHoldWeak) { + return CopyArgsAndRunProcessw(false, aArgs, aCount, aObserver, aHoldWeak); +} + +nsresult nsProcess::CopyArgsAndRunProcessw(bool aBlocking, + const char16_t** aArgs, + uint32_t aCount, + nsIObserver* aObserver, + bool aHoldWeak) { + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2)); + + my_argv[0] = ToNewUTF8String(mTargetPath); + + for (uint32_t i = 0; i < aCount; i++) { + my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i])); + } + + my_argv[aCount + 1] = nullptr; + + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true); + + for (uint32_t i = 0; i <= aCount; ++i) { + free(my_argv[i]); + } + free(my_argv); + return rv; +} + +nsresult nsProcess::RunProcess(bool aBlocking, char** aMyArgv, + nsIObserver* aObserver, bool aHoldWeak, + bool aArgsUTF8) { + NS_WARNING_ASSERTION(!XRE_IsContentProcess(), + "No launching of new processes in the content process"); + + if (NS_WARN_IF(!mExecutable)) { + return NS_ERROR_NOT_INITIALIZED; + } + if (NS_WARN_IF(mThread)) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aObserver) { + if (aHoldWeak) { + nsresult rv = NS_OK; + mObserver = do_GetWeakReference(aObserver, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mObserver = aObserver; + } + } + + mExitValue = -1; + mPid = -1; + +#if defined(PROCESSMODEL_WINAPI) + BOOL retVal; + UniqueFreePtr<wchar_t> cmdLine; + + // |aMyArgv| is null-terminated and always starts with the program path. If + // the second slot is non-null then arguments are being passed. + if (aMyArgv[1] || mNoShell) { + // Pass the executable path as argv[0] to the launched program when calling + // CreateProcess(). + char** argv = mNoShell ? aMyArgv : aMyArgv + 1; + + wchar_t* assembledCmdLine = nullptr; + if (assembleCmdLine(argv, &assembledCmdLine, + aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + cmdLine.reset(assembledCmdLine); + } + + // The program name in aMyArgv[0] is always UTF-8 + NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]); + + if (mNoShell) { + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + startupInfo.dwFlags = STARTF_USESHOWWINDOW; + startupInfo.wShowWindow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL; + + PROCESS_INFORMATION processInfo; + retVal = CreateProcess(/* lpApplicationName = */ wideFile.get(), + /* lpCommandLine */ cmdLine.get(), + /* lpProcessAttributes = */ NULL, + /* lpThreadAttributes = */ NULL, + /* bInheritHandles = */ FALSE, + /* dwCreationFlags = */ 0, + /* lpEnvironment = */ NULL, + /* lpCurrentDirectory = */ NULL, + /* lpStartupInfo = */ &startupInfo, + /* lpProcessInformation */ &processInfo); + + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + CloseHandle(processInfo.hThread); + + mProcess = processInfo.hProcess; + } else { + SHELLEXECUTEINFOW sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + sinfo.hwnd = nullptr; + sinfo.lpFile = wideFile.get(); + sinfo.nShow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL; + + /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows + * from appearing. This makes behavior the same on all platforms. The flag + * will not have any effect on non-console applications. + */ + sinfo.fMask = + SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS; + + if (cmdLine) { + sinfo.lpParameters = cmdLine.get(); + } + + retVal = ShellExecuteExW(&sinfo); + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + mProcess = sinfo.hProcess; + } + + mPid = GetProcessId(mProcess); +#elif defined(XP_MACOSX) + // Note: |aMyArgv| is already null-terminated as required by posix_spawnp. + pid_t newPid = 0; + int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, nullptr, aMyArgv, + *_NSGetEnviron()); + mPid = static_cast<int32_t>(newPid); + + if (result != 0) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_UNIX) + base::LaunchOptions options; + std::vector<std::string> argvVec; + for (char** arg = aMyArgv; *arg != nullptr; ++arg) { + argvVec.push_back(*arg); + } + pid_t newPid; + if (base::LaunchApp(argvVec, options, &newPid)) { + static_assert(sizeof(pid_t) <= sizeof(int32_t), + "mPid is large enough to hold a pid"); + mPid = static_cast<int32_t>(newPid); + } else { + return NS_ERROR_FAILURE; + } +#else + mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr); + if (!mProcess) { + return NS_ERROR_FAILURE; + } + struct MYProcess { + uint32_t pid; + }; + MYProcess* ptrProc = (MYProcess*)mProcess; + mPid = ptrProc->pid; +#endif + + NS_ADDREF_THIS(); + mBlocking = aBlocking; + if (aBlocking) { + Monitor(this); + if (mExitValue < 0) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } else { + mThread = + PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + if (!mThread) { + NS_RELEASE_THIS(); + return NS_ERROR_FAILURE; + } + + // It isn't a failure if we just can't watch for shutdown + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, "xpcom-shutdown", false); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetIsRunning(bool* aIsRunning) { + if (mThread) { + *aIsRunning = true; + } else { + *aIsRunning = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetStartHidden(bool* aStartHidden) { + *aStartHidden = mStartHidden; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::SetStartHidden(bool aStartHidden) { + mStartHidden = aStartHidden; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetNoShell(bool* aNoShell) { + *aNoShell = mNoShell; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::SetNoShell(bool aNoShell) { + mNoShell = aNoShell; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetPid(uint32_t* aPid) { + if (!mThread) { + return NS_ERROR_FAILURE; + } + if (mPid < 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + *aPid = mPid; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::Kill() { + if (!mThread) { + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mLock); +#if defined(PROCESSMODEL_WINAPI) + if (TerminateProcess(mProcess, 0) == 0) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_UNIX) + if (kill(mPid, SIGKILL) != 0) { + return NS_ERROR_FAILURE; + } +#else + if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) { + return NS_ERROR_FAILURE; + } +#endif + } + + // We must null out mThread if we want IsRunning to return false immediately + // after this call. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + PR_JoinThread(mThread); + mThread = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetExitValue(int32_t* aExitValue) { + MutexAutoLock lock(mLock); + + *aExitValue = mExitValue; + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + // Shutting down, drop all references + if (mThread) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + mThread = nullptr; + } + + mObserver = nullptr; + + MutexAutoLock lock(mLock); + mShutdown = true; + + return NS_OK; +} diff --git a/xpcom/threads/nsProxyRelease.cpp b/xpcom/threads/nsProxyRelease.cpp new file mode 100644 index 0000000000..b53fe4c022 --- /dev/null +++ b/xpcom/threads/nsProxyRelease.cpp @@ -0,0 +1,30 @@ +/* -*- 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 "nsProxyRelease.h" +#include "nsThreadUtils.h" + +namespace detail { + +/* static */ void ProxyReleaseChooser<true>::ProxyReleaseISupports( + const char* aName, nsIEventTarget* aTarget, nsISupports* aDoomed, + bool aAlwaysProxy) { + ::detail::ProxyRelease<nsISupports>(aName, aTarget, dont_AddRef(aDoomed), + aAlwaysProxy); +} + +} // namespace detail + +extern "C" { + +// This function uses C linkage because it's exposed to Rust to support the +// `ThreadPtrHolder` wrapper in the `moz_task` crate. +void NS_ProxyReleaseISupports(const char* aName, nsIEventTarget* aTarget, + nsISupports* aDoomed, bool aAlwaysProxy) { + NS_ProxyRelease(aName, aTarget, dont_AddRef(aDoomed), aAlwaysProxy); +} + +} // extern "C" diff --git a/xpcom/threads/nsProxyRelease.h b/xpcom/threads/nsProxyRelease.h new file mode 100644 index 0000000000..0c132d362b --- /dev/null +++ b/xpcom/threads/nsProxyRelease.h @@ -0,0 +1,384 @@ +/* -*- 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/. */ + +#ifndef nsProxyRelease_h__ +#define nsProxyRelease_h__ + +#include <utility> + +#include "MainThreadUtils.h" +#include "mozilla/Likely.h" +#include "mozilla/Unused.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsISerialEventTarget.h" +#include "nsIThread.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" + +#ifdef XPCOM_GLUE_AVOID_NSPR +# error NS_ProxyRelease implementation depends on NSPR. +#endif + +class nsIRunnable; + +namespace detail { + +template <typename T> +class ProxyReleaseEvent : public mozilla::CancelableRunnable { + public: + ProxyReleaseEvent(const char* aName, already_AddRefed<T> aDoomed) + : CancelableRunnable(aName), mDoomed(aDoomed.take()) {} + + NS_IMETHOD Run() override { + NS_IF_RELEASE(mDoomed); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + if (mName) { + aName.Append(nsPrintfCString("ProxyReleaseEvent for %s", mName)); + } else { + aName.AssignLiteral("ProxyReleaseEvent"); + } + return NS_OK; + } +#endif + + private: + T* MOZ_OWNING_REF mDoomed; +}; + +template <typename T> +void ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy) { + // Auto-managing release of the pointer. + RefPtr<T> doomed = aDoomed; + nsresult rv; + + if (!doomed || !aTarget) { + return; + } + + if (!aAlwaysProxy) { + bool onCurrentThread = false; + rv = aTarget->IsOnCurrentThread(&onCurrentThread); + if (NS_SUCCEEDED(rv) && onCurrentThread) { + return; + } + } + + nsCOMPtr<nsIRunnable> ev = new ProxyReleaseEvent<T>(aName, doomed.forget()); + + rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("failed to post proxy release event, leaking!"); + // It is better to leak the aDoomed object than risk crashing as + // a result of deleting it on the wrong thread. + } +} + +template <bool nsISupportsBased> +struct ProxyReleaseChooser { + template <typename T> + static void ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy) { + ::detail::ProxyRelease(aName, aTarget, std::move(aDoomed), aAlwaysProxy); + } +}; + +template <> +struct ProxyReleaseChooser<true> { + // We need an intermediate step for handling classes with ambiguous + // inheritance to nsISupports. + template <typename T> + static void ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy) { + ProxyReleaseISupports(aName, aTarget, ToSupports(aDoomed.take()), + aAlwaysProxy); + } + + static void ProxyReleaseISupports(const char* aName, nsIEventTarget* aTarget, + nsISupports* aDoomed, bool aAlwaysProxy); +}; + +} // namespace detail + +/** + * Ensures that the delete of a smart pointer occurs on the target thread. + * + * @param aName + * the labelling name of the runnable involved in the releasing. + * @param aTarget + * the target thread where the doomed object should be released. + * @param aDoomed + * the doomed object; the object to be released on the target thread. + * @param aAlwaysProxy + * normally, if NS_ProxyRelease is called on the target thread, then the + * doomed object will be released directly. However, if this parameter is + * true, then an event will always be posted to the target thread for + * asynchronous release. + */ +template <class T> +inline NS_HIDDEN_(void) + NS_ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed<T> aDoomed, bool aAlwaysProxy = false) { + ::detail::ProxyReleaseChooser< + std::is_base_of<nsISupports, T>::value>::ProxyRelease(aName, aTarget, + std::move(aDoomed), + aAlwaysProxy); +} + +/** + * Ensures that the delete of a smart pointer occurs on the main thread. + * + * @param aName + * the labelling name of the runnable involved in the releasing + * @param aDoomed + * the doomed object; the object to be released on the main thread. + * @param aAlwaysProxy + * normally, if NS_ReleaseOnMainThread is called on the main + * thread, then the doomed object will be released directly. However, if + * this parameter is true, then an event will always be posted to the + * main thread for asynchronous release. + */ +template <class T> +inline NS_HIDDEN_(void) + NS_ReleaseOnMainThread(const char* aName, already_AddRefed<T> aDoomed, + bool aAlwaysProxy = false) { + RefPtr<T> doomed = aDoomed; + if (!doomed) { + return; // Nothing to do. + } + + // NS_ProxyRelease treats a null event target as "the current thread". So a + // handle on the main thread is only necessary when we're not already on the + // main thread or the release must happen asynchronously. + nsCOMPtr<nsIEventTarget> target; + if (!NS_IsMainThread() || aAlwaysProxy) { + target = mozilla::GetMainThreadSerialEventTarget(); + + if (!target) { + MOZ_ASSERT_UNREACHABLE("Could not get main thread; leaking an object!"); + mozilla::Unused << doomed.forget().take(); + return; + } + } + + NS_ProxyRelease(aName, target, doomed.forget(), aAlwaysProxy); +} + +template <class T> +inline NS_HIDDEN_(void) NS_ReleaseOnMainThread(already_AddRefed<T> aDoomed, + bool aAlwaysProxy = false) { + NS_ReleaseOnMainThread("NS_ReleaseOnMainThread", std::move(aDoomed), + aAlwaysProxy); +} + +/** + * Class to safely handle main-thread-only pointers off the main thread. + * + * Classes like XPCWrappedJS are main-thread-only, which means that it is + * forbidden to call methods on instances of these classes off the main thread. + * For various reasons (see bug 771074), this restriction applies to + * AddRef/Release as well. + * + * This presents a problem for consumers that wish to hold a callback alive + * on non-main-thread code. A common example of this is the proxy callback + * pattern, where non-main-thread code holds a strong-reference to the callback + * object, and dispatches new Runnables (also with a strong reference) to the + * main thread in order to execute the callback. This involves several AddRef + * and Release calls on the other thread, which is verboten. + * + * The basic idea of this class is to introduce a layer of indirection. + * nsMainThreadPtrHolder is a threadsafe reference-counted class that internally + * maintains one strong reference to the main-thread-only object. It must be + * instantiated on the main thread (so that the AddRef of the underlying object + * happens on the main thread), but consumers may subsequently pass references + * to the holder anywhere they please. These references are meant to be opaque + * when accessed off-main-thread (assertions enforce this). + * + * The semantics of RefPtr<nsMainThreadPtrHolder<T>> would be cumbersome, so we + * also introduce nsMainThreadPtrHandle<T>, which is conceptually identical to + * the above (though it includes various convenience methods). The basic pattern + * is as follows. + * + * // On the main thread: + * nsCOMPtr<nsIFooCallback> callback = ...; + * nsMainThreadPtrHandle<nsIFooCallback> callbackHandle = + * new nsMainThreadPtrHolder<nsIFooCallback>(callback); + * // Pass callbackHandle to structs/classes that might be accessed on other + * // threads. + * + * All structs and classes that might be accessed on other threads should store + * an nsMainThreadPtrHandle<T> rather than an nsCOMPtr<T>. + */ +template <class T> +class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHolder final { + public: + // We can only acquire a pointer on the main thread. We want to fail fast for + // threading bugs, so by default we assert if our pointer is used or acquired + // off-main-thread. But some consumers need to use the same pointer for + // multiple classes, some of which are main-thread-only and some of which + // aren't. So we allow them to explicitly disable this strict checking. + nsMainThreadPtrHolder(const char* aName, T* aPtr, bool aStrict = true, + nsIEventTarget* aMainThreadEventTarget = nullptr) + : mRawPtr(aPtr), + mStrict(aStrict), + mMainThreadEventTarget(aMainThreadEventTarget) +#ifndef RELEASE_OR_BETA + , + mName(aName) +#endif + { + // We can only AddRef our pointer on the main thread, which means that the + // holder must be constructed on the main thread. + MOZ_ASSERT(!mStrict || NS_IsMainThread()); + NS_IF_ADDREF(mRawPtr); + } + nsMainThreadPtrHolder(const char* aName, already_AddRefed<T> aPtr, + bool aStrict = true, + nsIEventTarget* aMainThreadEventTarget = nullptr) + : mRawPtr(aPtr.take()), + mStrict(aStrict), + mMainThreadEventTarget(aMainThreadEventTarget) +#ifndef RELEASE_OR_BETA + , + mName(aName) +#endif + { + // Since we don't need to AddRef the pointer, this constructor is safe to + // call on any thread. + } + + // Copy constructor and operator= deleted. Once constructed, the holder is + // immutable. + T& operator=(nsMainThreadPtrHolder& aOther) = delete; + nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther) = delete; + + private: + // We can be released on any thread. + ~nsMainThreadPtrHolder() { + if (NS_IsMainThread()) { + NS_IF_RELEASE(mRawPtr); + } else if (mRawPtr) { + if (!mMainThreadEventTarget) { + mMainThreadEventTarget = do_GetMainThread(); + } + MOZ_ASSERT(mMainThreadEventTarget); + NS_ProxyRelease( +#ifdef RELEASE_OR_BETA + nullptr, +#else + mName, +#endif + mMainThreadEventTarget, dont_AddRef(mRawPtr)); + } + } + + public: + T* get() const { + // Nobody should be touching the raw pointer off-main-thread. + if (mStrict && MOZ_UNLIKELY(!NS_IsMainThread())) { + NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread"); + MOZ_CRASH(); + } + return mRawPtr; + } + + bool operator==(const nsMainThreadPtrHolder<T>& aOther) const { + return mRawPtr == aOther.mRawPtr; + } + bool operator!() const { return !mRawPtr; } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder<T>) + + private: + // Our wrapped pointer. + T* mRawPtr = nullptr; + + // Whether to strictly enforce thread invariants in this class. + bool mStrict = true; + + nsCOMPtr<nsIEventTarget> mMainThreadEventTarget; + +#ifndef RELEASE_OR_BETA + const char* mName = nullptr; +#endif +}; + +template <class T> +class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHandle { + public: + nsMainThreadPtrHandle() : mPtr(nullptr) {} + MOZ_IMPLICIT nsMainThreadPtrHandle(decltype(nullptr)) : mPtr(nullptr) {} + explicit nsMainThreadPtrHandle(nsMainThreadPtrHolder<T>* aHolder) + : mPtr(aHolder) {} + explicit nsMainThreadPtrHandle( + already_AddRefed<nsMainThreadPtrHolder<T>> aHolder) + : mPtr(aHolder) {} + nsMainThreadPtrHandle(const nsMainThreadPtrHandle& aOther) = default; + nsMainThreadPtrHandle(nsMainThreadPtrHandle&& aOther) = default; + nsMainThreadPtrHandle& operator=(const nsMainThreadPtrHandle& aOther) = + default; + nsMainThreadPtrHandle& operator=(nsMainThreadPtrHandle&& aOther) = default; + nsMainThreadPtrHandle& operator=(nsMainThreadPtrHolder<T>* aHolder) { + mPtr = aHolder; + return *this; + } + + // These all call through to nsMainThreadPtrHolder, and thus implicitly + // assert that we're on the main thread (if strict). Off-main-thread consumers + // must treat these handles as opaque. + T* get() const { + if (mPtr) { + return mPtr.get()->get(); + } + return nullptr; + } + + operator T*() const { return get(); } + T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return get(); } + + // These are safe to call on other threads with appropriate external locking. + bool operator==(const nsMainThreadPtrHandle<T>& aOther) const { + if (!mPtr || !aOther.mPtr) { + return mPtr == aOther.mPtr; + } + return *mPtr == *aOther.mPtr; + } + bool operator!=(const nsMainThreadPtrHandle<T>& aOther) const { + return !operator==(aOther); + } + bool operator==(decltype(nullptr)) const { return mPtr == nullptr; } + bool operator!=(decltype(nullptr)) const { return mPtr != nullptr; } + bool operator!() const { return !mPtr || !*mPtr; } + + private: + RefPtr<nsMainThreadPtrHolder<T>> mPtr; +}; + +class nsCycleCollectionTraversalCallback; +template <typename T> +void CycleCollectionNoteChild(nsCycleCollectionTraversalCallback& aCallback, + T* aChild, const char* aName, uint32_t aFlags); + +template <typename T> +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsMainThreadPtrHandle<T>& aField, const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +template <typename T> +inline void ImplCycleCollectionUnlink(nsMainThreadPtrHandle<T>& aField) { + aField = nullptr; +} + +#endif diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp new file mode 100644 index 0000000000..cbae8e42d3 --- /dev/null +++ b/xpcom/threads/nsThread.cpp @@ -0,0 +1,1557 @@ +/* -*- 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 "nsThread.h" + +#include "base/message_loop.h" +#include "base/platform_thread.h" + +// Chromium's logging can sometimes leak through... +#ifdef LOG +# undef LOG +#endif + +#include "mozilla/ReentrantMonitor.h" +#include "nsMemoryPressure.h" +#include "nsThreadManager.h" +#include "nsIClassInfoImpl.h" +#include "nsCOMPtr.h" +#include "nsQueryObject.h" +#include "pratom.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/Logging.h" +#include "nsIObserverService.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/Preferences.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPrefs_threads.h" +#include "mozilla/TaskController.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsThreadSyncDispatch.h" +#include "nsServiceManagerUtils.h" +#include "GeckoProfiler.h" +#include "InputEventStatistics.h" +#include "ThreadEventQueue.h" +#include "ThreadEventTarget.h" +#include "ThreadDelay.h" + +#include <limits> + +#ifdef XP_LINUX +# ifdef __GLIBC__ +# include <gnu/libc-version.h> +# endif +# include <sys/mman.h> +# include <sys/time.h> +# include <sys/resource.h> +# include <sched.h> +# include <stdio.h> +#endif + +#ifdef XP_WIN +# include "mozilla/DynamicallyLinkedFunctionPtr.h" + +# include <winbase.h> + +using GetCurrentThreadStackLimitsFn = void(WINAPI*)(PULONG_PTR LowLimit, + PULONG_PTR HighLimit); +#endif + +#define HAVE_UALARM \ + _BSD_SOURCE || \ + (_XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \ + !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) + +#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE) +# define HAVE_SCHED_SETAFFINITY +#endif + +#ifdef XP_MACOSX +# include <mach/mach.h> +# include <mach/thread_policy.h> +#endif + +#ifdef MOZ_CANARY +# include <unistd.h> +# include <execinfo.h> +# include <signal.h> +# include <fcntl.h> +# include "nsXULAppAPI.h" +#endif + +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracer.h" +# include "TracedTaskCommon.h" +using namespace mozilla::tasktracer; +#endif + +using namespace mozilla; + +extern void InitThreadLocalVariables(); + +static LazyLogModule sThreadLog("nsThread"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args) + +NS_DECL_CI_INTERFACE_GETTER(nsThread) + +Array<char, nsThread::kRunnableNameBufSize> nsThread::sMainThreadRunnableName; + +uint32_t nsThread::sActiveThreads; +uint32_t nsThread::sMaxActiveThreads; + +#ifdef EARLY_BETA_OR_EARLIER +const uint32_t kTelemetryWakeupCountLimit = 100; +#endif + +//----------------------------------------------------------------------------- +// Because we do not have our own nsIFactory, we have to implement nsIClassInfo +// somewhat manually. + +class nsThreadClassInfo : public nsIClassInfo { + public: + NS_DECL_ISUPPORTS_INHERITED // no mRefCnt + NS_DECL_NSICLASSINFO + + nsThreadClassInfo() = default; +}; + +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::Release() { return 1; } +NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo) + +NS_IMETHODIMP +nsThreadClassInfo::GetInterfaces(nsTArray<nsIID>& aArray) { + return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aArray); +} + +NS_IMETHODIMP +nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) { + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetContractID(nsACString& aResult) { + aResult.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassDescription(nsACString& aResult) { + aResult.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassID(nsCID** aResult) { + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetFlags(uint32_t* aResult) { + *aResult = THREADSAFE; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) { + return NS_ERROR_NOT_AVAILABLE; +} + +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsThread) +NS_IMPL_RELEASE(nsThread) +NS_INTERFACE_MAP_BEGIN(nsThread) + NS_INTERFACE_MAP_ENTRY(nsIThread) + NS_INTERFACE_MAP_ENTRY(nsIThreadInternal) + NS_INTERFACE_MAP_ENTRY(nsIEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) + NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread) + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { + static nsThreadClassInfo sThreadClassInfo; + foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo); + } else +NS_INTERFACE_MAP_END +NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal, + nsIEventTarget, nsISerialEventTarget, + nsISupportsPriority) + +//----------------------------------------------------------------------------- + +class nsThreadStartupEvent final : public Runnable { + public: + nsThreadStartupEvent() + : Runnable("nsThreadStartupEvent"), + mMon("nsThreadStartupEvent.mMon"), + mInitialized(false) {} + + // This method does not return until the thread startup object is in the + // completion state. + void Wait() { + ReentrantMonitorAutoEnter mon(mMon); + while (!mInitialized) { + mon.Wait(); + } + } + + private: + ~nsThreadStartupEvent() = default; + + NS_IMETHOD Run() override { + ReentrantMonitorAutoEnter mon(mMon); + mInitialized = true; + mon.Notify(); + return NS_OK; + } + + ReentrantMonitor mMon; + bool mInitialized; +}; +//----------------------------------------------------------------------------- + +bool nsThread::ShutdownContextsComp::Equals( + const ShutdownContexts::elem_type& a, + const ShutdownContexts::elem_type::Pointer b) const { + return a.get() == b; +} + +// This event is responsible for notifying nsThread::Shutdown that it is time +// to call PR_JoinThread. It implements nsICancelableRunnable so that it can +// run on a DOM Worker thread (where all events must implement +// nsICancelableRunnable.) +class nsThreadShutdownAckEvent : public CancelableRunnable { + public: + explicit nsThreadShutdownAckEvent(NotNull<nsThreadShutdownContext*> aCtx) + : CancelableRunnable("nsThreadShutdownAckEvent"), + mShutdownContext(aCtx) {} + NS_IMETHOD Run() override { + mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext); + return NS_OK; + } + nsresult Cancel() override { return Run(); } + + private: + virtual ~nsThreadShutdownAckEvent() = default; + + NotNull<nsThreadShutdownContext*> mShutdownContext; +}; + +// This event is responsible for setting mShutdownContext +class nsThreadShutdownEvent : public Runnable { + public: + nsThreadShutdownEvent(NotNull<nsThread*> aThr, + NotNull<nsThreadShutdownContext*> aCtx) + : Runnable("nsThreadShutdownEvent"), + mThread(aThr), + mShutdownContext(aCtx) {} + NS_IMETHOD Run() override { + mThread->mShutdownContext = mShutdownContext; + MessageLoop::current()->Quit(); + return NS_OK; + } + + private: + NotNull<RefPtr<nsThread>> mThread; + NotNull<nsThreadShutdownContext*> mShutdownContext; +}; + +//----------------------------------------------------------------------------- + +static void SetThreadAffinity(unsigned int cpu) { +#ifdef HAVE_SCHED_SETAFFINITY + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(cpu, &cpus); + sched_setaffinity(0, sizeof(cpus), &cpus); + // Don't assert sched_setaffinity's return value because it intermittently (?) + // fails with EINVAL on Linux x64 try runs. +#elif defined(XP_MACOSX) + // OS X does not provide APIs to pin threads to specific processors, but you + // can tag threads as belonging to the same "affinity set" and the OS will try + // to run them on the same processor. To run threads on different processors, + // tag them as belonging to different affinity sets. Tag 0, the default, means + // "no affinity" so let's pretend each CPU has its own tag `cpu+1`. + thread_affinity_policy_data_t policy; + policy.affinity_tag = cpu + 1; + MOZ_ALWAYS_TRUE(thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, + &policy.affinity_tag, 1) == KERN_SUCCESS); +#elif defined(XP_WIN) + MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) != + (DWORD)-1); +#endif +} + +static void SetupCurrentThreadForChaosMode() { + if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) { + return; + } + +#ifdef XP_LINUX + // PR_SetThreadPriority doesn't really work since priorities > + // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use + // setpriority(2) to set random 'nice values'. In regular Linux this is only + // a dynamic adjustment so it still doesn't really do what we want, but tools + // like 'rr' can be more aggressive about honoring these values. + // Some of these calls may fail due to trying to lower the priority + // (e.g. something may have already called setpriority() for this thread). + // This makes it hard to have non-main threads with higher priority than the + // main thread, but that's hard to fix. Tools like rr can choose to honor the + // requested values anyway. + // Use just 4 priorities so there's a reasonable chance of any two threads + // having equal priority. + setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4)); +#else + // We should set the affinity here but NSPR doesn't provide a way to expose + // it. + uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1); + PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority)); +#endif + + // Force half the threads to CPU 0 so they compete for CPU + if (ChaosMode::randomUint32LessThan(2)) { + SetThreadAffinity(0); + } +} + +namespace { + +struct ThreadInitData { + nsThread* thread; + const nsACString& name; +}; + +} // namespace + +/* static */ mozilla::OffTheBooksMutex& nsThread::ThreadListMutex() { + static OffTheBooksMutex sMutex("nsThread::ThreadListMutex"); + return sMutex; +} + +/* static */ LinkedList<nsThread>& nsThread::ThreadList() { + static LinkedList<nsThread> sList; + return sList; +} + +/* static */ +void nsThread::ClearThreadList() { + OffTheBooksMutexAutoLock mal(ThreadListMutex()); + while (ThreadList().popFirst()) { + } +} + +/* static */ +nsThreadEnumerator nsThread::Enumerate() { return {}; } + +/* static */ +uint32_t nsThread::MaxActiveThreads() { + OffTheBooksMutexAutoLock mal(ThreadListMutex()); + return sMaxActiveThreads; +} + +void nsThread::AddToThreadList() { + OffTheBooksMutexAutoLock mal(ThreadListMutex()); + MOZ_ASSERT(!isInList()); + + sActiveThreads++; + sMaxActiveThreads = std::max(sActiveThreads, sMaxActiveThreads); + + ThreadList().insertBack(this); +} + +void nsThread::MaybeRemoveFromThreadList() { + OffTheBooksMutexAutoLock mal(ThreadListMutex()); + if (isInList()) { + sActiveThreads--; + removeFrom(ThreadList()); + } +} + +/*static*/ +void nsThread::ThreadFunc(void* aArg) { + using mozilla::ipc::BackgroundChild; + + ThreadInitData* initData = static_cast<ThreadInitData*>(aArg); + nsThread* self = initData->thread; // strong reference + + MOZ_ASSERT(self->mEventTarget); + MOZ_ASSERT(self->mEvents); + + self->mThread = PR_GetCurrentThread(); + self->mEventTarget->SetCurrentThread(); + SetupCurrentThreadForChaosMode(); + + if (!initData->name.IsEmpty()) { + NS_SetCurrentThreadName(initData->name.BeginReading()); + } + + self->InitCommon(); + + // Inform the ThreadManager + nsThreadManager::get().RegisterCurrentThread(*self); + + mozilla::IOInterposer::RegisterCurrentThread(); + +#ifdef MOZ_GECKO_PROFILER + // This must come after the call to nsThreadManager::RegisterCurrentThread(), + // because that call is needed to properly set up this thread as an nsThread, + // which profiler_register_thread() requires. See bug 1347007. + const bool registerWithProfiler = !initData->name.IsEmpty(); + if (registerWithProfiler) { + PROFILER_REGISTER_THREAD(initData->name.BeginReading()); + } +#endif // MOZ_GECKO_PROFILER + + // Wait for and process startup event + nsCOMPtr<nsIRunnable> event = self->mEvents->GetEvent(true, nullptr); + MOZ_ASSERT(event); + + initData = nullptr; // clear before unblocking nsThread::Init + + event->Run(); // unblocks nsThread::Init + event = nullptr; + + { + // Scope for MessageLoop. + MessageLoop loop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, self); + + // Now, process incoming events... + loop.Run(); + + BackgroundChild::CloseForCurrentThread(); + + // NB: The main thread does not shut down here! It shuts down via + // nsThreadManager::Shutdown. + + // Do NS_ProcessPendingEvents but with special handling to set + // mEventsAreDoomed atomically with the removal of the last event. The key + // invariant here is that we will never permit PutEvent to succeed if the + // event would be left in the queue after our final call to + // NS_ProcessPendingEvents. We also have to keep processing events as long + // as we have outstanding mRequestedShutdownContexts. + while (true) { + // Check and see if we're waiting on any threads. + self->WaitForAllAsynchronousShutdowns(); + + if (self->mEvents->ShutdownIfNoPendingEvents()) { + break; + } + NS_ProcessPendingEvents(self); + } + } + + mozilla::IOInterposer::UnregisterCurrentThread(); + + // Inform the threadmanager that this thread is going away + nsThreadManager::get().UnregisterCurrentThread(*self); + +#ifdef MOZ_GECKO_PROFILER + // The thread should only unregister itself if it was registered above. + if (registerWithProfiler) { + PROFILER_UNREGISTER_THREAD(); + } +#endif // MOZ_GECKO_PROFILER + + // Dispatch shutdown ACK + NotNull<nsThreadShutdownContext*> context = + WrapNotNull(self->mShutdownContext); + MOZ_ASSERT(context->mTerminatingThread == self); + event = do_QueryObject(new nsThreadShutdownAckEvent(context)); + if (context->mIsMainThreadJoining) { + SchedulerGroup::Dispatch(TaskCategory::Other, event.forget()); + } else { + context->mJoiningThread->Dispatch(event, NS_DISPATCH_NORMAL); + } + + // Release any observer of the thread here. + self->SetObserver(nullptr); + +#ifdef MOZ_TASK_TRACER + FreeTraceInfo(); +#endif + + // The PRThread will be deleted in PR_JoinThread(), so clear references. + self->mThread = nullptr; + self->mEventTarget->ClearCurrentThread(); + NS_RELEASE(self); +} + +void nsThread::InitCommon() { + mThreadId = uint32_t(PlatformThread::CurrentId()); + + { +#if defined(XP_LINUX) + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_getattr_np(pthread_self(), &attr); + + size_t stackSize; + pthread_attr_getstack(&attr, &mStackBase, &stackSize); + + // Glibc prior to 2.27 reports the stack size and base including the guard + // region, so we need to compensate for it to get accurate accounting. + // Also, this behavior difference isn't guarded by a versioned symbol, so we + // actually need to check the runtime glibc version, not the version we were + // compiled against. + static bool sAdjustForGuardSize = ({ +# ifdef __GLIBC__ + unsigned major, minor; + sscanf(gnu_get_libc_version(), "%u.%u", &major, &minor) < 2 || + major < 2 || (major == 2 && minor < 27); +# else + false; +# endif + }); + if (sAdjustForGuardSize) { + size_t guardSize; + pthread_attr_getguardsize(&attr, &guardSize); + + // Note: This assumes that the stack grows down, as is the case on all of + // our tier 1 platforms. On platforms where the stack grows up, the + // mStackBase adjustment is unnecessary, but doesn't cause any harm other + // than under-counting stack memory usage by one page. + mStackBase = reinterpret_cast<char*>(mStackBase) + guardSize; + stackSize -= guardSize; + } + + mStackSize = stackSize; + + // This is a bit of a hack. + // + // We really do want the NOHUGEPAGE flag on our thread stacks, since we + // don't expect any of them to need anywhere near 2MB of space. But setting + // it here is too late to have an effect, since the first stack page has + // already been faulted in existence, and NSPR doesn't give us a way to set + // it beforehand. + // + // What this does get us, however, is a different set of VM flags on our + // thread stacks compared to normal heap memory. Which makes the Linux + // kernel report them as separate regions, even when they are adjacent to + // heap memory. This allows us to accurately track the actual memory + // consumption of our allocated stacks. + madvise(mStackBase, stackSize, MADV_NOHUGEPAGE); + + pthread_attr_destroy(&attr); +#elif defined(XP_WIN) + static const StaticDynamicallyLinkedFunctionPtr< + GetCurrentThreadStackLimitsFn> + sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits"); + + if (sGetStackLimits) { + ULONG_PTR stackBottom, stackTop; + sGetStackLimits(&stackBottom, &stackTop); + mStackBase = reinterpret_cast<void*>(stackBottom); + mStackSize = stackTop - stackBottom; + } +#endif + } + + InitThreadLocalVariables(); + AddToThreadList(); +} + +//----------------------------------------------------------------------------- + +#ifdef MOZ_CANARY +int sCanaryOutputFD = -1; +#endif + +nsThread::nsThread(NotNull<SynchronizedEventQueue*> aQueue, + MainThreadFlag aMainThread, uint32_t aStackSize) + : mEvents(aQueue.get()), + mEventTarget( + new ThreadEventTarget(mEvents.get(), aMainThread == MAIN_THREAD)), + mShutdownContext(nullptr), + mScriptObserver(nullptr), + mStackSize(aStackSize), + mNestedEventLoopDepth(0), + mShutdownRequired(false), + mPriority(PRIORITY_NORMAL), + mIsMainThread(aMainThread == MAIN_THREAD), + mUseHangMonitor(aMainThread == MAIN_THREAD), + mIsAPoolThreadFree(nullptr), + mCanInvokeJS(false), +#ifdef EARLY_BETA_OR_EARLIER + mLastWakeupCheckTime(TimeStamp::Now()), +#endif + mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) { + if (mIsMainThread) { + mozilla::TaskController::Get()->SetPerformanceCounterState( + &mPerformanceCounterState); + } +} + +nsThread::nsThread() + : mEvents(nullptr), + mEventTarget(nullptr), + mShutdownContext(nullptr), + mScriptObserver(nullptr), + mStackSize(0), + mNestedEventLoopDepth(0), + mShutdownRequired(false), + mPriority(PRIORITY_NORMAL), + mIsMainThread(false), + mUseHangMonitor(false), + mCanInvokeJS(false), +#ifdef EARLY_BETA_OR_EARLIER + mLastWakeupCheckTime(TimeStamp::Now()), +#endif + mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) { + MOZ_ASSERT(!NS_IsMainThread()); +} + +nsThread::~nsThread() { + NS_ASSERTION(mRequestedShutdownContexts.IsEmpty(), + "shouldn't be waiting on other threads to shutdown"); + + MaybeRemoveFromThreadList(); + +#ifdef DEBUG + // We deliberately leak these so they can be tracked by the leak checker. + // If you're having nsThreadShutdownContext leaks, you can set: + // XPCOM_MEM_LOG_CLASSES=nsThreadShutdownContext + // during a test run and that will at least tell you what thread is + // requesting shutdown on another, which can be helpful for diagnosing + // the leak. + for (size_t i = 0; i < mRequestedShutdownContexts.Length(); ++i) { + Unused << mRequestedShutdownContexts[i].release(); + } +#endif +} + +nsresult nsThread::Init(const nsACString& aName) { + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + + // spawn thread and wait until it is fully setup + RefPtr<nsThreadStartupEvent> startup = new nsThreadStartupEvent(); + + NS_ADDREF_THIS(); + + mShutdownRequired = true; + + ThreadInitData initData = {this, aName}; + + // ThreadFunc is responsible for setting mThread + if (!PR_CreateThread(PR_USER_THREAD, ThreadFunc, &initData, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, + mStackSize)) { + NS_RELEASE_THIS(); + return NS_ERROR_OUT_OF_MEMORY; + } + + // ThreadFunc will wait for this event to be run before it tries to access + // mThread. By delaying insertion of this event into the queue, we ensure + // that mThread is set properly. + { + mEvents->PutEvent(do_AddRef(startup), + EventQueuePriority::Normal); // retain a reference + } + + // Wait for thread to call ThreadManager::SetupCurrentThread, which completes + // initialization of ThreadFunc. + startup->Wait(); + return NS_OK; +} + +nsresult nsThread::InitCurrentThread() { + mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); + InitCommon(); + + nsThreadManager::get().RegisterCurrentThread(*this); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIEventTarget + +NS_IMETHODIMP +nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIRunnable> event(aEvent); + return mEventTarget->Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */ nullptr, aFlags)); + + return mEventTarget->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +nsThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelayMs) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs); +} + +NS_IMETHODIMP +nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) { + if (mIsAPoolThreadFree && *mIsAPoolThreadFree) { + // if there are unstarted threads in the pool, a new event to the + // pool would not be delayed at all (beyond thread start time) + *aDelay = TimeDuration(); + *aStart = TimeStamp(); + } else { + *aDelay = mLastEventDelay; + *aStart = mLastEventStart; + } + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) { + mLastEventDelay = aDelay; + mLastEventStart = aStart; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::IsOnCurrentThread(bool* aResult) { + if (mEventTarget) { + return mEventTarget->IsOnCurrentThread(aResult); + } + *aResult = PR_GetCurrentThread() == mThread; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +nsThread::IsOnCurrentThreadInfallible() { + // This method is only going to be called if `mThread` is null, which + // only happens when the thread has exited the event loop. Therefore, when + // we are called, we can never be on this thread. + return false; +} + +//----------------------------------------------------------------------------- +// nsIThread + +NS_IMETHODIMP +nsThread::GetPRThread(PRThread** aResult) { + PRThread* thread = mThread; // atomic load + *aResult = thread; + return thread ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsThread::GetCanInvokeJS(bool* aResult) { + *aResult = mCanInvokeJS; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetCanInvokeJS(bool aCanInvokeJS) { + mCanInvokeJS = aCanInvokeJS; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::GetLastLongTaskEnd(TimeStamp* _retval) { + *_retval = mPerformanceCounterState.LastLongTaskEnd(); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) { + *_retval = mPerformanceCounterState.LastLongNonIdleTaskEnd(); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetNameForWakeupTelemetry(const nsACString& aName) { +#ifdef EARLY_BETA_OR_EARLIER + mNameForWakeupTelemetry = aName; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AsyncShutdown() { + LOG(("THRD(%p) async shutdown\n", this)); + + ShutdownInternal(/* aSync = */ false); + return NS_OK; +} + +nsThreadShutdownContext* nsThread::ShutdownInternal(bool aSync) { + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(mThread != PR_GetCurrentThread()); + if (NS_WARN_IF(mThread == PR_GetCurrentThread())) { + return nullptr; + } + + // Prevent multiple calls to this method. + if (!mShutdownRequired.compareExchange(true, false)) { + return nullptr; + } + MOZ_ASSERT(mThread); + + MaybeRemoveFromThreadList(); + + NotNull<nsThread*> currentThread = + WrapNotNull(nsThreadManager::get().GetCurrentThread()); + + MOZ_DIAGNOSTIC_ASSERT(currentThread->EventQueue(), + "Shutdown() may only be called from an XPCOM thread"); + + // Allocate a shutdown context and store a strong ref. + auto context = + new nsThreadShutdownContext(WrapNotNull(this), currentThread, aSync); + Unused << *currentThread->mRequestedShutdownContexts.EmplaceBack(context); + + // Set mShutdownContext and wake up the thread in case it is waiting for + // events to process. + nsCOMPtr<nsIRunnable> event = + new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context)); + // XXXroc What if posting the event fails due to OOM? + mEvents->PutEvent(event.forget(), EventQueuePriority::Normal); + + // We could still end up with other events being added after the shutdown + // task, but that's okay because we process pending events in ThreadFunc + // after setting mShutdownContext just before exiting. + return context; +} + +void nsThread::ShutdownComplete(NotNull<nsThreadShutdownContext*> aContext) { + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(aContext->mTerminatingThread == this); + + MaybeRemoveFromThreadList(); + + if (aContext->mAwaitingShutdownAck) { + // We're in a synchronous shutdown, so tell whatever is up the stack that + // we're done and unwind the stack so it can call us again. + aContext->mAwaitingShutdownAck = false; + return; + } + + // Now, it should be safe to join without fear of dead-locking. + PR_JoinThread(aContext->mTerminatingPRThread); + MOZ_ASSERT(!mThread); + +#ifdef DEBUG + nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver(); + MOZ_ASSERT(!obs, "Should have been cleared at shutdown!"); +#endif + + // Delete aContext. + // aContext might not be in mRequestedShutdownContexts if it belongs to a + // thread that was leaked by calling nsIThreadPool::ShutdownWithTimeout. + aContext->mJoiningThread->mRequestedShutdownContexts.RemoveElement( + aContext, ShutdownContextsComp{}); +} + +void nsThread::WaitForAllAsynchronousShutdowns() { + // This is the motivating example for why SpinEventLoop has the template + // parameter we are providing here. + SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( + [&]() { return mRequestedShutdownContexts.IsEmpty(); }, this); +} + +NS_IMETHODIMP +nsThread::Shutdown() { + LOG(("THRD(%p) sync shutdown\n", this)); + + nsThreadShutdownContext* maybeContext = ShutdownInternal(/* aSync = */ true); + if (!maybeContext) { + return NS_OK; // The thread has already shut down. + } + + NotNull<nsThreadShutdownContext*> context = WrapNotNull(maybeContext); + + // Process events on the current thread until we receive a shutdown ACK. + // Allows waiting; ensure no locks are held that would deadlock us! + SpinEventLoopUntil([&, context]() { return !context->mAwaitingShutdownAck; }, + context->mJoiningThread); + + ShutdownComplete(context); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingEvents(bool* aResult) { + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + if (mIsMainThread && !mIsInLocalExecutionMode) { + *aResult = TaskController::Get()->HasMainThreadPendingTasks(); + } else { + *aResult = mEvents->HasPendingEvent(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingHighPriorityEvents(bool* aResult) { + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // This function appears to never be called anymore. + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::DispatchToQueue(already_AddRefed<nsIRunnable> aEvent, + EventQueuePriority aQueue) { + nsCOMPtr<nsIRunnable> event = aEvent; + + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + if (!mEvents->PutEvent(event.forget(), aQueue)) { + NS_WARNING( + "An idle event was posted to a thread that will never run it " + "(rejected)"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +#ifdef MOZ_CANARY +void canary_alarm_handler(int signum); + +class Canary { + // XXX ToDo: support nested loops + public: + Canary() { + if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) { + signal(SIGALRM, canary_alarm_handler); + ualarm(15000, 0); + } + } + + ~Canary() { + if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) { + ualarm(0, 0); + } + } + + static bool EventLatencyIsImportant() { + return NS_IsMainThread() && XRE_IsParentProcess(); + } +}; + +void canary_alarm_handler(int signum) { + void* array[30]; + const char msg[29] = "event took too long to run:\n"; + // use write to be safe in the signal handler + write(sCanaryOutputFD, msg, sizeof(msg)); + backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD); +} + +#endif + +#define NOTIFY_EVENT_OBSERVERS(observers_, func_, params_) \ + do { \ + if (!observers_.IsEmpty()) { \ + for (nsCOMPtr<nsIThreadObserver> obs_ : observers_.ForwardRange()) { \ + obs_->func_ params_; \ + } \ + } \ + } while (0) + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +// static +bool nsThread::GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName, + EventQueuePriority aPriority) { + bool labeled = false; + if (RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent)) { + labeled = true; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(groupRunnable->GetName(aName))); + } else if (nsCOMPtr<nsINamed> named = do_QueryInterface(aEvent)) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName))); + } else { + aName.AssignLiteral("non-nsINamed runnable"); + } + if (aName.IsEmpty()) { + aName.AssignLiteral("anonymous runnable"); + } + + if (!labeled && aPriority > EventQueuePriority::InputHigh) { + aName.AppendLiteral("(unlabeled)"); + } + + return labeled; +} +#endif + +mozilla::PerformanceCounter* nsThread::GetPerformanceCounter( + nsIRunnable* aEvent) const { + return GetPerformanceCounterBase(aEvent); +} + +// static +mozilla::PerformanceCounter* nsThread::GetPerformanceCounterBase( + nsIRunnable* aEvent) { + RefPtr<SchedulerGroup::Runnable> docRunnable = do_QueryObject(aEvent); + if (docRunnable) { + mozilla::dom::DocGroup* docGroup = docRunnable->DocGroup(); + if (docGroup) { + return docGroup->GetPerformanceCounter(); + } + } + return nullptr; +} + +size_t nsThread::ShallowSizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mShutdownContext) { + n += aMallocSizeOf(mShutdownContext); + } + n += mRequestedShutdownContexts.ShallowSizeOfExcludingThis(aMallocSizeOf); + return aMallocSizeOf(this) + aMallocSizeOf(mThread) + n; +} + +size_t nsThread::SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mEventTarget) { + // The size of mEvents is reported by mEventTarget. + n += mEventTarget->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +size_t nsThread::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return ShallowSizeOfIncludingThis(aMallocSizeOf) + + SizeOfEventQueues(aMallocSizeOf); +} + +NS_IMETHODIMP +nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait, + mNestedEventLoopDepth)); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // The toplevel event loop normally blocks waiting for the next event, but + // if we're trying to shut this thread down, we must exit the event loop + // when the event queue is empty. This only applys to the toplevel event + // loop! Nested event loops (e.g. during sync dispatch) are waiting for + // some state change and must be able to block even if something has + // requested shutdown of the thread. Otherwise we'll just busywait as we + // endlessly look for an event, fail to find one, and repeat the nested + // event loop since its state change hasn't happened yet. + bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown()); + + if (mIsInLocalExecutionMode) { + if (nsCOMPtr<nsIRunnable> event = mEvents->GetEvent(reallyWait)) { + *aResult = true; + LogRunnable::Run log(event); + event->Run(); + event = nullptr; + } else { + *aResult = false; + } + return NS_OK; + } + + Maybe<dom::AutoNoJSAPI> noJSAPI; + + if (mUseHangMonitor && reallyWait) { + BackgroundHangMonitor().NotifyWait(); + } + + if (mIsMainThread) { + DoMainThreadSpecificProcessing(); + } + + ++mNestedEventLoopDepth; + + // We only want to create an AutoNoJSAPI on threads that actually do DOM + // stuff (including workers). Those are exactly the threads that have an + // mScriptObserver. + bool callScriptObserver = !!mScriptObserver; + if (callScriptObserver) { + noJSAPI.emplace(); + mScriptObserver->BeforeProcessTask(reallyWait); + } + +#ifdef EARLY_BETA_OR_EARLIER + // Need to capture mayWaitForWakeup state before OnProcessNextEvent, + // since on the main thread OnProcessNextEvent ends up waiting for the new + // events. + bool mayWaitForWakeup = reallyWait && !mEvents->HasPendingEvent(); +#endif + + nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserverOnThread(); + if (obs) { + obs->OnProcessNextEvent(this, reallyWait); + } + + NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), OnProcessNextEvent, + (this, reallyWait)); + +#ifdef MOZ_CANARY + Canary canary; +#endif + nsresult rv = NS_OK; + + { + // Scope for |event| to make sure that its destructor fires while + // mNestedEventLoopDepth has been incremented, since that destructor can + // also do work. + nsCOMPtr<nsIRunnable> event; + bool usingTaskController = mIsMainThread; + if (usingTaskController) { + event = TaskController::Get()->GetRunnableForMTTask(reallyWait); + } else { + event = mEvents->GetEvent(reallyWait, &mLastEventDelay); + } + + *aResult = (event.get() != nullptr); + + if (event) { +#ifdef EARLY_BETA_OR_EARLIER + if (mayWaitForWakeup && mThread) { + ++mWakeupCount; + if (mWakeupCount == kTelemetryWakeupCountLimit) { + TimeStamp now = TimeStamp::Now(); + double ms = (now - mLastWakeupCheckTime).ToMilliseconds(); + if (ms < 0) { + ms = 0; + } + const char* name = !mNameForWakeupTelemetry.IsEmpty() + ? mNameForWakeupTelemetry.get() + : PR_GetThreadName(mThread); + if (!name) { + name = mIsMainThread ? "MainThread" : "(nameless thread)"; + } + nsDependentCString key(name); + Telemetry::Accumulate(Telemetry::THREAD_WAKEUP, key, + static_cast<uint32_t>(ms)); + mLastWakeupCheckTime = now; + mWakeupCount = 0; + } + } +#endif + + LOG(("THRD(%p) running [%p]\n", this, event.get())); + + Maybe<LogRunnable::Run> log; + + if (!usingTaskController) { + log.emplace(event); + } + + // Delay event processing to encourage whoever dispatched this event + // to run. + DelayForChaosMode(ChaosFeature::TaskRunning, 1000); + + mozilla::TimeStamp now = mozilla::TimeStamp::Now(); + + if (mUseHangMonitor) { + BackgroundHangMonitor().NotifyActivity(); + } + + Maybe<PerformanceCounterState::Snapshot> snapshot; + if (!usingTaskController) { + snapshot.emplace(mPerformanceCounterState.RunnableWillRun( + GetPerformanceCounter(event), now, false)); + } + + mLastEventStart = now; + + event->Run(); + + if (usingTaskController) { + *aResult = TaskController::Get()->MTTaskRunnableProcessedTask(); + } else { + mPerformanceCounterState.RunnableDidRun(std::move(snapshot.ref())); + } + + // To cover the event's destructor code inside the LogRunnable span. + event = nullptr; + } else { + mLastEventDelay = TimeDuration(); + mLastEventStart = TimeStamp(); + if (aMayWait) { + MOZ_ASSERT(ShuttingDown(), + "This should only happen when shutting down"); + rv = NS_ERROR_UNEXPECTED; + } + } + } + + DrainDirectTasks(); + + NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), AfterProcessNextEvent, + (this, *aResult)); + + if (obs) { + obs->AfterProcessNextEvent(this, *aResult); + } + + // In case some EventObserver dispatched some direct tasks; process them + // now. + DrainDirectTasks(); + + if (callScriptObserver) { + if (mScriptObserver) { + mScriptObserver->AfterProcessTask(mNestedEventLoopDepth); + } + noJSAPI.reset(); + } + + --mNestedEventLoopDepth; + + return rv; +} + +//----------------------------------------------------------------------------- +// nsISupportsPriority + +NS_IMETHODIMP +nsThread::GetPriority(int32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetPriority(int32_t aPriority) { + if (NS_WARN_IF(!mThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // NSPR defines the following four thread priorities: + // PR_PRIORITY_LOW + // PR_PRIORITY_NORMAL + // PR_PRIORITY_HIGH + // PR_PRIORITY_URGENT + // We map the priority values defined on nsISupportsPriority to these + // values. + + mPriority = aPriority; + + PRThreadPriority pri; + if (mPriority <= PRIORITY_HIGHEST) { + pri = PR_PRIORITY_URGENT; + } else if (mPriority < PRIORITY_NORMAL) { + pri = PR_PRIORITY_HIGH; + } else if (mPriority > PRIORITY_NORMAL) { + pri = PR_PRIORITY_LOW; + } else { + pri = PR_PRIORITY_NORMAL; + } + // If chaos mode is active, retain the randomly chosen priority + if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) { + PR_SetThreadPriority(mThread, pri); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AdjustPriority(int32_t aDelta) { + return SetPriority(mPriority + aDelta); +} + +//----------------------------------------------------------------------------- +// nsIThreadInternal + +NS_IMETHODIMP +nsThread::GetObserver(nsIThreadObserver** aObs) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIThreadObserver> obs = mEvents->GetObserver(); + obs.forget(aObs); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetObserver(nsIThreadObserver* aObs) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + mEvents->SetObserver(aObs); + return NS_OK; +} + +uint32_t nsThread::RecursionDepth() const { + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + return mNestedEventLoopDepth; +} + +NS_IMETHODIMP +nsThread::AddObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + EventQueue()->AddObserver(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::RemoveObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + EventQueue()->RemoveObserver(aObserver); + + return NS_OK; +} + +void nsThread::SetScriptObserver( + mozilla::CycleCollectedJSContext* aScriptObserver) { + if (!aScriptObserver) { + mScriptObserver = nullptr; + return; + } + + MOZ_ASSERT(!mScriptObserver); + mScriptObserver = aScriptObserver; +} + +void nsThread::DoMainThreadSpecificProcessing() const { + MOZ_ASSERT(mIsMainThread); + + ipc::CancelCPOWs(); + + // Fire a memory pressure notification, if one is pending. + if (!ShuttingDown()) { + MemoryPressureState mpPending = NS_GetPendingMemoryPressure(); + if (mpPending != MemPressure_None) { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + + if (os) { + if (mpPending == MemPressure_Stopping) { + os->NotifyObservers(nullptr, "memory-pressure-stop", nullptr); + } else { + os->NotifyObservers(nullptr, "memory-pressure", + mpPending == MemPressure_New + ? u"low-memory" + : u"low-memory-ongoing"); + } + } else { + NS_WARNING("Can't get observer service!"); + } + } + } +} + +NS_IMETHODIMP +nsThread::GetEventTarget(nsIEventTarget** aEventTarget) { + nsCOMPtr<nsIEventTarget> target = this; + target.forget(aEventTarget); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher + +NS_IMETHODIMP +nsThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.AddTask(std::move(aEvent)); + return NS_OK; +} + +NS_IMETHODIMP nsThread::DrainDirectTasks() { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.DrainTasks(); + return NS_OK; +} + +NS_IMETHODIMP nsThread::HaveDirectTasks(bool* aValue) { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + + *aValue = mDirectTasks.HaveTasks(); + return NS_OK; +} + +nsIEventTarget* nsThread::EventTarget() { return this; } + +nsISerialEventTarget* nsThread::SerialEventTarget() { return this; } + +nsLocalExecutionRecord nsThread::EnterLocalExecution() { + MOZ_RELEASE_ASSERT(!mIsInLocalExecutionMode); + MOZ_ASSERT(IsOnCurrentThread()); + MOZ_ASSERT(EventQueue()); + return nsLocalExecutionRecord(*EventQueue(), mIsInLocalExecutionMode); +} + +nsLocalExecutionGuard::nsLocalExecutionGuard( + nsLocalExecutionRecord&& aLocalExecutionRecord) + : mEventQueueStack(aLocalExecutionRecord.mEventQueueStack), + mLocalEventTarget(mEventQueueStack.PushEventQueue()), + mLocalExecutionFlag(aLocalExecutionRecord.mLocalExecutionFlag) { + MOZ_ASSERT(mLocalEventTarget); + MOZ_ASSERT(!mLocalExecutionFlag); + mLocalExecutionFlag = true; +} + +nsLocalExecutionGuard::~nsLocalExecutionGuard() { + MOZ_ASSERT(mLocalExecutionFlag); + mLocalExecutionFlag = false; + mEventQueueStack.PopEventQueue(mLocalEventTarget); +} + +namespace mozilla { +PerformanceCounterState::Snapshot PerformanceCounterState::RunnableWillRun( + PerformanceCounter* aCounter, TimeStamp aNow, bool aIsIdleRunnable) { + if (IsNestedRunnable()) { + // Flush out any accumulated time that should be accounted to the + // current runnable before we start running a nested runnable. + MaybeReportAccumulatedTime(aNow); + } + + Snapshot snapshot(mCurrentEventLoopDepth, mCurrentPerformanceCounter, + mCurrentRunnableIsIdleRunnable); + + mCurrentEventLoopDepth = mNestedEventLoopDepth; + mCurrentPerformanceCounter = aCounter; + mCurrentRunnableIsIdleRunnable = aIsIdleRunnable; + mCurrentTimeSliceStart = aNow; + + return snapshot; +} + +void PerformanceCounterState::RunnableDidRun(Snapshot&& aSnapshot) { + // First thing: Restore our mCurrentEventLoopDepth so we can use + // IsNestedRunnable(). + mCurrentEventLoopDepth = aSnapshot.mOldEventLoopDepth; + + // We may not need the current timestamp; don't bother computing it if we + // don't. + TimeStamp now; + if (mCurrentPerformanceCounter || mIsMainThread || IsNestedRunnable()) { + now = TimeStamp::Now(); + } + if (mCurrentPerformanceCounter || mIsMainThread) { + MaybeReportAccumulatedTime(now); + } + + // And now restore the rest of our state. + mCurrentPerformanceCounter = std::move(aSnapshot.mOldPerformanceCounter); + mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable; + if (IsNestedRunnable()) { + // Reset mCurrentTimeSliceStart to right now, so our parent runnable's + // next slice can be properly accounted for. + mCurrentTimeSliceStart = now; + } else { + // We are done at the outermost level; we are no longer in a timeslice. + mCurrentTimeSliceStart = TimeStamp(); + } +} + +void PerformanceCounterState::MaybeReportAccumulatedTime(TimeStamp aNow) { + MOZ_ASSERT(mCurrentTimeSliceStart, + "How did we get here if we're not in a timeslice?"); + + if (!mCurrentPerformanceCounter && !mIsMainThread) { + // No one cares about this timeslice. + return; + } + + TimeDuration duration = aNow - mCurrentTimeSliceStart; + if (mCurrentPerformanceCounter) { + mCurrentPerformanceCounter->IncrementExecutionDuration( + duration.ToMicroseconds()); + } + + // Long tasks only matter on the main thread. + if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_BUSY_WINDOW_MS) { + // Idle events (gc...) don't *really* count here + if (!mCurrentRunnableIsIdleRunnable) { + mLastLongNonIdleTaskEnd = aNow; + } + mLastLongTaskEnd = aNow; + +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + struct LongTaskMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("MainThreadLongTask"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) { + aWriter.StringProperty("category", "LongTask"); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::markerChart, MS::Location::markerTable}; + schema.AddKeyLabelFormat("category", "Type", MS::Format::string); + return schema; + } + }; + + profiler_add_marker(mCurrentRunnableIsIdleRunnable + ? ProfilerString8View("LongIdleTask") + : ProfilerString8View("LongTask"), + geckoprofiler::category::OTHER, + MarkerTiming::Interval(mCurrentTimeSliceStart, aNow), + LongTaskMarker{}); + } +#endif + } +} + +} // namespace mozilla diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h new file mode 100644 index 0000000000..305f092dba --- /dev/null +++ b/xpcom/threads/nsThread.h @@ -0,0 +1,444 @@ +/* -*- 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/. */ + +#ifndef nsThread_h__ +#define nsThread_h__ + +#include "MainThreadUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventQueue.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/NotNull.h" +#include "mozilla/PerformanceCounter.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsIEventTarget.h" +#include "nsISerialEventTarget.h" +#include "nsISupportsPriority.h" +#include "nsIThread.h" +#include "nsIThreadInternal.h" +#include "nsTArray.h" + +namespace mozilla { +class CycleCollectedJSContext; +class SynchronizedEventQueue; +class ThreadEventQueue; +class ThreadEventTarget; + +template <typename T, size_t Length> +class Array; +} // namespace mozilla + +using mozilla::NotNull; + +class nsIRunnable; +class nsLocalExecutionRecord; +class nsThreadEnumerator; + +// See https://www.w3.org/TR/longtasks +#define LONGTASK_BUSY_WINDOW_MS 50 + +// A class for managing performance counter state. +namespace mozilla { +class PerformanceCounterState { + public: + explicit PerformanceCounterState(const uint32_t& aNestedEventLoopDepthRef, + bool aIsMainThread) + : mNestedEventLoopDepth(aNestedEventLoopDepthRef), + mIsMainThread(aIsMainThread), + // Does it really make sense to initialize these to "now" when we + // haven't run any tasks? + mLastLongTaskEnd(TimeStamp::Now()), + mLastLongNonIdleTaskEnd(mLastLongTaskEnd) {} + + class Snapshot { + public: + Snapshot(uint32_t aOldEventLoopDepth, PerformanceCounter* aCounter, + bool aOldIsIdleRunnable) + : mOldEventLoopDepth(aOldEventLoopDepth), + mOldPerformanceCounter(aCounter), + mOldIsIdleRunnable(aOldIsIdleRunnable) {} + + Snapshot(const Snapshot&) = default; + Snapshot(Snapshot&&) = default; + + private: + friend class PerformanceCounterState; + + const uint32_t mOldEventLoopDepth; + // Non-const so we can move out of it and avoid the extra refcounting. + RefPtr<PerformanceCounter> mOldPerformanceCounter; + const bool mOldIsIdleRunnable; + }; + + // Notification that a runnable is about to run. This captures a snapshot of + // our current state before we reset to prepare for the new runnable. This + // muast be called after mNestedEventLoopDepth has been incremented for the + // runnable execution. The performance counter passed in should be the one + // for the relevant runnable and may be null. aIsIdleRunnable should be true + // if and only if the runnable has idle priority. + Snapshot RunnableWillRun(PerformanceCounter* Counter, TimeStamp aNow, + bool aIsIdleRunnable); + + // Notification that a runnable finished executing. This must be passed the + // snapshot that RunnableWillRun returned for the same runnable. This must be + // called before mNestedEventLoopDepth is decremented after the runnable's + // execution. + void RunnableDidRun(Snapshot&& aSnapshot); + + const TimeStamp& LastLongTaskEnd() const { return mLastLongTaskEnd; } + const TimeStamp& LastLongNonIdleTaskEnd() const { + return mLastLongNonIdleTaskEnd; + } + + private: + // Called to report accumulated time, as needed, when we're about to run a + // runnable or just finished running one. + void MaybeReportAccumulatedTime(TimeStamp aNow); + + // Whether the runnable we are about to run, or just ran, is a nested + // runnable, in the sense that there is some other runnable up the stack + // spinning the event loop. This must be called before we change our + // mCurrentEventLoopDepth (when about to run a new event) or after we restore + // it (after we ran one). + bool IsNestedRunnable() const { + return mNestedEventLoopDepth > mCurrentEventLoopDepth; + } + + // The event loop depth of the currently running runnable. Set to the max + // value of a uint32_t when there is no runnable running, so when starting to + // run a toplevel (not nested) runnable IsNestedRunnable() will test false. + uint32_t mCurrentEventLoopDepth = std::numeric_limits<uint32_t>::max(); + + // A reference to the nsThread's mNestedEventLoopDepth, so we can + // see what it is right now. + const uint32_t& mNestedEventLoopDepth; + + // A boolean that indicates whether the currently running runnable is an idle + // runnable. Only has a useful value between RunnableWillRun() being called + // and RunnableDidRun() returning. + bool mCurrentRunnableIsIdleRunnable = false; + + // Whether we're attached to the mainthread nsThread. + const bool mIsMainThread; + + // The timestamp from which time to be accounted for should be measured. This + // can be the start of a runnable running or the end of a nested runnable + // running. + TimeStamp mCurrentTimeSliceStart; + + // Information about when long tasks last ended. + TimeStamp mLastLongTaskEnd; + TimeStamp mLastLongNonIdleTaskEnd; + + // The performance counter to use for accumulating the runtime of + // the currently running event. May be null, in which case the + // event's running time should not be accounted to any performance + // counters. + RefPtr<PerformanceCounter> mCurrentPerformanceCounter; +}; +} // namespace mozilla + +// A native thread +class nsThread : public nsIThreadInternal, + public nsISupportsPriority, + public nsIDirectTaskDispatcher, + private mozilla::LinkedListElement<nsThread> { + friend mozilla::LinkedList<nsThread>; + friend mozilla::LinkedListElement<nsThread>; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREAD + NS_DECL_NSITHREADINTERNAL + NS_DECL_NSISUPPORTSPRIORITY + NS_DECL_NSIDIRECTTASKDISPATCHER + + enum MainThreadFlag { MAIN_THREAD, NOT_MAIN_THREAD }; + + nsThread(NotNull<mozilla::SynchronizedEventQueue*> aQueue, + MainThreadFlag aMainThread, uint32_t aStackSize); + + private: + nsThread(); + + public: + // Initialize this as a named wrapper for a new PRThread. + nsresult Init(const nsACString& aName); + + // Initialize this as a wrapper for the current PRThread. + nsresult InitCurrentThread(); + + private: + // Initializes the mThreadId and stack base/size members, and adds the thread + // to the ThreadList(). + void InitCommon(); + + public: + // The PRThread corresponding to this thread. + PRThread* GetPRThread() const { return mThread; } + + const void* StackBase() const { return mStackBase; } + size_t StackSize() const { return mStackSize; } + + uint32_t ThreadId() const { return mThreadId; } + + // If this flag is true, then the nsThread was created using + // nsIThreadManager::NewThread. + bool ShutdownRequired() { return mShutdownRequired; } + + // Lets GetRunningEventDelay() determine if the pool this is part + // of has an unstarted thread + void SetPoolThreadFreePtr(mozilla::Atomic<bool, mozilla::Relaxed>* aPtr) { + mIsAPoolThreadFree = aPtr; + } + + void SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver); + + uint32_t RecursionDepth() const; + + void ShutdownComplete(NotNull<struct nsThreadShutdownContext*> aContext); + + void WaitForAllAsynchronousShutdowns(); + + static const uint32_t kRunnableNameBufSize = 1000; + static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName; + + mozilla::SynchronizedEventQueue* EventQueue() { return mEvents.get(); } + + bool ShuttingDown() const { return mShutdownContext != nullptr; } + + static bool GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName, + mozilla::EventQueuePriority aPriority); + + virtual mozilla::PerformanceCounter* GetPerformanceCounter( + nsIRunnable* aEvent) const; + + static mozilla::PerformanceCounter* GetPerformanceCounterBase( + nsIRunnable* aEvent); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Returns the size of this object, its PRThread, and its shutdown contexts, + // but excluding its event queues. + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + size_t SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const; + + static nsThreadEnumerator Enumerate(); + + static uint32_t MaxActiveThreads(); + + // When entering local execution mode a new event queue is created and used as + // an event source. This queue is only accessible through an + // nsLocalExecutionGuard constructed from the nsLocalExecutionRecord returned + // by this function, effectively restricting the events that get run while in + // local execution mode to those dispatched by the owner of the guard object. + // + // Local execution is not nestable. When the nsLocalExecutionGuard is + // destructed, the thread exits the local execution mode. + // + // Note that code run in local execution mode is not considered a task in the + // spec sense. Events from the local queue are considered part of the + // enclosing task and as such do not trigger profiling hooks, observer + // notifications, etc. + nsLocalExecutionRecord EnterLocalExecution(); + + void SetUseHangMonitor(bool aValue) { + MOZ_ASSERT(IsOnCurrentThread()); + mUseHangMonitor = aValue; + } + + private: + void DoMainThreadSpecificProcessing() const; + + protected: + friend class nsThreadShutdownEvent; + + friend class nsThreadEnumerator; + + virtual ~nsThread(); + + static void ThreadFunc(void* aArg); + + // Helper + already_AddRefed<nsIThreadObserver> GetObserver() { + nsIThreadObserver* obs; + nsThread::GetObserver(&obs); + return already_AddRefed<nsIThreadObserver>(obs); + } + + struct nsThreadShutdownContext* ShutdownInternal(bool aSync); + + friend class nsThreadManager; + friend class nsThreadPool; + + static mozilla::OffTheBooksMutex& ThreadListMutex(); + static mozilla::LinkedList<nsThread>& ThreadList(); + static void ClearThreadList(); + + // The current number of active threads. + static uint32_t sActiveThreads; + // The maximum current number of active threads we've had in this session. + static uint32_t sMaxActiveThreads; + + void AddToThreadList(); + void MaybeRemoveFromThreadList(); + + // Whether or not these members have a value determines whether the nsThread + // is treated as a full XPCOM thread or as a thin wrapper. + // + // For full nsThreads, they will always contain valid pointers. For thin + // wrappers around non-XPCOM threads, they will be null, and event dispatch + // methods which rely on them will fail (and assert) if called. + RefPtr<mozilla::SynchronizedEventQueue> mEvents; + RefPtr<mozilla::ThreadEventTarget> mEventTarget; + + // The shutdown contexts for any other threads we've asked to shut down. + using ShutdownContexts = + nsTArray<mozilla::UniquePtr<struct nsThreadShutdownContext>>; + + // Helper for finding a ShutdownContext in the contexts array. + struct ShutdownContextsComp { + bool Equals(const ShutdownContexts::elem_type& a, + const ShutdownContexts::elem_type::Pointer b) const; + }; + + ShutdownContexts mRequestedShutdownContexts; + // The shutdown context for ourselves. + struct nsThreadShutdownContext* mShutdownContext; + + mozilla::CycleCollectedJSContext* mScriptObserver; + + void* mStackBase = nullptr; + uint32_t mStackSize; + uint32_t mThreadId; + + uint32_t mNestedEventLoopDepth; + + mozilla::Atomic<bool> mShutdownRequired; + + int8_t mPriority; + + const bool mIsMainThread; + bool mUseHangMonitor; + mozilla::Atomic<bool, mozilla::Relaxed>* mIsAPoolThreadFree; + + // Set to true if this thread creates a JSRuntime. + bool mCanInvokeJS; + + bool mHasTLSEntry = false; + + // The time the currently running event spent in event queues, and + // when it started running. If no event is running, they are + // TimeDuration() & TimeStamp(). + mozilla::TimeDuration mLastEventDelay; + mozilla::TimeStamp mLastEventStart; + +#ifdef EARLY_BETA_OR_EARLIER + nsCString mNameForWakeupTelemetry; + mozilla::TimeStamp mLastWakeupCheckTime; + uint32_t mWakeupCount = 0; +#endif + + mozilla::PerformanceCounterState mPerformanceCounterState; + + bool mIsInLocalExecutionMode = false; + + mozilla::SimpleTaskQueue mDirectTasks; +}; + +struct nsThreadShutdownContext { + nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread, + NotNull<nsThread*> aJoiningThread, + bool aAwaitingShutdownAck) + : mTerminatingThread(aTerminatingThread), + mTerminatingPRThread(aTerminatingThread->GetPRThread()), + mJoiningThread(aJoiningThread), + mAwaitingShutdownAck(aAwaitingShutdownAck), + mIsMainThreadJoining(NS_IsMainThread()) { + MOZ_COUNT_CTOR(nsThreadShutdownContext); + } + MOZ_COUNTED_DTOR(nsThreadShutdownContext) + + // NB: This will be the last reference. + NotNull<RefPtr<nsThread>> mTerminatingThread; + PRThread* const mTerminatingPRThread; + NotNull<nsThread*> MOZ_UNSAFE_REF( + "Thread manager is holding reference to joining thread") mJoiningThread; + bool mAwaitingShutdownAck; + bool mIsMainThreadJoining; +}; + +// This RAII class controls the duration of the associated nsThread's local +// execution mode and provides access to the local event target. (See +// nsThread::EnterLocalExecution() for details.) It is constructed from an +// nsLocalExecutionRecord, which can only be constructed by nsThread. +class MOZ_RAII nsLocalExecutionGuard final { + public: + MOZ_IMPLICIT nsLocalExecutionGuard( + nsLocalExecutionRecord&& aLocalExecutionRecord); + nsLocalExecutionGuard(const nsLocalExecutionGuard&) = delete; + nsLocalExecutionGuard(nsLocalExecutionGuard&&) = delete; + ~nsLocalExecutionGuard(); + + nsCOMPtr<nsISerialEventTarget> GetEventTarget() const { + return mLocalEventTarget; + } + + private: + mozilla::SynchronizedEventQueue& mEventQueueStack; + nsCOMPtr<nsISerialEventTarget> mLocalEventTarget; + bool& mLocalExecutionFlag; +}; + +class MOZ_TEMPORARY_CLASS nsLocalExecutionRecord final { + private: + friend class nsThread; + friend class nsLocalExecutionGuard; + + nsLocalExecutionRecord(mozilla::SynchronizedEventQueue& aEventQueueStack, + bool& aLocalExecutionFlag) + : mEventQueueStack(aEventQueueStack), + mLocalExecutionFlag(aLocalExecutionFlag) {} + + nsLocalExecutionRecord(nsLocalExecutionRecord&&) = default; + + public: + nsLocalExecutionRecord(const nsLocalExecutionRecord&) = delete; + + private: + mozilla::SynchronizedEventQueue& mEventQueueStack; + bool& mLocalExecutionFlag; +}; + +class MOZ_STACK_CLASS nsThreadEnumerator final { + public: + nsThreadEnumerator() = default; + + auto begin() { return nsThread::ThreadList().begin(); } + auto end() { return nsThread::ThreadList().end(); } + + private: + mozilla::OffTheBooksMutexAutoLock mMal{nsThread::ThreadListMutex()}; +}; + +#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM && \ + defined(_GNU_SOURCE) +# define MOZ_CANARY + +extern int sCanaryOutputFD; +#endif + +#endif // nsThread_h__ diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp new file mode 100644 index 0000000000..c6f3eca640 --- /dev/null +++ b/xpcom/threads/nsThreadManager.cpp @@ -0,0 +1,841 @@ +/* -*- 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 "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/EventQueue.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/Mutex.h" +#include "mozilla/Preferences.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/ThreadEventQueue.h" +#include "mozilla/ThreadLocal.h" +#include "TaskController.h" +#ifdef MOZ_CANARY +# include <fcntl.h> +# include <unistd.h> +#endif + +#include "MainThreadIdlePeriod.h" +#include "InputEventStatistics.h" + +using namespace mozilla; + +static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread; + +bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); } + +class BackgroundEventTarget final : public nsIEventTarget { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + BackgroundEventTarget(); + + nsresult Init(); + + already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue( + const char* aName); + + void BeginShutdown(nsTArray<RefPtr<ShutdownPromise>>&); + void FinishShutdown(); + + private: + ~BackgroundEventTarget() = default; + + nsCOMPtr<nsIThreadPool> mPool; + nsCOMPtr<nsIThreadPool> mIOPool; + + Mutex mMutex; + nsTArray<RefPtr<TaskQueue>> mTaskQueues; +}; + +NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget) + +BackgroundEventTarget::BackgroundEventTarget() + : mMutex("BackgroundEventTarget::mMutex") {} + +nsresult BackgroundEventTarget::Init() { + nsCOMPtr<nsIThreadPool> pool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + nsresult rv = pool->SetName("BackgroundThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 2 makes deadlock during synchronous dispatch less likely. + rv = pool->SetThreadLimit(2); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = pool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + // Initialize the background I/O event target. + nsCOMPtr<nsIThreadPool> ioPool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + rv = ioPool->SetName("BgIOThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 4 makes deadlock during synchronous dispatch less likely. + rv = ioPool->SetThreadLimit(4); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ioPool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = ioPool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + pool.swap(mPool); + ioPool.swap(mIOPool); + + return NS_OK; +} + +NS_IMETHODIMP_(bool) +BackgroundEventTarget::IsOnCurrentThreadInfallible() { + return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread(); +} + +NS_IMETHODIMP +BackgroundEventTarget::IsOnCurrentThread(bool* aValue) { + bool value = false; + if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) { + *aValue = value; + return NS_OK; + } + return mIOPool->IsOnCurrentThread(aValue); +} + +NS_IMETHODIMP +BackgroundEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags) { + // We need to be careful here, because if an event is getting dispatched here + // from within TaskQueue::Runner::Run, it will be dispatched with + // NS_DISPATCH_AT_END, but we might not be running the event on the same + // pool, depending on which pool we were on and the dispatch flags. If we + // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool + // may not process the event in a timely fashion, which can lead to deadlock. + uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK; + bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK); + nsCOMPtr<nsIThreadPool>& pool = mayBlock ? mIOPool : mPool; + + // If we're already running on the pool we want to dispatch to, we can + // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin + // up a new thread. + // + // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues + // like those in the above comment. + if (pool->IsOnCurrentThread()) { + flags |= NS_DISPATCH_AT_END; + } else { + flags &= ~NS_DISPATCH_AT_END; + } + + return pool->Dispatch(std::move(aRunnable), flags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + return Dispatch(runnable.forget(), aFlags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t) { + nsCOMPtr<nsIRunnable> dropRunnable(aRunnable); + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BackgroundEventTarget::BeginShutdown( + nsTArray<RefPtr<ShutdownPromise>>& promises) { + for (auto& queue : mTaskQueues) { + promises.AppendElement(queue->BeginShutdown()); + } +} + +void BackgroundEventTarget::FinishShutdown() { + mPool->Shutdown(); + mIOPool->Shutdown(); +} + +already_AddRefed<nsISerialEventTarget> +BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) { + MutexAutoLock lock(mMutex); + + RefPtr<TaskQueue> queue = new TaskQueue(do_AddRef(this), aName); + mTaskQueues.AppendElement(queue); + + return queue.forget(); +} + +extern "C" { +// This uses the C language linkage because it's exposed to Rust +// via the xpcom/rust/moz_task crate. +bool NS_IsMainThread() { return sTLSIsMainThread.get(); } +} + +void NS_SetMainThread() { + if (!sTLSIsMainThread.init()) { + MOZ_CRASH(); + } + sTLSIsMainThread.set(true); + MOZ_ASSERT(NS_IsMainThread()); + // We initialize the SerialEventTargetGuard's TLS here for simplicity as it + // needs to be initialized around the same time you would initialize + // sTLSIsMainThread. + SerialEventTargetGuard::InitTLS(); +} + +#ifdef DEBUG + +namespace mozilla { + +void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); } + +} // namespace mozilla + +#endif + +typedef nsTArray<NotNull<RefPtr<nsThread>>> nsThreadArray; + +static Atomic<bool> sShutdownComplete; + +//----------------------------------------------------------------------------- + +/* static */ +void nsThreadManager::ReleaseThread(void* aData) { + if (sShutdownComplete) { + // We've already completed shutdown and released the references to all or + // our TLS wrappers. Don't try to release them again. + return; + } + + auto* thread = static_cast<nsThread*>(aData); + + if (thread->mHasTLSEntry) { + thread->mHasTLSEntry = false; + thread->Release(); + } +} + +// statically allocated instance +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::Release() { return 1; } +NS_IMPL_CLASSINFO(nsThreadManager, nullptr, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, + NS_THREADMANAGER_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) +NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) + +namespace { + +// Simple observer to monitor the beginning of the shutdown. +class ShutdownObserveHelper final : public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + + static nsresult Create(ShutdownObserveHelper** aObserver) { + MOZ_ASSERT(aObserver); + + RefPtr<ShutdownObserveHelper> observer = new ShutdownObserveHelper(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return NS_ERROR_FAILURE; + } + + nsresult rv = + obs->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = obs->AddObserver(observer, "content-child-will-shutdown", true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + observer.forget(aObserver); + return NS_OK; + } + + NS_IMETHOD + Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) || + !strcmp(aTopic, "content-child-will-shutdown")) { + mShuttingDown = true; + return NS_OK; + } + + return NS_OK; + } + + bool ShuttingDown() const { return mShuttingDown; } + + private: + explicit ShutdownObserveHelper() : mShuttingDown(false) {} + + ~ShutdownObserveHelper() = default; + + bool mShuttingDown; +}; + +NS_INTERFACE_MAP_BEGIN(ShutdownObserveHelper) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(ShutdownObserveHelper) +NS_IMPL_RELEASE(ShutdownObserveHelper) + +StaticRefPtr<ShutdownObserveHelper> gShutdownObserveHelper; + +} // namespace + +//----------------------------------------------------------------------------- + +/*static*/ nsThreadManager& nsThreadManager::get() { + static nsThreadManager sInstance; + return sInstance; +} + +/* static */ +void nsThreadManager::InitializeShutdownObserver() { + MOZ_ASSERT(!gShutdownObserveHelper); + + RefPtr<ShutdownObserveHelper> observer; + nsresult rv = ShutdownObserveHelper::Create(getter_AddRefs(observer)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + gShutdownObserveHelper = observer; + ClearOnShutdown(&gShutdownObserveHelper); +} + +nsThreadManager::nsThreadManager() + : mCurThreadIndex(0), mMainPRThread(nullptr), mInitialized(false) {} + +nsThreadManager::~nsThreadManager() = default; + +nsresult nsThreadManager::Init() { + // Child processes need to initialize the thread manager before they + // initialize XPCOM in order to set up the crash reporter. This leads to + // situations where we get initialized twice. + if (mInitialized) { + return NS_OK; + } + + if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseThread) == PR_FAILURE) { + return NS_ERROR_FAILURE; + } + +#ifdef MOZ_CANARY + const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + char* env_var_flag = getenv("MOZ_KILL_CANARIES"); + sCanaryOutputFD = + env_var_flag + ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO) + : 0; +#endif + + TaskController::Initialize(); + + // Initialize idle handling. + nsCOMPtr<nsIIdlePeriod> idlePeriod = new MainThreadIdlePeriod(); + TaskController::Get()->SetIdleTaskManager( + new IdleTaskManager(idlePeriod.forget())); + + // Create main thread queue that forwards events to TaskController and + // construct main thread. + UniquePtr<EventQueue> queue = MakeUnique<EventQueue>(true); + + RefPtr<ThreadEventQueue> synchronizedQueue = + new ThreadEventQueue(std::move(queue), true); + + mMainThread = + new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, 0); + + nsresult rv = mMainThread->InitCurrentThread(); + if (NS_FAILED(rv)) { + mMainThread = nullptr; + return rv; + } + + // We need to keep a pointer to the current thread, so we can satisfy + // GetIsMainThread calls that occur post-Shutdown. + mMainThread->GetPRThread(&mMainPRThread); + + // Init AbstractThread. + AbstractThread::InitTLS(); + AbstractThread::InitMainThread(); + + // Initialize the background event target. + RefPtr<BackgroundEventTarget> target(new BackgroundEventTarget()); + + rv = target->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mBackgroundEventTarget = std::move(target); + + mInitialized = true; + + return NS_OK; +} + +void nsThreadManager::Shutdown() { + MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); + + // Prevent further access to the thread manager (no more new threads!) + // + // What happens if shutdown happens before NewThread completes? + // We Shutdown() the new thread, and return error if we've started Shutdown + // between when NewThread started, and when the thread finished initializing + // and registering with ThreadManager. + // + mInitialized = false; + + // Empty the main thread event queue before we begin shutting down threads. + NS_ProcessPendingEvents(mMainThread); + + typedef typename ShutdownPromise::AllPromiseType AllPromise; + typename AllPromise::ResolveOrRejectValue val; + using ResolveValueT = typename AllPromise::ResolveValueType; + using RejectValueT = typename AllPromise::RejectValueType; + + nsTArray<RefPtr<ShutdownPromise>> promises; + mBackgroundEventTarget->BeginShutdown(promises); + + RefPtr<AllPromise> complete = ShutdownPromise::All(mMainThread, promises); + + bool taskQueuesShutdown = false; + + complete->Then( + mMainThread, __func__, + [&](const ResolveValueT& aResolveValue) { + mBackgroundEventTarget->FinishShutdown(); + taskQueuesShutdown = true; + }, + [&](RejectValueT aRejectValue) { + mBackgroundEventTarget->FinishShutdown(); + taskQueuesShutdown = true; + }); + + // Wait for task queues to shutdown, so we don't shut down the underlying + // threads of the background event target in the block below, thereby + // preventing the task queues from emptying, preventing the shutdown promises + // from resolving, and prevent anything checking `taskQueuesShutdown` from + // working. + ::SpinEventLoopUntil([&]() { return taskQueuesShutdown; }, mMainThread); + + { + // We gather the threads from the hashtable into a list, so that we avoid + // holding the enumerator lock while calling nsIThread::Shutdown. + nsTArray<RefPtr<nsThread>> threadsToShutdown; + for (auto* thread : nsThread::Enumerate()) { + if (thread->ShutdownRequired()) { + threadsToShutdown.AppendElement(thread); + } + } + + // It's tempting to walk the list of threads here and tell them each to stop + // accepting new events, but that could lead to badness if one of those + // threads is stuck waiting for a response from another thread. To do it + // right, we'd need some way to interrupt the threads. + // + // Instead, we process events on the current thread while waiting for + // threads to shutdown. This means that we have to preserve a mostly + // functioning world until such time as the threads exit. + + // Shutdown all threads that require it (join with threads that we created). + for (auto& thread : threadsToShutdown) { + thread->Shutdown(); + } + } + + // NB: It's possible that there are events in the queue that want to *start* + // an asynchronous shutdown. But we have already shutdown the threads above, + // so there's no need to worry about them. We only have to wait for all + // in-flight asynchronous thread shutdowns to complete. + mMainThread->WaitForAllAsynchronousShutdowns(); + + // In case there are any more events somehow... + NS_ProcessPendingEvents(mMainThread); + + // There are no more background threads at this point. + + // Normally thread shutdown clears the observer for the thread, but since the + // main thread is special we do it manually here after we're sure all events + // have been processed. + mMainThread->SetObserver(nullptr); + + mBackgroundEventTarget = nullptr; + + // Release main thread object. + mMainThread = nullptr; + + // Remove the TLS entry for the main thread. + PR_SetThreadPrivate(mCurThreadIndex, nullptr); + + { + // Cleanup the last references to any threads which haven't shut down yet. + nsTArray<RefPtr<nsThread>> threads; + for (auto* thread : nsThread::Enumerate()) { + if (thread->mHasTLSEntry) { + threads.AppendElement(dont_AddRef(thread)); + thread->mHasTLSEntry = false; + } + } + } + + // xpcshell tests sometimes leak the main thread. They don't enable leak + // checking, so that doesn't cause the test to fail, but leaving the entry in + // the thread list triggers an assertion, which does. + nsThread::ClearThreadList(); + + sShutdownComplete = true; +} + +void nsThreadManager::RegisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + aThread.AddRef(); // for TLS entry + aThread.mHasTLSEntry = true; + PR_SetThreadPrivate(mCurThreadIndex, &aThread); +} + +void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + PR_SetThreadPrivate(mCurThreadIndex, nullptr); + // Ref-count balanced via ReleaseThread +} + +nsThread* nsThreadManager::CreateCurrentThread( + SynchronizedEventQueue* aQueue, nsThread::MainThreadFlag aMainThread) { + // Make sure we don't have an nsThread yet. + MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex)); + + if (!mInitialized) { + return nullptr; + } + + RefPtr<nsThread> thread = new nsThread(WrapNotNull(aQueue), aMainThread, 0); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags) { + if (!mInitialized) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIEventTarget> backgroundTarget(mBackgroundEventTarget); + return backgroundTarget->Dispatch(aEvent, aDispatchFlags); +} + +already_AddRefed<nsISerialEventTarget> +nsThreadManager::CreateBackgroundTaskQueue(const char* aName) { + if (!mInitialized) { + return nullptr; + } + + return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName); +} + +nsThread* nsThreadManager::GetCurrentThread() { + // read thread local storage + void* data = PR_GetThreadPrivate(mCurThreadIndex); + if (data) { + return static_cast<nsThread*>(data); + } + + if (!mInitialized) { + return nullptr; + } + + // OK, that's fine. We'll dynamically create one :-) + // + // We assume that if we're implicitly creating a thread here that it doesn't + // want an event queue. Any thread which wants an event queue should + // explicitly create its nsThread wrapper. + RefPtr<nsThread> thread = new nsThread(); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +bool nsThreadManager::IsNSThread() const { + if (!mInitialized) { + return false; + } + if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) { + return thread->EventQueue(); + } + return false; +} + +NS_IMETHODIMP +nsThreadManager::NewThread(uint32_t aCreationFlags, uint32_t aStackSize, + nsIThread** aResult) { + return NewNamedThread(""_ns, aStackSize, aResult); +} + +NS_IMETHODIMP +nsThreadManager::NewNamedThread(const nsACString& aName, uint32_t aStackSize, + nsIThread** aResult) { + // Note: can be called from arbitrary threads + + // No new threads during Shutdown + if (NS_WARN_IF(!mInitialized)) { + return NS_ERROR_NOT_INITIALIZED; + } + + TimeStamp startTime = TimeStamp::Now(); + + RefPtr<ThreadEventQueue> queue = + new ThreadEventQueue(MakeUnique<EventQueue>()); + RefPtr<nsThread> thr = + new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aStackSize); + nsresult rv = + thr->Init(aName); // Note: blocks until the new thread has been set up + if (NS_FAILED(rv)) { + return rv; + } + + // At this point, we expect that the thread has been registered in + // mThreadByPRThread; however, it is possible that it could have also been + // replaced by now, so we cannot really assert that it was added. Instead, + // kill it if we entered Shutdown() during/before Init() + + if (NS_WARN_IF(!mInitialized)) { + if (thr->ShutdownRequired()) { + thr->Shutdown(); // ok if it happens multiple times + } + return NS_ERROR_NOT_INITIALIZED; + } + + PROFILER_MARKER_TEXT( + "NewThread", OTHER, + MarkerOptions(MarkerStack::Capture(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + if (!NS_IsMainThread()) { + PROFILER_MARKER_TEXT( + "NewThread (non-main thread)", OTHER, + MarkerOptions(MarkerStack::Capture(), MarkerThreadId::MainThread(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + } + + thr.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + if (!NS_IsMainThread()) { + NS_WARNING( + "Called GetMainThread but there isn't a main thread and " + "we're not the main thread."); + } + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aResult = mMainThread); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetCurrentThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = GetCurrentThread(); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntil(nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aCondition, false); +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilOrShutdown( + nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aCondition, true); +} + +nsresult nsThreadManager::SpinEventLoopUntilInternal( + nsINestedEventLoopCondition* aCondition, bool aCheckingShutdown) { + nsCOMPtr<nsINestedEventLoopCondition> condition(aCondition); + nsresult rv = NS_OK; + + // Nothing to do if already shutting down. Note that gShutdownObserveHelper is + // nullified on shutdown. + if (aCheckingShutdown && + (!gShutdownObserveHelper || gShutdownObserveHelper->ShuttingDown())) { + return NS_OK; + } + + if (!mozilla::SpinEventLoopUntil([&]() -> bool { + // Shutting down is started. + if (aCheckingShutdown && (!gShutdownObserveHelper || + gShutdownObserveHelper->ShuttingDown())) { + return true; + } + + bool isDone = false; + rv = condition->IsDone(&isDone); + // JS failure should be unusual, but we need to stop and propagate + // the error back to the caller. + if (NS_FAILED(rv)) { + return true; + } + + return isDone; + })) { + // We stopped early for some reason, which is unexpected. + return NS_ERROR_UNEXPECTED; + } + + // If we exited when the condition told us to, we need to return whether + // the condition encountered failure when executing. + return rv; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilEmpty() { + nsIThread* thread = NS_GetCurrentThread(); + + while (NS_HasPendingEvents(thread)) { + (void)NS_ProcessNextEvent(thread, false); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) { + nsCOMPtr<nsIEventTarget> target = GetMainThreadSerialEventTarget(); + target.forget(aTarget); + return NS_OK; +} + +uint32_t nsThreadManager::GetHighestNumberOfThreads() { + return nsThread::MaxActiveThreads(); +} + +NS_IMETHODIMP +nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority, + uint8_t aArgc) { + // Note: C++ callers should instead use NS_DispatchToMainThread. + MOZ_ASSERT(NS_IsMainThread()); + + // Keep this functioning during Shutdown + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + // If aPriority wasn't explicitly passed, that means it should be treated as + // PRIORITY_NORMAL. + if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) { + nsCOMPtr<nsIRunnable> event(aEvent); + return mMainThread->DispatchFromScript( + new PrioritizableRunnable(event.forget(), aPriority), 0); + } + return mMainThread->DispatchFromScript(aEvent, 0); +} + +void nsThreadManager::EnableMainThreadEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputEventStatistics::Get().SetEnable(true); + InputTaskManager::Get()->EnableInputEventPrioritization(); +} + +void nsThreadManager::FlushInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->FlushInputEventPrioritization(); +} + +void nsThreadManager::SuspendInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->SuspendInputEventPrioritization(); +} + +void nsThreadManager::ResumeInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->ResumeInputEventPrioritization(); +} + +// static +bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() { + MOZ_ASSERT(NS_IsMainThread()); + bool retVal = false; + if (get().mMainThread) { + get().mMainThread->HasPendingHighPriorityEvents(&retVal); + } + return retVal; +} + +NS_IMETHODIMP +nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent, + uint32_t aTimeout) { + // Note: C++ callers should instead use NS_DispatchToThreadQueue or + // NS_DispatchToCurrentThreadQueue. + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIRunnable> event(aEvent); + if (aTimeout) { + return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread, + EventQueuePriority::Idle); + } + + return NS_DispatchToThreadQueue(event.forget(), mMainThread, + EventQueuePriority::Idle); +} diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h new file mode 100644 index 0000000000..fa223702aa --- /dev/null +++ b/xpcom/threads/nsThreadManager.h @@ -0,0 +1,111 @@ +/* -*- 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/. */ + +#ifndef nsThreadManager_h__ +#define nsThreadManager_h__ + +#include "nsIThreadManager.h" +#include "nsThread.h" + +class nsIRunnable; +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThread; + +namespace mozilla { +class IdleTaskManager; +class SynchronizedEventQueue; +} // namespace mozilla + +class BackgroundEventTarget; + +class nsThreadManager : public nsIThreadManager { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITHREADMANAGER + + static nsThreadManager& get(); + + static void InitializeShutdownObserver(); + + nsresult Init(); + + // Shutdown all threads. This function should only be called on the main + // thread of the application process. + void Shutdown(); + + // Called by nsThread to inform the ThreadManager it exists. This method + // must be called when the given thread is the current thread. + void RegisterCurrentThread(nsThread& aThread); + + // Called by nsThread to inform the ThreadManager it is going away. This + // method must be called when the given thread is the current thread. + void UnregisterCurrentThread(nsThread& aThread); + + // Returns the current thread. Returns null if OOM or if ThreadManager isn't + // initialized. Creates the nsThread if one does not exist yet. + nsThread* GetCurrentThread(); + + // Returns true iff the currently running thread has an nsThread associated + // with it (ie; whether this is a thread that we can dispatch runnables to). + bool IsNSThread() const; + + // CreateCurrentThread sets up an nsThread for the current thread. It uses the + // event queue and main thread flags passed in. It should only be called once + // for the current thread. After it returns, GetCurrentThread() will return + // the thread that was created. GetCurrentThread() will also create a thread + // (lazily), but it doesn't allow the queue or main-thread attributes to be + // specified. + nsThread* CreateCurrentThread(mozilla::SynchronizedEventQueue* aQueue, + nsThread::MainThreadFlag aMainThread); + + nsresult DispatchToBackgroundThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags); + + already_AddRefed<nsISerialEventTarget> CreateBackgroundTaskQueue( + const char* aName); + + // Returns the maximal number of threads that have been in existence + // simultaneously during the execution of the thread manager. + uint32_t GetHighestNumberOfThreads(); + + ~nsThreadManager(); + + void EnableMainThreadEventPrioritization(); + void FlushInputEventPrioritization(); + void SuspendInputEventPrioritization(); + void ResumeInputEventPrioritization(); + + static bool MainThreadHasPendingHighPriorityEvents(); + + nsIThread* GetMainThreadWeak() { return mMainThread; } + + private: + nsThreadManager(); + + nsresult SpinEventLoopUntilInternal(nsINestedEventLoopCondition* aCondition, + bool aCheckingShutdown); + + static void ReleaseThread(void* aData); + + unsigned mCurThreadIndex; // thread-local-storage index + RefPtr<mozilla::IdleTaskManager> mIdleTaskManager; + RefPtr<nsThread> mMainThread; + PRThread* mMainPRThread; + mozilla::Atomic<bool, mozilla::SequentiallyConsistent> mInitialized; + + // Shared event target used for background runnables. + RefPtr<BackgroundEventTarget> mBackgroundEventTarget; +}; + +#define NS_THREADMANAGER_CID \ + { /* 7a4204c6-e45a-4c37-8ebb-6709a22c917c */ \ + 0x7a4204c6, 0xe45a, 0x4c37, { \ + 0x8e, 0xbb, 0x67, 0x09, 0xa2, 0x2c, 0x91, 0x7c \ + } \ + } + +#endif // nsThreadManager_h__ diff --git a/xpcom/threads/nsThreadPool.cpp b/xpcom/threads/nsThreadPool.cpp new file mode 100644 index 0000000000..0d7aca27ec --- /dev/null +++ b/xpcom/threads/nsThreadPool.cpp @@ -0,0 +1,627 @@ +/* -*- 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 "nsCOMArray.h" +#include "ThreadDelay.h" +#include "nsThreadPool.h" +#include "nsThreadManager.h" +#include "nsThread.h" +#include "nsMemory.h" +#include "prinrval.h" +#include "mozilla/Logging.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsThreadSyncDispatch.h" + +#include <mutex> + +using namespace mozilla; + +static LazyLogModule sThreadPoolLog("nsThreadPool"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args) + +static MOZ_THREAD_LOCAL(nsThreadPool*) gCurrentThreadPool; + +// DESIGN: +// o Allocate anonymous threads. +// o Use nsThreadPool::Run as the main routine for each thread. +// o Each thread waits on the event queue's monitor, checking for +// pending events and rescheduling itself as an idle thread. + +#define DEFAULT_THREAD_LIMIT 4 +#define DEFAULT_IDLE_THREAD_LIMIT 1 +#define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60) + +NS_IMPL_ISUPPORTS_INHERITED(nsThreadPool, Runnable, nsIThreadPool, + nsIEventTarget) + +nsThreadPool::nsThreadPool() + : Runnable("nsThreadPool"), + mMutex("[nsThreadPool.mMutex]"), + mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]"), + mThreadLimit(DEFAULT_THREAD_LIMIT), + mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT), + mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT), + mIdleCount(0), + mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE), + mShutdown(false), + mRegressiveMaxIdleTime(false), + mIsAPoolThreadFree(true) { + static std::once_flag flag; + std::call_once(flag, [] { gCurrentThreadPool.infallibleInit(); }); + + LOG(("THRD-P(%p) constructor!!!\n", this)); +} + +nsThreadPool::~nsThreadPool() { + // Threads keep a reference to the nsThreadPool until they return from Run() + // after removing themselves from mThreads. + MOZ_ASSERT(mThreads.IsEmpty()); +} + +nsresult nsThreadPool::PutEvent(nsIRunnable* aEvent) { + nsCOMPtr<nsIRunnable> event(aEvent); + return PutEvent(event.forget(), 0); +} + +nsresult nsThreadPool::PutEvent(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + // Avoid spawning a new thread while holding the event queue lock... + + bool spawnThread = false; + uint32_t stackSize = 0; + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(), + mThreadLimit)); + MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops"); + + // Make sure we have a thread to service this event. + if (mThreads.Count() < (int32_t)mThreadLimit && + !(aFlags & NS_DISPATCH_AT_END) && + // Spawn a new thread if we don't have enough idle threads to serve + // pending events immediately. + mEvents.Count(lock) >= mIdleCount) { + spawnThread = true; + } + + nsCOMPtr<nsIRunnable> event(aEvent); + LogRunnable::LogDispatch(event); + mEvents.PutEvent(event.forget(), EventQueuePriority::Normal, lock); + mEventsAvailable.Notify(); + stackSize = mStackSize; + } + + auto delay = MakeScopeExit([&]() { + // Delay to encourage the receiving task to run before we do work. + DelayForChaosMode(ChaosFeature::TaskDispatching, 1000); + }); + + LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread)); + if (!spawnThread) { + return NS_OK; + } + + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName(mName), + getter_AddRefs(thread), nullptr, stackSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + + bool killThread = false; + { + MutexAutoLock lock(mMutex); + if (mShutdown) { + killThread = true; + } else if (mThreads.Count() < (int32_t)mThreadLimit) { + mThreads.AppendObject(thread); + if (mThreads.Count() >= (int32_t)mThreadLimit) { + mIsAPoolThreadFree = false; + } + } else { + // Someone else may have also been starting a thread + killThread = true; // okay, we don't need this thread anymore + } + } + LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread)); + if (killThread) { + // We never dispatched any events to the thread, so we can shut it down + // asynchronously without worrying about anything. + ShutdownThread(thread); + } else { + thread->Dispatch(this, NS_DISPATCH_NORMAL); + } + + return NS_OK; +} + +void nsThreadPool::ShutdownThread(nsIThread* aThread) { + LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread)); + + // This is either called by a threadpool thread that is out of work, or + // a thread that attempted to create a threadpool thread and raced in + // such a way that the newly created thread is no longer necessary. + // In the first case, we must go to another thread to shut aThread down + // (because it is the current thread). In the second case, we cannot + // synchronously shut down the current thread (because then Dispatch() would + // spin the event loop, and that could blow up the world), and asynchronous + // shutdown requires this thread have an event loop (and it may not, see bug + // 10204784). The simplest way to cover all cases is to asynchronously + // shutdown aThread from the main thread. + SchedulerGroup::Dispatch( + TaskCategory::Other, + NewRunnableMethod("nsIThread::AsyncShutdown", aThread, + &nsIThread::AsyncShutdown)); +} + +// This event 'runs' for the lifetime of the worker thread. The actual +// eventqueue is mEvents, and is shared by all the worker threads. This +// means that the set of threads together define the delay seen by a new +// event sent to the pool. +// +// To model the delay experienced by the pool, we can have each thread in +// the pool report 0 if it's idle OR if the pool is below the threadlimit; +// or otherwise the current event's queuing delay plus current running +// time. +// +// To reconstruct the delays for the pool, the profiler can look at all the +// threads that are part of a pool (pools have defined naming patterns that +// can be user to connect them). If all threads have delays at time X, +// that means that all threads saturated at that point and any event +// dispatched to the pool would get a delay. +// +// The delay experienced by an event dispatched when all pool threads are +// busy is based on the calculations shown in platform.cpp. Run that +// algorithm for each thread in the pool, and the delay at time X is the +// longest value for time X of any of the threads, OR the time from X until +// any one of the threads reports 0 (i.e. it's not busy), whichever is +// shorter. + +// In order to record this when the profiler samples threads in the pool, +// each thread must (effectively) override GetRunnningEventDelay, by +// resetting the mLastEventDelay/Start values in the nsThread when we start +// to run an event (or when we run out of events to run). Note that handling +// the shutdown of a thread may be a little tricky. + +NS_IMETHODIMP +nsThreadPool::Run() { + LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading())); + + nsCOMPtr<nsIThread> current; + nsThreadManager::get().GetCurrentThread(getter_AddRefs(current)); + + bool shutdownThreadOnExit = false; + bool exitThread = false; + bool wasIdle = false; + TimeStamp idleSince; + + // This thread is an nsThread created below with NS_NewNamedThread() + static_cast<nsThread*>(current.get()) + ->SetPoolThreadFreePtr(&mIsAPoolThreadFree); + + nsCOMPtr<nsIThreadPoolListener> listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + } + + if (listener) { + listener->OnThreadCreated(); + } + + MOZ_ASSERT(!gCurrentThreadPool.get()); + gCurrentThreadPool.set(this); + + do { + nsCOMPtr<nsIRunnable> event; + TimeDuration delay; + { + MutexAutoLock lock(mMutex); + + event = mEvents.GetEvent(lock, &delay); + if (!event) { + TimeStamp now = TimeStamp::Now(); + uint32_t idleTimeoutDivider = + (mIdleCount && mRegressiveMaxIdleTime) ? mIdleCount : 1; + TimeDuration timeout = TimeDuration::FromMilliseconds( + static_cast<double>(mIdleThreadTimeout) / idleTimeoutDivider); + + // If we are shutting down, then don't keep any idle threads + if (mShutdown) { + exitThread = true; + } else { + if (wasIdle) { + // if too many idle threads or idle for too long, then bail. + if (mIdleCount > mIdleThreadLimit || + (mIdleThreadTimeout != UINT32_MAX && + (now - idleSince) >= timeout)) { + exitThread = true; + } + } else { + // if would be too many idle threads... + if (mIdleCount == mIdleThreadLimit) { + exitThread = true; + } else { + ++mIdleCount; + idleSince = now; + wasIdle = true; + } + } + } + + if (exitThread) { + if (wasIdle) { + --mIdleCount; + } + shutdownThreadOnExit = mThreads.RemoveObject(current); + + // keep track if there are threads available to start + mIsAPoolThreadFree = (mThreads.Count() < (int32_t)mThreadLimit); + } else { + current->SetRunningEventDelay(TimeDuration(), TimeStamp()); + + AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE); + + TimeDuration delta = timeout - (now - idleSince); + LOG(("THRD-P(%p) %s waiting [%f]\n", this, mName.BeginReading(), + delta.ToMilliseconds())); + mEventsAvailable.Wait(delta); + LOG(("THRD-P(%p) done waiting\n", this)); + } + } else if (wasIdle) { + wasIdle = false; + --mIdleCount; + } + } + if (event) { + LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(), + event.get())); + + // Delay event processing to encourage whoever dispatched this event + // to run. + DelayForChaosMode(ChaosFeature::TaskRunning, 1000); + + // We'll handle the case of unstarted threads available + // when we sample. + current->SetRunningEventDelay(delay, TimeStamp::Now()); + + LogRunnable::Run log(event); + event->Run(); + // To cover the event's destructor code in the LogRunnable span + event = nullptr; + } + } while (!exitThread); + + if (listener) { + listener->OnThreadShuttingDown(); + } + + MOZ_ASSERT(gCurrentThreadPool.get() == this); + gCurrentThreadPool.set(nullptr); + + if (shutdownThreadOnExit) { + ShutdownThread(current); + } + + LOG(("THRD-P(%p) leave\n", this)); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThreadPool::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) { + LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags)); + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (aFlags & DISPATCH_SYNC) { + nsCOMPtr<nsIThread> thread; + nsThreadManager::get().GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<nsThreadSyncDispatch> wrapper = + new nsThreadSyncDispatch(thread.forget(), std::move(aEvent)); + PutEvent(wrapper); + + SpinEventLoopUntil( + [&, wrapper]() -> bool { return !wrapper->IsPending(); }); + } else { + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END, + "unexpected dispatch flags"); + PutEvent(std::move(aEvent), aFlags); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP_(bool) +nsThreadPool::IsOnCurrentThreadInfallible() { + return gCurrentThreadPool.get() == this; +} + +NS_IMETHODIMP +nsThreadPool::IsOnCurrentThread(bool* aResult) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aResult = IsOnCurrentThreadInfallible(); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::Shutdown() { + nsCOMArray<nsIThread> threads; + nsCOMPtr<nsIThreadPoolListener> listener; + { + MutexAutoLock lock(mMutex); + mShutdown = true; + mEventsAvailable.NotifyAll(); + + threads.AppendObjects(mThreads); + mThreads.Clear(); + + // Swap in a null listener so that we release the listener at the end of + // this method. The listener will be kept alive as long as the other threads + // that were created when it was set. + mListener.swap(listener); + } + + // It's important that we shutdown the threads while outside the event queue + // monitor. Otherwise, we could end up dead-locking. + + for (int32_t i = 0; i < threads.Count(); ++i) { + threads[i]->Shutdown(); + } + + return NS_OK; +} + +template <typename Pred> +static void SpinMTEventLoopUntil(Pred&& aPredicate, nsIThread* aThread, + TimeDuration aTimeout) { + MOZ_ASSERT(NS_IsMainThread(), "Must be run on the main thread"); + + // From a latency perspective, spinning the event loop is like leaving script + // and returning to the event loop. Tell the watchdog we stopped running + // script (until we return). + mozilla::Maybe<xpc::AutoScriptActivity> asa; + asa.emplace(false); + + TimeStamp deadline = TimeStamp::Now() + aTimeout; + while (!aPredicate() && TimeStamp::Now() < deadline) { + if (!NS_ProcessNextEvent(aThread, false)) { + PR_Sleep(PR_MillisecondsToInterval(1)); + } + } +} + +NS_IMETHODIMP +nsThreadPool::ShutdownWithTimeout(int32_t aTimeoutMs) { + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMArray<nsIThread> threads; + nsCOMPtr<nsIThreadPoolListener> listener; + { + MutexAutoLock lock(mMutex); + mShutdown = true; + mEventsAvailable.NotifyAll(); + + threads.AppendObjects(mThreads); + mThreads.Clear(); + + // Swap in a null listener so that we release the listener at the end of + // this method. The listener will be kept alive as long as the other threads + // that were created when it was set. + mListener.swap(listener); + } + + // IMPORTANT! Never dereference these pointers, as the objects may go away at + // any time. We just use the pointers values for comparison, to check if the + // thread has been shut down or not. + nsTArray<nsThreadShutdownContext*> contexts; + + // It's important that we shutdown the threads while outside the event queue + // monitor. Otherwise, we could end up dead-locking. + for (int32_t i = 0; i < threads.Count(); ++i) { + // Shutdown async + nsThreadShutdownContext* maybeContext = + static_cast<nsThread*>(threads[i])->ShutdownInternal(false); + contexts.AppendElement(maybeContext); + } + + NotNull<nsThread*> currentThread = + WrapNotNull(nsThreadManager::get().GetCurrentThread()); + + // We spin the event loop until all of the threads in the thread pool + // have shut down, or the timeout expires. + SpinMTEventLoopUntil( + [&]() { + for (nsIThread* thread : threads) { + if (static_cast<nsThread*>(thread)->mThread) { + return false; + } + } + return true; + }, + currentThread, TimeDuration::FromMilliseconds(aTimeoutMs)); + + // For any threads that have not shutdown yet, we need to remove them from + // mRequestedShutdownContexts so the thread manager does not wait for them + // at shutdown. + static const nsThread::ShutdownContextsComp comparator{}; + for (int32_t i = 0; i < threads.Count(); ++i) { + nsThread* thread = static_cast<nsThread*>(threads[i]); + // If mThread is not null on the thread it means that it hasn't shutdown + // context[i] corresponds to thread[i] + if (thread->mThread && contexts[i]) { + auto index = currentThread->mRequestedShutdownContexts.IndexOf( + contexts[i], 0, comparator); + if (index != nsThread::ShutdownContexts::NoIndex) { + // We must leak the shutdown context just in case the leaked thread + // does get unstuck and completes before the main thread is done. + Unused << currentThread->mRequestedShutdownContexts[index].release(); + currentThread->mRequestedShutdownContexts.RemoveElementAt(index); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetThreadLimit(uint32_t* aValue) { + *aValue = mThreadLimit; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetThreadLimit(uint32_t aValue) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue)); + mThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { + mIdleThreadLimit = mThreadLimit; + } + + if (static_cast<uint32_t>(mThreads.Count()) > mThreadLimit) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) { + *aValue = mIdleThreadLimit; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadLimit(uint32_t aValue) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue)); + mIdleThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { + mIdleThreadLimit = mThreadLimit; + } + + // Do we need to kill some idle threads? + if (mIdleCount > mIdleThreadLimit) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) { + *aValue = mIdleThreadTimeout; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadTimeout(uint32_t aValue) { + MutexAutoLock lock(mMutex); + uint32_t oldTimeout = mIdleThreadTimeout; + mIdleThreadTimeout = aValue; + + // Do we need to notify any idle threads that their sleep time has shortened? + if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue) { + *aValue = mRegressiveMaxIdleTime; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadTimeoutRegressive(bool aValue) { + MutexAutoLock lock(mMutex); + bool oldRegressive = mRegressiveMaxIdleTime; + mRegressiveMaxIdleTime = aValue; + + // Would setting regressive timeout effect idle threads? + if (mRegressiveMaxIdleTime > oldRegressive && mIdleCount > 1) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetThreadStackSize(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mStackSize; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetThreadStackSize(uint32_t aValue) { + MutexAutoLock lock(mMutex); + mStackSize = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetListener(nsIThreadPoolListener** aListener) { + MutexAutoLock lock(mMutex); + NS_IF_ADDREF(*aListener = mListener); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetListener(nsIThreadPoolListener* aListener) { + nsCOMPtr<nsIThreadPoolListener> swappedListener(aListener); + { + MutexAutoLock lock(mMutex); + mListener.swap(swappedListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetName(const nsACString& aName) { + { + MutexAutoLock lock(mMutex); + if (mThreads.Count()) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + mName = aName; + return NS_OK; +} diff --git a/xpcom/threads/nsThreadPool.h b/xpcom/threads/nsThreadPool.h new file mode 100644 index 0000000000..7bdafde57c --- /dev/null +++ b/xpcom/threads/nsThreadPool.h @@ -0,0 +1,63 @@ +/* -*- 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/. */ + +#ifndef nsThreadPool_h__ +#define nsThreadPool_h__ + +#include "nsIThreadPool.h" +#include "nsIRunnable.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/CondVar.h" +#include "mozilla/EventQueue.h" +#include "mozilla/Mutex.h" + +class nsIThread; + +class nsThreadPool final : public mozilla::Runnable, public nsIThreadPool { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREADPOOL + NS_DECL_NSIRUNNABLE + + nsThreadPool(); + + private: + ~nsThreadPool(); + + void ShutdownThread(nsIThread* aThread); + nsresult PutEvent(nsIRunnable* aEvent); + nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags); + + nsCOMArray<nsIThread> mThreads; + mozilla::Mutex mMutex; + mozilla::CondVar mEventsAvailable; + mozilla::EventQueue mEvents; + uint32_t mThreadLimit; + uint32_t mIdleThreadLimit; + uint32_t mIdleThreadTimeout; + uint32_t mIdleCount; + uint32_t mStackSize; + nsCOMPtr<nsIThreadPoolListener> mListener; + bool mShutdown; + bool mRegressiveMaxIdleTime; + mozilla::Atomic<bool, mozilla::Relaxed> mIsAPoolThreadFree; + nsCString mName; + nsThreadPoolNaming mThreadNaming; +}; + +#define NS_THREADPOOL_CID \ + { /* 547ec2a8-315e-4ec4-888e-6e4264fe90eb */ \ + 0x547ec2a8, 0x315e, 0x4ec4, { \ + 0x88, 0x8e, 0x6e, 0x42, 0x64, 0xfe, 0x90, 0xeb \ + } \ + } + +#endif // nsThreadPool_h__ diff --git a/xpcom/threads/nsThreadSyncDispatch.h b/xpcom/threads/nsThreadSyncDispatch.h new file mode 100644 index 0000000000..7e0e121234 --- /dev/null +++ b/xpcom/threads/nsThreadSyncDispatch.h @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +#ifndef nsThreadSyncDispatch_h_ +#define nsThreadSyncDispatch_h_ + +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" + +#include "nsThreadUtils.h" +#include "LeakRefPtr.h" + +class nsThreadSyncDispatch : public mozilla::Runnable { + public: + nsThreadSyncDispatch(already_AddRefed<nsIEventTarget> aOrigin, + already_AddRefed<nsIRunnable>&& aTask) + : Runnable("nsThreadSyncDispatch"), + mOrigin(aOrigin), + mSyncTask(std::move(aTask)), + mIsPending(true) {} + + bool IsPending() { + // This is an atomic acquire on the origin thread. + return mIsPending; + } + + private: + NS_IMETHOD Run() override { + if (nsCOMPtr<nsIRunnable> task = mSyncTask.take()) { + MOZ_ASSERT(!mSyncTask); + + mozilla::DebugOnly<nsresult> result = task->Run(); + MOZ_ASSERT(NS_SUCCEEDED(result), "task in sync dispatch should not fail"); + + // We must release the task here to ensure that when the original + // thread is unblocked, this task has been released. + task = nullptr; + + // This is an atomic release on the target thread. + mIsPending = false; + + // unblock the origin thread + mOrigin->Dispatch(this, NS_DISPATCH_NORMAL); + } + + return NS_OK; + } + + nsCOMPtr<nsIEventTarget> mOrigin; + // The task is leaked by default when Run() is not called, because + // otherwise we may release it in an incorrect thread. + mozilla::LeakRefPtr<nsIRunnable> mSyncTask; + mozilla::Atomic<bool, mozilla::ReleaseAcquire> mIsPending; +}; + +#endif // nsThreadSyncDispatch_h_ diff --git a/xpcom/threads/nsThreadUtils.cpp b/xpcom/threads/nsThreadUtils.cpp new file mode 100644 index 0000000000..bbca11b6b3 --- /dev/null +++ b/xpcom/threads/nsThreadUtils.cpp @@ -0,0 +1,878 @@ +/* -*- 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 "nsThreadUtils.h" + +#include "chrome/common/ipc_message.h" // for IPC::Message +#include "LeakRefPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" +#include "nsExceptionHandler.h" +#include "nsITimer.h" +#include "nsTimerImpl.h" +#include "prsystem.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsThreadManager.h" +#else +# include "nsIThreadManager.h" +# include "nsServiceManagerUtils.h" +# include "nsXPCOMCIDInternal.h" +#endif + +#ifdef XP_WIN +# include <windows.h> +#elif defined(XP_MACOSX) +# include <sys/resource.h> +#endif + +#if defined(ANDROID) +# include <sys/prctl.h> +#endif + +static mozilla::LazyLogModule sEventDispatchAndRunLog("events"); +#ifdef LOG1 +# undef LOG1 +#endif +#define LOG1(args) \ + MOZ_LOG(sEventDispatchAndRunLog, mozilla::LogLevel::Error, args) +#define LOG1_ENABLED() \ + MOZ_LOG_TEST(sEventDispatchAndRunLog, mozilla::LogLevel::Error) + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(TailDispatchingTarget, nsIEventTarget, nsISerialEventTarget) + +#ifndef XPCOM_GLUE_AVOID_NSPR + +NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod) + +NS_IMETHODIMP +IdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) { + *aIdleDeadline = TimeStamp(); + return NS_OK; +} + +// NS_IMPL_NAMED_* relies on the mName field, which is not present on +// release or beta. Instead, fall back to using "Runnable" for all +// runnables. +# ifndef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMPL_ISUPPORTS(Runnable, nsIRunnable) +# else +NS_IMPL_NAMED_ADDREF(Runnable, mName) +NS_IMPL_NAMED_RELEASE(Runnable, mName) +NS_IMPL_QUERY_INTERFACE(Runnable, nsIRunnable, nsINamed) +# endif + +NS_IMETHODIMP +Runnable::Run() { + // Do nothing + return NS_OK; +} + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +Runnable::GetName(nsACString& aName) { + if (mName) { + aName.AssignASCII(mName); + } else { + aName.Truncate(); + } + return NS_OK; +} +# endif + +NS_IMPL_ISUPPORTS_INHERITED(DiscardableRunnable, Runnable, + nsIDiscardableRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(CancelableRunnable, DiscardableRunnable, + nsICancelableRunnable) + +void CancelableRunnable::OnDiscard() { + // Tasks that implement Cancel() can be safely cleaned up if it turns out + // that the task will not run. + (void)NS_WARN_IF(NS_FAILED(Cancel())); +} + +NS_IMPL_ISUPPORTS_INHERITED(IdleRunnable, DiscardableRunnable, nsIIdleRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(CancelableIdleRunnable, CancelableRunnable, + nsIIdleRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(PrioritizableRunnable, Runnable, + nsIRunnablePriority) + +PrioritizableRunnable::PrioritizableRunnable( + already_AddRefed<nsIRunnable>&& aRunnable, uint32_t aPriority) + // Real runnable name is managed by overridding the GetName function. + : Runnable("PrioritizableRunnable"), + mRunnable(std::move(aRunnable)), + mPriority(aPriority) { +# if DEBUG + nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable); + MOZ_ASSERT(!runnablePrio); +# endif +} + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +PrioritizableRunnable::GetName(nsACString& aName) { + // Try to get a name from the underlying runnable. + nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable); + if (named) { + named->GetName(aName); + } + return NS_OK; +} +# endif + +NS_IMETHODIMP +PrioritizableRunnable::Run() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + return mRunnable->Run(); +} + +NS_IMETHODIMP +PrioritizableRunnable::GetPriority(uint32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +already_AddRefed<nsIRunnable> mozilla::CreateMediumHighRunnable( + already_AddRefed<nsIRunnable>&& aRunnable) { + nsCOMPtr<nsIRunnable> runnable = new PrioritizableRunnable( + std::move(aRunnable), nsIRunnablePriority::PRIORITY_MEDIUMHIGH); + return runnable.forget(); +} + +#endif // XPCOM_GLUE_AVOID_NSPR + +//----------------------------------------------------------------------------- + +nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult, + nsIRunnable* aInitialEvent, uint32_t aStackSize) { + nsCOMPtr<nsIRunnable> event = aInitialEvent; + return NS_NewNamedThread(aName, aResult, event.forget(), aStackSize); +} + +nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult, + already_AddRefed<nsIRunnable> aInitialEvent, + uint32_t aStackSize) { + nsCOMPtr<nsIRunnable> event = std::move(aInitialEvent); + nsCOMPtr<nsIThread> thread; +#ifdef MOZILLA_INTERNAL_API + nsresult rv = nsThreadManager::get().nsThreadManager::NewNamedThread( + aName, aStackSize, getter_AddRefs(thread)); +#else + nsresult rv; + nsCOMPtr<nsIThreadManager> mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mgr->NewNamedThread(aName, aStackSize, getter_AddRefs(thread)); +#endif + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (event) { + rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + *aResult = nullptr; + thread.swap(*aResult); + return NS_OK; +} + +nsresult NS_GetCurrentThread(nsIThread** aResult) { +#ifdef MOZILLA_INTERNAL_API + return nsThreadManager::get().nsThreadManager::GetCurrentThread(aResult); +#else + nsresult rv; + nsCOMPtr<nsIThreadManager> mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return mgr->GetCurrentThread(aResult); +#endif +} + +nsresult NS_GetMainThread(nsIThread** aResult) { +#ifdef MOZILLA_INTERNAL_API + return nsThreadManager::get().nsThreadManager::GetMainThread(aResult); +#else + nsresult rv; + nsCOMPtr<nsIThreadManager> mgr = + do_GetService(NS_THREADMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return mgr->GetMainThread(aResult); +#endif +} + +nsresult NS_DispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent) { + nsresult rv; + nsCOMPtr<nsIRunnable> event(aEvent); +#ifdef MOZILLA_INTERNAL_API + nsIEventTarget* thread = GetCurrentEventTarget(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsCOMPtr<nsIThread> thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + return rv; +} + +// It is common to call NS_DispatchToCurrentThread with a newly +// allocated runnable with a refcount of zero. To keep us from leaking +// the runnable if the dispatch method fails, we take a death grip. +nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent) { + nsCOMPtr<nsIRunnable> event(aEvent); + return NS_DispatchToCurrentThread(event.forget()); +} + +nsresult NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aDispatchFlags) { + LeakRefPtr<nsIRunnable> event(std::move(aEvent)); + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_GetMainThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_ASSERTION(false, + "Failed NS_DispatchToMainThread() in shutdown; leaking"); + // NOTE: if you stop leaking here, adjust Promise::MaybeReportRejected(), + // which assumes a leak here, or split into leaks and no-leaks versions + return rv; + } + return thread->Dispatch(event.take(), aDispatchFlags); +} + +// In the case of failure with a newly allocated runnable with a +// refcount of zero, we intentionally leak the runnable, because it is +// likely that the runnable is being dispatched to the main thread +// because it owns main thread only objects, so it is not safe to +// release them here. +nsresult NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return NS_DispatchToMainThread(event.forget(), aDispatchFlags); +} + +nsresult NS_DelayedDispatchToCurrentThread( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs) { + nsCOMPtr<nsIRunnable> event(aEvent); +#ifdef MOZILLA_INTERNAL_API + nsIEventTarget* thread = GetCurrentEventTarget(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } +#else + nsresult rv; + nsCOMPtr<nsIThread> thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + return thread->DelayedDispatch(event.forget(), aDelayMs); +} + +nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + nsIThread* aThread, + EventQueuePriority aQueue) { + nsresult rv; + nsCOMPtr<nsIRunnable> event(aEvent); + NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG); + if (!aThread) { + return NS_ERROR_UNEXPECTED; + } + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = aThread->DispatchToQueue(event.forget(), aQueue); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + + return rv; +} + +nsresult NS_DispatchToCurrentThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + EventQueuePriority aQueue) { + return NS_DispatchToThreadQueue(std::move(aEvent), NS_GetCurrentThread(), + aQueue); +} + +extern nsresult NS_DispatchToMainThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, EventQueuePriority aQueue) { + nsCOMPtr<nsIThread> mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_SUCCEEDED(rv)) { + return NS_DispatchToThreadQueue(std::move(aEvent), mainThread, aQueue); + } + return rv; +} + +class IdleRunnableWrapper final : public Runnable, + public nsIDiscardableRunnable, + public nsIIdleRunnable { + public: + explicit IdleRunnableWrapper(already_AddRefed<nsIRunnable>&& aEvent) + : Runnable("IdleRunnableWrapper"), + mRunnable(std::move(aEvent)), + mDiscardable(do_QueryInterface(mRunnable)) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Run() override { + if (!mRunnable) { + return NS_OK; + } + CancelTimer(); + // Don't clear mDiscardable because that would cause QueryInterface to + // change behavior during the lifetime of an instance. + nsCOMPtr<nsIRunnable> runnable = std::move(mRunnable); + return runnable->Run(); + } + + // nsIDiscardableRunnable + void OnDiscard() override { + if (!mRunnable) { + // Run() was already called from TimedOut(). + return; + } + mDiscardable->OnDiscard(); + mRunnable = nullptr; + } + + static void TimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr<IdleRunnableWrapper> runnable = + static_cast<IdleRunnableWrapper*>(aClosure); + LogRunnable::Run log(runnable); + runnable->Run(); + runnable = nullptr; + } + + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override { + MOZ_ASSERT(aTarget); + MOZ_ASSERT(!mTimer); + NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "IdleRunnableWrapper::SetTimer", aTarget); + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("IdleRunnableWrapper"); + 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: + ~IdleRunnableWrapper() { CancelTimer(); } + + void CancelTimer() { + if (mTimer) { + mTimer->Cancel(); + } + } + + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIRunnable> mRunnable; + nsCOMPtr<nsIDiscardableRunnable> mDiscardable; +}; + +NS_IMPL_ADDREF_INHERITED(IdleRunnableWrapper, Runnable) +NS_IMPL_RELEASE_INHERITED(IdleRunnableWrapper, Runnable) + +NS_INTERFACE_MAP_BEGIN(IdleRunnableWrapper) + NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDiscardableRunnable, mDiscardable) +NS_INTERFACE_MAP_END_INHERITING(Runnable) + +extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aTimeout, nsIThread* aThread, + EventQueuePriority aQueue) { + nsCOMPtr<nsIRunnable> event(std::move(aEvent)); + NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG); + MOZ_ASSERT(aQueue == EventQueuePriority::Idle || + aQueue == EventQueuePriority::DeferredTimers); + + // XXX Using current thread for now as the nsIEventTarget. + nsIEventTarget* target = mozilla::GetCurrentEventTarget(); + if (!target) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIIdleRunnable> idleEvent = do_QueryInterface(event); + + if (!idleEvent) { + idleEvent = new IdleRunnableWrapper(event.forget()); + event = do_QueryInterface(idleEvent); + MOZ_DIAGNOSTIC_ASSERT(event); + } + idleEvent->SetTimer(aTimeout, target); + + nsresult rv = NS_DispatchToThreadQueue(event.forget(), aThread, aQueue); + if (NS_SUCCEEDED(rv)) { + // This is intended to bind with the "DISP" log made from inside + // NS_DispatchToThreadQueue for the `event`. There is no possibly to inject + // another "DISP" for a different event on this thread. + LOG1(("TIMEOUT %u", aTimeout)); + } + + return rv; +} + +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout, + EventQueuePriority aQueue) { + return NS_DispatchToThreadQueue(std::move(aEvent), aTimeout, + NS_GetCurrentThread(), aQueue); +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +nsresult NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout) { + nsresult rv = NS_OK; + +# ifdef MOZILLA_INTERNAL_API + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return NS_ERROR_UNEXPECTED; + } + } +# else + nsCOMPtr<nsIThread> current; + if (!aThread) { + rv = NS_GetCurrentThread(getter_AddRefs(current)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + aThread = current.get(); + } +# endif + + PRIntervalTime start = PR_IntervalNow(); + for (;;) { + bool processedEvent; + rv = aThread->ProcessNextEvent(false, &processedEvent); + if (NS_FAILED(rv) || !processedEvent) { + break; + } + if (PR_IntervalNow() - start > aTimeout) { + break; + } + } + return rv; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +inline bool hasPendingEvents(nsIThread* aThread) { + bool val; + return NS_SUCCEEDED(aThread->HasPendingEvents(&val)) && val; +} + +bool NS_HasPendingEvents(nsIThread* aThread) { + if (!aThread) { +#ifndef MOZILLA_INTERNAL_API + nsCOMPtr<nsIThread> current; + NS_GetCurrentThread(getter_AddRefs(current)); + return hasPendingEvents(current); +#else + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } +#endif + } + return hasPendingEvents(aThread); +} + +bool NS_ProcessNextEvent(nsIThread* aThread, bool aMayWait) { +#ifdef MOZILLA_INTERNAL_API + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } + } +#else + nsCOMPtr<nsIThread> current; + if (!aThread) { + NS_GetCurrentThread(getter_AddRefs(current)); + if (NS_WARN_IF(!current)) { + return false; + } + aThread = current.get(); + } +#endif + bool val; + return NS_SUCCEEDED(aThread->ProcessNextEvent(aMayWait, &val)) && val; +} + +void NS_SetCurrentThreadName(const char* aName) { +#if defined(ANDROID) + // Workaround for Bug 1541216 - PR_SetCurrentThreadName() Fails to set the + // thread name on Android. + prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(aName)); +#else + PR_SetCurrentThreadName(aName); +#endif + CrashReporter::SetCurrentThreadName(aName); +} + +#ifdef MOZILLA_INTERNAL_API +nsIThread* NS_GetCurrentThread() { + return nsThreadManager::get().GetCurrentThread(); +} + +nsIThread* NS_GetCurrentThreadNoCreate() { + if (nsThreadManager::get().IsNSThread()) { + return NS_GetCurrentThread(); + } + return nullptr; +} +#endif + +// nsThreadPoolNaming +nsCString nsThreadPoolNaming::GetNextThreadName(const nsACString& aPoolName) { + nsCString name(aPoolName); + name.AppendLiteral(" #"); + name.AppendInt(++mCounter, 10); // The counter is declared as atomic + return name; +} + +nsresult NS_DispatchBackgroundTask(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDispatchFlags) { + nsCOMPtr<nsIRunnable> event(aEvent); + return nsThreadManager::get().DispatchToBackgroundThread(event, + aDispatchFlags); +} + +// nsAutoLowPriorityIO +nsAutoLowPriorityIO::nsAutoLowPriorityIO() { +#if defined(XP_WIN) + lowIOPrioritySet = + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); +#elif defined(XP_MACOSX) + oldPriority = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD); + lowIOPrioritySet = + oldPriority != -1 && + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE) != -1; +#else + lowIOPrioritySet = false; +#endif +} + +nsAutoLowPriorityIO::~nsAutoLowPriorityIO() { +#if defined(XP_WIN) + if (MOZ_LIKELY(lowIOPrioritySet)) { + // On Windows the old thread priority is automatically restored + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); + } +#elif defined(XP_MACOSX) + if (MOZ_LIKELY(lowIOPrioritySet)) { + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, oldPriority); + } +#endif +} + +namespace mozilla { + +nsIEventTarget* GetCurrentEventTarget() { + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return thread->EventTarget(); +} + +nsIEventTarget* GetMainThreadEventTarget() { + return GetMainThreadSerialEventTarget(); +} + +nsISerialEventTarget* GetCurrentSerialEventTarget() { + if (nsISerialEventTarget* current = + SerialEventTargetGuard::GetCurrentSerialEventTarget()) { + return current; + } + + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return thread->SerialEventTarget(); +} + +nsISerialEventTarget* GetMainThreadSerialEventTarget() { + return static_cast<nsThread*>(nsThreadManager::get().GetMainThreadWeak()); +} + +size_t GetNumberOfProcessors() { +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + static const PRInt32 procs = PR_GetNumberOfProcessors(); +#else + PRInt32 procs = PR_GetNumberOfProcessors(); +#endif + MOZ_ASSERT(procs > 0); + return static_cast<size_t>(procs); +} + +template <typename T> +void LogTaskBase<T>::LogDispatch(T* aEvent) { + LOG1(("DISP %p", aEvent)); +} +template <typename T> +void LogTaskBase<T>::LogDispatch(T* aEvent, void* aContext) { + LOG1(("DISP %p (%p)", aEvent, aContext)); +} + +template <> +void LogTaskBase<IPC::Message>::LogDispatchWithPid(IPC::Message* aEvent, + int32_t aPid) { + if (aEvent->seqno() && aPid > 0) { + LOG1(("SEND %p %d %d", aEvent, aEvent->seqno(), aPid)); + } +} + +template <typename T> +LogTaskBase<T>::Run::Run(T* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + // Logging address of this RAII so that we can use it to identify the DONE log + // while not keeping any ref to the event that could be invalid at the dtor + // time. + LOG1(("EXEC %p %p", aEvent, this)); +} +template <typename T> +LogTaskBase<T>::Run::Run(T* aEvent, void* aContext, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + LOG1(("EXEC %p (%p) %p", aEvent, aContext, this)); +} + +template <> +LogTaskBase<nsIRunnable>::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + if (!LOG1_ENABLED()) { + return; + } + + nsCOMPtr<nsINamed> named(do_QueryInterface(aEvent)); + if (!named) { + LOG1(("EXEC %p %p", aEvent, this)); + return; + } + + nsAutoCString name; + named->GetName(name); + LOG1(("EXEC %p %p [%s]", aEvent, this, name.BeginReading())); +} + +template <> +LogTaskBase<Task>::Run::Run(Task* aTask, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + if (!LOG1_ENABLED()) { + return; + } + + nsAutoCString name; + if (!aTask->GetName(name)) { + LOG1(("EXEC %p %p", aTask, this)); + return; + } + + LOG1(("EXEC %p %p [%s]", aTask, this, name.BeginReading())); +} + +template <> +LogTaskBase<IPC::Message>::Run::Run(IPC::Message* aMessage, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + LOG1(("RECV %p %p %d [%s]", aMessage, this, aMessage->seqno(), + aMessage->name())); +} + +template <> +LogTaskBase<nsTimerImpl>::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + // The name of the timer will be logged when running it on the target thread. + // Logging it here (on the `Timer` thread) would be redundant. + LOG1(("EXEC %p %p [nsTimerImpl]", aEvent, this)); +} + +template <typename T> +LogTaskBase<T>::Run::~Run() { + LOG1((mWillRunAgain ? "INTERRUPTED %p" : "DONE %p", this)); +} + +template class LogTaskBase<nsIRunnable>; +template class LogTaskBase<MicroTaskRunnable>; +template class LogTaskBase<IPC::Message>; +template class LogTaskBase<nsTimerImpl>; +template class LogTaskBase<Task>; +template class LogTaskBase<PresShell>; +template class LogTaskBase<dom::FrameRequestCallback>; + +MOZ_THREAD_LOCAL(nsISerialEventTarget*) +SerialEventTargetGuard::sCurrentThreadTLS; +void SerialEventTargetGuard::InitTLS() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } +} + +DelayedRunnable::DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget, + already_AddRefed<nsIRunnable> aRunnable, + uint32_t aDelay) + : mozilla::Runnable("DelayedRunnable"), + mTarget(aTarget), + mWrappedRunnable(aRunnable), + mDelayedFrom(TimeStamp::NowLoRes()), + mDelay(aDelay) {} + +nsresult DelayedRunnable::Init() { + return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay, + nsITimer::TYPE_ONE_SHOT, mTarget); +} + +NS_IMETHODIMP DelayedRunnable::Run() { + MOZ_ASSERT(mTimer, "DelayedRunnable without Init?"); + + // Already ran? + if (!mWrappedRunnable) { + return NS_OK; + } + + // Are we too early? + if ((mozilla::TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < + mDelay) { + return NS_OK; // Let the nsITimer run us. + } + + mTimer->Cancel(); + return DoRun(); +} + +NS_IMETHODIMP DelayedRunnable::Notify(nsITimer* aTimer) { + // If we already ran, the timer should have been canceled. + MOZ_ASSERT(mWrappedRunnable); + MOZ_ASSERT(aTimer == mTimer); + + return DoRun(); +} + +nsresult DelayedRunnable::DoRun() { + nsCOMPtr<nsIRunnable> r = std::move(mWrappedRunnable); + return r->Run(); +} + +NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback) + +} // namespace mozilla + +bool nsIEventTarget::IsOnCurrentThread() { + if (mThread) { + return mThread == PR_GetCurrentThread(); + } + return IsOnCurrentThreadInfallible(); +} + +extern "C" { +// These functions use the C language linkage because they're exposed to Rust +// via the xpcom/rust/moz_task crate, which wraps them in safe Rust functions +// that enable Rust code to get/create threads and dispatch runnables on them. + +nsresult NS_GetCurrentThreadEventTarget(nsIEventTarget** aResult) { + nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentEventTarget(); + if (!target) { + return NS_ERROR_UNEXPECTED; + } + target.forget(aResult); + return NS_OK; +} + +nsresult NS_GetMainThreadEventTarget(nsIEventTarget** aResult) { + nsCOMPtr<nsIEventTarget> target = mozilla::GetMainThreadEventTarget(); + if (!target) { + return NS_ERROR_UNEXPECTED; + } + target.forget(aResult); + return NS_OK; +} + +// NS_NewNamedThread's aStackSize parameter has the default argument +// nsIThreadManager::DEFAULT_STACK_SIZE, but we can't omit default arguments +// when calling a C++ function from Rust, and we can't access +// nsIThreadManager::DEFAULT_STACK_SIZE in Rust to pass it explicitly, +// since it is defined in a %{C++ ... %} block within nsIThreadManager.idl. +// So we indirect through this function. +nsresult NS_NewNamedThreadWithDefaultStackSize(const nsACString& aName, + nsIThread** aResult, + nsIRunnable* aEvent) { + return NS_NewNamedThread(aName, aResult, aEvent); +} + +bool NS_IsCurrentThread(nsIEventTarget* aThread) { + return aThread->IsOnCurrentThread(); +} + +nsresult NS_DispatchBackgroundTask(nsIRunnable* aEvent, + uint32_t aDispatchFlags) { + return nsThreadManager::get().DispatchToBackgroundThread(aEvent, + aDispatchFlags); +} + +nsresult NS_CreateBackgroundTaskQueue(const char* aName, + nsISerialEventTarget** aTarget) { + nsCOMPtr<nsISerialEventTarget> target = + nsThreadManager::get().CreateBackgroundTaskQueue(aName); + if (!target) { + return NS_ERROR_FAILURE; + } + + target.forget(aTarget); + return NS_OK; +} + +} // extern "C" diff --git a/xpcom/threads/nsThreadUtils.h b/xpcom/threads/nsThreadUtils.h new file mode 100644 index 0000000000..a8d64de83e --- /dev/null +++ b/xpcom/threads/nsThreadUtils.h @@ -0,0 +1,1968 @@ +/* -*- 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/. */ + +#ifndef nsThreadUtils_h__ +#define nsThreadUtils_h__ + +#include <type_traits> +#include <utility> + +#include "MainThreadUtils.h" +#include "mozilla/EventQueue.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Tuple.h" +#include "nsCOMPtr.h" +#include "nsICancelableRunnable.h" +#include "nsIDiscardableRunnable.h" +#include "nsIIdlePeriod.h" +#include "nsIIdleRunnable.h" +#include "nsINamed.h" +#include "nsIRunnable.h" +#include "nsIThreadManager.h" +#include "nsITimer.h" +#include "nsString.h" +#include "prinrval.h" +#include "prthread.h" + +class MessageLoop; +class nsIThread; + +//----------------------------------------------------------------------------- +// These methods are alternatives to the methods on nsIThreadManager, provided +// for convenience. + +/** + * Create a new thread, and optionally provide an initial event for the thread. + * + * @param aName + * The name of the thread. + * @param aResult + * The resulting nsIThread object. + * @param aInitialEvent + * The initial event to run on this thread. This parameter may be null. + * @param aStackSize + * The size in bytes to reserve for the thread's stack. + * + * @returns NS_ERROR_INVALID_ARG + * Indicates that the given name is not unique. + */ + +extern nsresult NS_NewNamedThread( + const nsACString& aName, nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE); + +extern nsresult NS_NewNamedThread( + const nsACString& aName, nsIThread** aResult, + already_AddRefed<nsIRunnable> aInitialEvent, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE); + +template <size_t LEN> +inline nsresult NS_NewNamedThread( + const char (&aName)[LEN], nsIThread** aResult, + already_AddRefed<nsIRunnable> aInitialEvent, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) { + static_assert(LEN <= 16, "Thread name must be no more than 16 characters"); + return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult, + std::move(aInitialEvent), aStackSize); +} + +template <size_t LEN> +inline nsresult NS_NewNamedThread( + const char (&aName)[LEN], nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + uint32_t aStackSize = nsIThreadManager::DEFAULT_STACK_SIZE) { + nsCOMPtr<nsIRunnable> event = aInitialEvent; + static_assert(LEN <= 16, "Thread name must be no more than 16 characters"); + return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult, + event.forget(), aStackSize); +} + +/** + * Get a reference to the current thread, creating it if it does not exist yet. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetCurrentThread(nsIThread** aResult); + +/** + * Dispatch the given event to the current thread. + * + * @param aEvent + * The event to dispatch. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent); +extern nsresult NS_DispatchToCurrentThread( + already_AddRefed<nsIRunnable>&& aEvent); + +/** + * Dispatch the given event to the main thread. + * + * @param aEvent + * The event to dispatch. + * @param aDispatchFlags + * The flags to pass to the main thread's dispatch method. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult NS_DispatchToMainThread( + nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); +extern nsresult NS_DispatchToMainThread( + already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + +extern nsresult NS_DelayedDispatchToCurrentThread( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs); + +/** + * Dispatch the given event to the specified queue of the current thread. + * + * @param aEvent The event to dispatch. + * @param aQueue The event queue for the thread to use + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to the specified queue of the main thread. + * + * @param aEvent The event to dispatch. + * @param aQueue The event queue for the thread to use + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToMainThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to an idle queue of the current thread. + * + * @param aEvent The event to dispatch. If the event implements + * nsIIdleRunnable, it will receive a call on + * nsIIdleRunnable::SetTimer when dispatched, with the value of + * aTimeout. + * + * @param aTimeout The time in milliseconds until the event should be + * moved from an idle queue to the regular queue, if it hasn't been + * executed. If aEvent is also an nsIIdleRunnable, it is expected + * that it should handle the timeout itself, after a call to + * nsIIdleRunnable::SetTimer. + * + * @param aQueue + * The event queue for the thread to use. Must be an idle queue + * (Idle or DeferredTimers) + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed<nsIRunnable>&& aEvent, uint32_t aTimeout, + mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to a queue of a thread. + * + * @param aEvent The event to dispatch. + * @param aThread The target thread for the dispatch. + * @param aQueue The event queue for the thread to use. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + nsIThread* aThread, + mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to an idle queue of a thread. + * + * @param aEvent The event to dispatch. If the event implements + * nsIIdleRunnable, it will receive a call on + * nsIIdleRunnable::SetTimer when dispatched, with the value of + * aTimeout. + * + * @param aTimeout The time in milliseconds until the event should be + * moved from an idle queue to the regular queue, if it hasn't been + * executed. If aEvent is also an nsIIdleRunnable, it is expected + * that it should handle the timeout itself, after a call to + * nsIIdleRunnable::SetTimer. + * + * @param aThread The target thread for the dispatch. + * + * @param aQueue + * The event queue for the thread to use. Must be an idle queue + * (Idle or DeferredTimers) + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToThreadQueue(already_AddRefed<nsIRunnable>&& aEvent, + uint32_t aTimeout, nsIThread* aThread, + mozilla::EventQueuePriority aQueue); + +#ifndef XPCOM_GLUE_AVOID_NSPR +/** + * Process all pending events for the given thread before returning. This + * method simply calls ProcessNextEvent on the thread while HasPendingEvents + * continues to return true and the time spent in NS_ProcessPendingEvents + * does not exceed the given timeout value. + * + * @param aThread + * The thread object for which to process pending events. If null, then + * events will be processed for the current thread. + * @param aTimeout + * The maximum number of milliseconds to spend processing pending events. + * Events are not pre-empted to honor this timeout. Rather, the timeout + * value is simply used to determine whether or not to process another event. + * Pass PR_INTERVAL_NO_TIMEOUT to specify no timeout. + */ +extern nsresult NS_ProcessPendingEvents( + nsIThread* aThread, PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT); +#endif + +/** + * Shortcut for nsIThread::HasPendingEvents. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will return false if called from some + * other thread. + * + * @param aThread + * The current thread or null. + * + * @returns + * A boolean value that if "true" indicates that there are pending events + * in the current thread's event queue. + */ +extern bool NS_HasPendingEvents(nsIThread* aThread = nullptr); + +/** + * Shortcut for nsIThread::ProcessNextEvent. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will simply return false if called + * from some other thread. + * + * @param aThread + * The current thread or null. + * @param aMayWait + * A boolean parameter that if "true" indicates that the method may block + * the calling thread to wait for a pending event. + * + * @returns + * A boolean value that if "true" indicates that an event from the current + * thread's event queue was processed. + */ +extern bool NS_ProcessNextEvent(nsIThread* aThread = nullptr, + bool aMayWait = true); + +/** + * Returns true if we're in the compositor thread. + * + * We declare this here because the headers required to invoke + * CompositorThreadHolder::IsInCompositorThread() also pull in a bunch of system + * headers that #define various tokens in a way that can break the build. + */ +extern bool NS_IsInCompositorThread(); + +extern bool NS_IsInCanvasThreadOrWorker(); + +extern bool NS_IsInVRThread(); + +//----------------------------------------------------------------------------- +// Helpers that work with nsCOMPtr: + +inline already_AddRefed<nsIThread> do_GetCurrentThread() { + nsIThread* thread = nullptr; + NS_GetCurrentThread(&thread); + return already_AddRefed<nsIThread>(thread); +} + +inline already_AddRefed<nsIThread> do_GetMainThread() { + nsIThread* thread = nullptr; + NS_GetMainThread(&thread); + return already_AddRefed<nsIThread>(thread); +} + +//----------------------------------------------------------------------------- + +#ifdef MOZILLA_INTERNAL_API +// Fast access to the current thread. Will create an nsIThread if one does not +// exist already! Do not release the returned pointer! If you want to use this +// pointer from some other thread, then you will need to AddRef it. Otherwise, +// you should only consider this pointer valid from code running on the current +// thread. +extern nsIThread* NS_GetCurrentThread(); + +// Exactly the same as NS_GetCurrentThread, except it will not create an +// nsThread if one does not exist yet. This is useful in cases where you have +// code that runs on threads that may or may not not be driven by an nsThread +// event loop, and wish to avoid inadvertently creating a superfluous nsThread. +extern nsIThread* NS_GetCurrentThreadNoCreate(); + +/** + * Set the name of the current thread. Prefer this function over + * PR_SetCurrentThreadName() if possible. The name will also be included in the + * crash report. + * + * @param aName + * Name of the thread. A C language null-terminated string. + */ +extern void NS_SetCurrentThreadName(const char* aName); +#endif + +//----------------------------------------------------------------------------- + +#ifndef XPCOM_GLUE_AVOID_NSPR + +namespace mozilla { + +// This class is designed to be subclassed. +class IdlePeriod : public nsIIdlePeriod { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIDLEPERIOD + + IdlePeriod() = default; + + protected: + virtual ~IdlePeriod() = default; + + private: + IdlePeriod(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&&) = delete; +}; + +// Cancelable runnable methods implement nsICancelableRunnable, and +// Idle and IdleWithTimer also nsIIdleRunnable. +enum class RunnableKind { Standard, Cancelable, Idle, IdleWithTimer }; + +// Implementing nsINamed on Runnable bloats vtables for the hundreds of +// Runnable subclasses that we have, so we want to avoid that overhead +// when we're not using nsINamed for anything. +# ifndef RELEASE_OR_BETA +# define MOZ_COLLECTING_RUNNABLE_TELEMETRY +# endif + +// This class is designed to be subclassed. +class Runnable : public nsIRunnable +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + , + public nsINamed +# endif +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_DECL_NSINAMED +# endif + + Runnable() = delete; + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + explicit Runnable(const char* aName) : mName(aName) {} +# else + explicit Runnable(const char* aName) {} +# endif + + protected: + virtual ~Runnable() = default; + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + const char* mName = nullptr; +# endif + + private: + Runnable(const Runnable&) = delete; + Runnable& operator=(const Runnable&) = delete; + Runnable& operator=(const Runnable&&) = delete; +}; + +// This is a base class for tasks that might not be run, such as those that may +// be dispatched to workers. +// The owner of an event target will call either Run() or OnDiscard() +// exactly once. +// Derived classes should override Run(). An OnDiscard() override may +// provide cleanup when Run() will not be called. +class DiscardableRunnable : public Runnable, public nsIDiscardableRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + // nsIDiscardableRunnable + void OnDiscard() override {} + + DiscardableRunnable() = delete; + explicit DiscardableRunnable(const char* aName) : Runnable(aName) {} + + protected: + virtual ~DiscardableRunnable() = default; + + private: + DiscardableRunnable(const DiscardableRunnable&) = delete; + DiscardableRunnable& operator=(const DiscardableRunnable&) = delete; + DiscardableRunnable& operator=(const DiscardableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +// Derived classes should override Run() and Cancel() to provide that +// calling Run() after Cancel() is a no-op. +class CancelableRunnable : public DiscardableRunnable, + public nsICancelableRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + // nsIDiscardableRunnable + void OnDiscard() override; + // nsICancelableRunnable + virtual nsresult Cancel() override = 0; + + CancelableRunnable() = delete; + explicit CancelableRunnable(const char* aName) : DiscardableRunnable(aName) {} + + protected: + virtual ~CancelableRunnable() = default; + + private: + CancelableRunnable(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class IdleRunnable : public DiscardableRunnable, public nsIIdleRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + + IdleRunnable() : DiscardableRunnable("IdleRunnable") {} + explicit IdleRunnable(const char* aName) : DiscardableRunnable(aName) {} + + protected: + virtual ~IdleRunnable() = default; + + private: + IdleRunnable(const IdleRunnable&) = delete; + IdleRunnable& operator=(const IdleRunnable&) = delete; + IdleRunnable& operator=(const IdleRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class CancelableIdleRunnable : public CancelableRunnable, + public nsIIdleRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + + CancelableIdleRunnable() : CancelableRunnable("CancelableIdleRunnable") {} + explicit CancelableIdleRunnable(const char* aName) + : CancelableRunnable(aName) {} + + protected: + virtual ~CancelableIdleRunnable() = default; + + private: + CancelableIdleRunnable(const CancelableIdleRunnable&) = delete; + CancelableIdleRunnable& operator=(const CancelableIdleRunnable&) = delete; + CancelableIdleRunnable& operator=(const CancelableIdleRunnable&&) = delete; +}; + +// This class is designed to be a wrapper of a real runnable to support event +// prioritizable. +class PrioritizableRunnable : public Runnable, public nsIRunnablePriority { + public: + PrioritizableRunnable(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aPriority); + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +# endif + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIRUNNABLEPRIORITY + + protected: + virtual ~PrioritizableRunnable() = default; + ; + nsCOMPtr<nsIRunnable> mRunnable; + uint32_t mPriority; +}; + +extern already_AddRefed<nsIRunnable> CreateMediumHighRunnable( + already_AddRefed<nsIRunnable>&& aRunnable); + +namespace detail { + +// An event that can be used to call a C++11 functions or function objects, +// including lambdas. The function must have no required arguments, and must +// return void. +template <typename StoredFunction> +class RunnableFunction : public Runnable { + public: + template <typename F> + explicit RunnableFunction(const char* aName, F&& aFunction) + : Runnable(aName), mFunction(std::forward<F>(aFunction)) {} + + NS_IMETHOD Run() override { + static_assert(std::is_void_v<decltype(mFunction())>, + "The lambda must return void!"); + mFunction(); + return NS_OK; + } + + private: + StoredFunction mFunction; +}; + +// Type alias for NS_NewRunnableFunction +template <typename Function> +using RunnableFunctionImpl = + // Make sure we store a non-reference in nsRunnableFunction. + typename detail::RunnableFunction<std::remove_reference_t<Function>>; +} // namespace detail + +namespace detail { + +template <typename CVRemoved> +struct IsRefcountedSmartPointerHelper : std::false_type {}; + +template <typename Pointee> +struct IsRefcountedSmartPointerHelper<RefPtr<Pointee>> : std::true_type {}; + +template <typename Pointee> +struct IsRefcountedSmartPointerHelper<nsCOMPtr<Pointee>> : std::true_type {}; + +} // namespace detail + +template <typename T> +struct IsRefcountedSmartPointer + : detail::IsRefcountedSmartPointerHelper<std::remove_cv_t<T>> {}; + +namespace detail { + +template <typename T, typename CVRemoved> +struct RemoveSmartPointerHelper { + typedef T Type; +}; + +template <typename T, typename Pointee> +struct RemoveSmartPointerHelper<T, RefPtr<Pointee>> { + typedef Pointee Type; +}; + +template <typename T, typename Pointee> +struct RemoveSmartPointerHelper<T, nsCOMPtr<Pointee>> { + typedef Pointee Type; +}; + +} // namespace detail + +template <typename T> +struct RemoveSmartPointer + : detail::RemoveSmartPointerHelper<T, std::remove_cv_t<T>> {}; + +namespace detail { + +template <typename T, typename CVRemoved> +struct RemoveRawOrSmartPointerHelper { + typedef T Type; +}; + +template <typename T, typename Pointee> +struct RemoveRawOrSmartPointerHelper<T, Pointee*> { + typedef Pointee Type; +}; + +template <typename T, typename Pointee> +struct RemoveRawOrSmartPointerHelper<T, RefPtr<Pointee>> { + typedef Pointee Type; +}; + +template <typename T, typename Pointee> +struct RemoveRawOrSmartPointerHelper<T, nsCOMPtr<Pointee>> { + typedef Pointee Type; +}; + +} // namespace detail + +template <typename T> +struct RemoveRawOrSmartPointer + : detail::RemoveRawOrSmartPointerHelper<T, std::remove_cv_t<T>> {}; + +} // namespace mozilla + +inline nsISupports* ToSupports(mozilla::Runnable* p) { + return static_cast<nsIRunnable*>(p); +} + +template <typename Function> +already_AddRefed<mozilla::Runnable> NS_NewRunnableFunction( + const char* aName, Function&& aFunction) { + // We store a non-reference in RunnableFunction, but still forward aFunction + // to move if possible. + return do_AddRef(new mozilla::detail::RunnableFunctionImpl<Function>( + aName, std::forward<Function>(aFunction))); +} + +// Creates a new object implementing nsIRunnable and nsICancelableRunnable, +// which runs a given function on Run and clears the stored function object on a +// call to `Cancel` (and thus destroys all objects it holds). +template <typename Function> +already_AddRefed<mozilla::CancelableRunnable> NS_NewCancelableRunnableFunction( + const char* aName, Function&& aFunc) { + class FuncCancelableRunnable final : public mozilla::CancelableRunnable { + public: + static_assert(std::is_void_v<decltype( + std::declval<std::remove_reference_t<Function>>()())>); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(FuncCancelableRunnable, + CancelableRunnable) + + explicit FuncCancelableRunnable(const char* aName, Function&& aFunc) + : CancelableRunnable{aName}, + mFunc{mozilla::Some(std::forward<Function>(aFunc))} {} + + NS_IMETHOD Run() override { + if (mFunc) { + (*mFunc)(); + } + + return NS_OK; + } + + nsresult Cancel() override { + mFunc.reset(); + return NS_OK; + } + + private: + ~FuncCancelableRunnable() = default; + + mozilla::Maybe<std::remove_reference_t<Function>> mFunc; + }; + + return mozilla::MakeAndAddRef<FuncCancelableRunnable>( + aName, std::forward<Function>(aFunc)); +} + +namespace mozilla { +namespace detail { + +template <RunnableKind Kind> +class TimerBehaviour { + public: + nsITimer* GetTimer() { return nullptr; } + void CancelTimer() {} + + protected: + ~TimerBehaviour() = default; +}; + +template <> +class TimerBehaviour<RunnableKind::IdleWithTimer> { + public: + nsITimer* GetTimer() { + if (!mTimer) { + mTimer = NS_NewTimer(); + } + + return mTimer; + } + + void CancelTimer() { + if (mTimer) { + mTimer->Cancel(); + } + } + + protected: + ~TimerBehaviour() { CancelTimer(); } + + private: + nsCOMPtr<nsITimer> mTimer; +}; + +} // namespace detail +} // namespace mozilla + +// An event that can be used to call a method on a class. The class type must +// support reference counting. This event supports Revoke for use +// with nsRevocableEventPtr. +template <class ClassType, typename ReturnType = void, bool Owning = true, + mozilla::RunnableKind Kind = mozilla::RunnableKind::Standard> +class nsRunnableMethod + : public std::conditional_t< + Kind == mozilla::RunnableKind::Standard, mozilla::Runnable, + std::conditional_t<Kind == mozilla::RunnableKind::Cancelable, + mozilla::CancelableRunnable, + mozilla::CancelableIdleRunnable>>, + protected mozilla::detail::TimerBehaviour<Kind> { + using BaseType = std::conditional_t< + Kind == mozilla::RunnableKind::Standard, mozilla::Runnable, + std::conditional_t<Kind == mozilla::RunnableKind::Cancelable, + mozilla::CancelableRunnable, + mozilla::CancelableIdleRunnable>>; + + public: + nsRunnableMethod(const char* aName) : BaseType(aName) {} + + virtual void Revoke() = 0; + + // These ReturnTypeEnforcer classes disallow return types that + // we know are not safe. The default ReturnTypeEnforcer compiles just fine but + // already_AddRefed will not. + template <typename OtherReturnType> + class ReturnTypeEnforcer { + public: + typedef int ReturnTypeIsSafe; + }; + + template <class T> + class ReturnTypeEnforcer<already_AddRefed<T>> { + // No ReturnTypeIsSafe makes this illegal! + }; + + // Make sure this return type is safe. + typedef typename ReturnTypeEnforcer<ReturnType>::ReturnTypeIsSafe check; +}; + +template <class ClassType, bool Owning> +struct nsRunnableMethodReceiver { + RefPtr<ClassType> mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + explicit nsRunnableMethodReceiver(RefPtr<ClassType>&& aObj) + : mObj(std::move(aObj)) {} + ~nsRunnableMethodReceiver() { Revoke(); } + ClassType* Get() const { return mObj.get(); } + void Revoke() { mObj = nullptr; } +}; + +template <class ClassType> +struct nsRunnableMethodReceiver<ClassType, false> { + ClassType* MOZ_NON_OWNING_REF mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + ClassType* Get() const { return mObj; } + void Revoke() { mObj = nullptr; } +}; + +static inline constexpr bool IsIdle(mozilla::RunnableKind aKind) { + return aKind == mozilla::RunnableKind::Idle || + aKind == mozilla::RunnableKind::IdleWithTimer; +} + +template <typename PtrType, typename Method, bool Owning, + mozilla::RunnableKind Kind> +struct nsRunnableMethodTraits; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (C::*)(As...), Owning, Kind> { + typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (C::*)(As...) const, Owning, Kind> { + typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type + class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +# ifdef NS_HAVE_STDCALL +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (__stdcall C::*)(As...), Owning, + Kind> { + typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind> +struct nsRunnableMethodTraits<PtrType, R (NS_STDCALL C::*)(), Owning, Kind> { + typedef typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind, typename... As> +struct nsRunnableMethodTraits<PtrType, R (__stdcall C::*)(As...) const, Owning, + Kind> { + typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type + class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template <typename PtrType, class C, typename R, bool Owning, + mozilla::RunnableKind Kind> +struct nsRunnableMethodTraits<PtrType, R (NS_STDCALL C::*)() const, Owning, + Kind> { + typedef const typename mozilla::RemoveRawOrSmartPointer<PtrType>::Type + class_type; + static_assert(std::is_base_of<C, class_type>::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod<C, R, Owning, Kind> base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; +# endif + +// IsParameterStorageClass<T>::value is true if T is a parameter-storage class +// that will be recognized by NS_New[NonOwning]RunnableMethodWithArg[s] to +// force a specific storage&passing strategy (instead of inferring one, +// see ParameterStorage). +// When creating a new storage class, add a specialization for it to be +// recognized. +template <typename T> +struct IsParameterStorageClass : public std::false_type {}; + +// StoreXPassByY structs used to inform nsRunnableMethodArguments how to +// store arguments, and how to pass them to the target method. + +template <typename T> +struct StoreCopyPassByValue { + using stored_type = std::decay_t<T>; + typedef stored_type passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByValue(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByValue<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByConstLRef { + using stored_type = std::decay_t<T>; + typedef const stored_type& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByConstLRef(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByConstLRef<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByLRef { + using stored_type = std::decay_t<T>; + typedef stored_type& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByLRef(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByLRef<S>> : public std::true_type { +}; + +template <typename T> +struct StoreCopyPassByRRef { + using stored_type = std::decay_t<T>; + typedef stored_type&& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByRRef(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return std::move(m); } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByRRef<S>> : public std::true_type { +}; + +template <typename T> +struct StoreRefPassByLRef { + typedef T& stored_type; + typedef T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreRefPassByLRef(A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreRefPassByLRef<S>> : public std::true_type { +}; + +template <typename T> +struct StoreConstRefPassByConstLRef { + typedef const T& stored_type; + typedef const T& passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreConstRefPassByConstLRef(const A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreConstRefPassByConstLRef<S>> + : public std::true_type {}; + +template <typename T> +struct StoreRefPtrPassByPtr { + typedef RefPtr<T> stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreRefPtrPassByPtr(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return m.get(); } +}; +template <typename S> +struct IsParameterStorageClass<StoreRefPtrPassByPtr<S>> + : public std::true_type {}; + +template <typename T> +struct StorePtrPassByPtr { + typedef T* stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StorePtrPassByPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StorePtrPassByPtr<S>> : public std::true_type {}; + +template <typename T> +struct StoreConstPtrPassByConstPtr { + typedef const T* stored_type; + typedef const T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreConstPtrPassByConstPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreConstPtrPassByConstPtr<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByConstPtr { + typedef T stored_type; + typedef const T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByConstPtr(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByConstPtr<S>> + : public std::true_type {}; + +template <typename T> +struct StoreCopyPassByPtr { + typedef T stored_type; + typedef T* passed_type; + stored_type m; + template <typename A> + MOZ_IMPLICIT StoreCopyPassByPtr(A&& a) : m(std::forward<A>(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template <typename S> +struct IsParameterStorageClass<StoreCopyPassByPtr<S>> : public std::true_type { +}; + +namespace detail { + +template <typename> +struct SFINAE1True : std::true_type {}; + +template <class T> +static auto HasRefCountMethodsTest(int) + -> SFINAE1True<decltype(std::declval<T>().AddRef(), + std::declval<T>().Release())>; +template <class> +static auto HasRefCountMethodsTest(long) -> std::false_type; + +template <class T> +struct HasRefCountMethods : decltype(HasRefCountMethodsTest<T>(0)) {}; + +template <typename TWithoutPointer> +struct NonnsISupportsPointerStorageClass + : std::conditional< + std::is_const_v<TWithoutPointer>, + StoreConstPtrPassByConstPtr<std::remove_const_t<TWithoutPointer>>, + StorePtrPassByPtr<TWithoutPointer>> { + using Type = typename NonnsISupportsPointerStorageClass::conditional::type; +}; + +template <typename TWithoutPointer> +struct PointerStorageClass + : std::conditional< + HasRefCountMethods<TWithoutPointer>::value, + StoreRefPtrPassByPtr<TWithoutPointer>, + typename NonnsISupportsPointerStorageClass<TWithoutPointer>::Type> { + using Type = typename PointerStorageClass::conditional::type; +}; + +template <typename TWithoutRef> +struct LValueReferenceStorageClass + : std::conditional< + std::is_const_v<TWithoutRef>, + StoreConstRefPassByConstLRef<std::remove_const_t<TWithoutRef>>, + StoreRefPassByLRef<TWithoutRef>> { + using Type = typename LValueReferenceStorageClass::conditional::type; +}; + +template <typename T> +struct SmartPointerStorageClass + : std::conditional< + mozilla::IsRefcountedSmartPointer<T>::value, + StoreRefPtrPassByPtr<typename mozilla::RemoveSmartPointer<T>::Type>, + StoreCopyPassByConstLRef<T>> { + using Type = typename SmartPointerStorageClass::conditional::type; +}; + +template <typename T> +struct NonLValueReferenceStorageClass + : std::conditional<std::is_rvalue_reference_v<T>, + StoreCopyPassByRRef<std::remove_reference_t<T>>, + typename SmartPointerStorageClass<T>::Type> { + using Type = typename NonLValueReferenceStorageClass::conditional::type; +}; + +template <typename T> +struct NonPointerStorageClass + : std::conditional<std::is_lvalue_reference_v<T>, + typename LValueReferenceStorageClass< + std::remove_reference_t<T>>::Type, + typename NonLValueReferenceStorageClass<T>::Type> { + using Type = typename NonPointerStorageClass::conditional::type; +}; + +template <typename T> +struct NonParameterStorageClass + : std::conditional< + std::is_pointer_v<T>, + typename PointerStorageClass<std::remove_pointer_t<T>>::Type, + typename NonPointerStorageClass<T>::Type> { + using Type = typename NonParameterStorageClass::conditional::type; +}; + +// Choose storage&passing strategy based on preferred storage type: +// - If IsParameterStorageClass<T>::value is true, use as-is. +// - RC* -> StoreRefPtrPassByPtr<RC> :Store RefPtr<RC>, pass RC* +// ^^ RC quacks like a ref-counted type (i.e., has AddRef and Release methods) +// - const T* -> StoreConstPtrPassByConstPtr<T> :Store const T*, pass const T* +// - T* -> StorePtrPassByPtr<T> :Store T*, pass T*. +// - const T& -> StoreConstRefPassByConstLRef<T>:Store const T&, pass const T&. +// - T& -> StoreRefPassByLRef<T> :Store T&, pass T&. +// - T&& -> StoreCopyPassByRRef<T> :Store T, pass std::move(T). +// - RefPtr<T>, nsCOMPtr<T> +// -> StoreRefPtrPassByPtr<T> :Store RefPtr<T>, pass T* +// - Other T -> StoreCopyPassByConstLRef<T> :Store T, pass const T&. +// Other available explicit options: +// - StoreCopyPassByValue<T> :Store T, pass T. +// - StoreCopyPassByLRef<T> :Store T, pass T& (of copy!) +// - StoreCopyPassByConstPtr<T> :Store T, pass const T* +// - StoreCopyPassByPtr<T> :Store T, pass T* (of copy!) +// Or create your own class with PassAsParameter() method, optional +// clean-up in destructor, and with associated IsParameterStorageClass<>. +template <typename T> +struct ParameterStorage + : std::conditional<IsParameterStorageClass<T>::value, T, + typename NonParameterStorageClass<T>::Type> { + using Type = typename ParameterStorage::conditional::type; +}; + +template <class T> +static auto HasSetDeadlineTest(int) -> SFINAE1True<decltype( + std::declval<T>().SetDeadline(std::declval<mozilla::TimeStamp>()))>; + +template <class T> +static auto HasSetDeadlineTest(long) -> std::false_type; + +template <class T> +struct HasSetDeadline : decltype(HasSetDeadlineTest<T>(0)) {}; + +template <class T> +std::enable_if_t<::detail::HasSetDeadline<T>::value> SetDeadlineImpl( + T* aObj, mozilla::TimeStamp aTimeStamp) { + aObj->SetDeadline(aTimeStamp); +} + +template <class T> +std::enable_if_t<!::detail::HasSetDeadline<T>::value> SetDeadlineImpl( + T* aObj, mozilla::TimeStamp aTimeStamp) {} +} /* namespace detail */ + +namespace mozilla { +namespace detail { + +// struct used to store arguments and later apply them to a method. +template <typename... Ts> +struct RunnableMethodArguments final { + Tuple<typename ::detail::ParameterStorage<Ts>::Type...> mArguments; + template <typename... As> + explicit RunnableMethodArguments(As&&... aArguments) + : mArguments(std::forward<As>(aArguments)...) {} + template <typename C, typename M, typename... Args, size_t... Indices> + static auto applyImpl(C* o, M m, Tuple<Args...>& args, + std::index_sequence<Indices...>) + -> decltype(((*o).*m)(Get<Indices>(args).PassAsParameter()...)) { + return ((*o).*m)(Get<Indices>(args).PassAsParameter()...); + } + template <class C, typename M> + auto apply(C* o, M m) + -> decltype(applyImpl(o, m, mArguments, + std::index_sequence_for<Ts...>{})) { + return applyImpl(o, m, mArguments, std::index_sequence_for<Ts...>{}); + } +}; + +template <typename PtrType, typename Method, bool Owning, RunnableKind Kind, + typename... Storages> +class RunnableMethodImpl final + : public ::nsRunnableMethodTraits<PtrType, Method, Owning, + Kind>::base_type { + typedef typename ::nsRunnableMethodTraits<PtrType, Method, Owning, Kind> + Traits; + + typedef typename Traits::class_type ClassType; + typedef typename Traits::base_type BaseType; + ::nsRunnableMethodReceiver<ClassType, Owning> mReceiver; + Method mMethod; + RunnableMethodArguments<Storages...> mArgs; + using BaseType::CancelTimer; + using BaseType::GetTimer; + + private: + virtual ~RunnableMethodImpl() { Revoke(); }; + static void TimedOut(nsITimer* aTimer, void* aClosure) { + static_assert(IsIdle(Kind), "Don't use me!"); + RefPtr<CancelableIdleRunnable> r = + static_cast<CancelableIdleRunnable*>(aClosure); + r->SetDeadline(TimeStamp()); + r->Run(); + r->Cancel(); + } + + public: + template <typename ForwardedPtrType, typename... Args> + explicit RunnableMethodImpl(const char* aName, ForwardedPtrType&& aObj, + Method aMethod, Args&&... aArgs) + : BaseType(aName), + mReceiver(std::forward<ForwardedPtrType>(aObj)), + mMethod(aMethod), + mArgs(std::forward<Args>(aArgs)...) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "Storages and Args should have equal sizes"); + } + + NS_IMETHOD Run() { + CancelTimer(); + + if (MOZ_LIKELY(mReceiver.Get())) { + mArgs.apply(mReceiver.Get(), mMethod); + } + + return NS_OK; + } + + nsresult Cancel() { + static_assert(Kind >= RunnableKind::Cancelable, "Don't use me!"); + Revoke(); + return NS_OK; + } + + void Revoke() { + CancelTimer(); + mReceiver.Revoke(); + } + + void SetDeadline(TimeStamp aDeadline) { + if (MOZ_LIKELY(mReceiver.Get())) { + ::detail::SetDeadlineImpl(mReceiver.Get(), aDeadline); + } + } + + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT(aTarget); + + if (nsCOMPtr<nsITimer> timer = GetTimer()) { + timer->Cancel(); + timer->SetTarget(aTarget); + timer->InitWithNamedFuncCallback(TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "detail::RunnableMethodImpl::SetTimer"); + } + } +}; + +// Type aliases for NewRunnableMethod. +template <typename PtrType, typename Method> +using OwningRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, RunnableKind::Standard>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using OwningRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::Standard, Storages...>; + +// Type aliases for NewCancelableRunnableMethod. +template <typename PtrType, typename Method> +using CancelableRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, + RunnableKind::Cancelable>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using CancelableRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::Cancelable, Storages...>; + +// Type aliases for NewIdleRunnableMethod. +template <typename PtrType, typename Method> +using IdleRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, RunnableKind::Idle>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using IdleRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::Idle, Storages...>; + +// Type aliases for NewIdleRunnableMethodWithTimer. +template <typename PtrType, typename Method> +using IdleRunnableMethodWithTimer = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + true, + RunnableKind::IdleWithTimer>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using IdleRunnableMethodWithTimerImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, true, + RunnableKind::IdleWithTimer, Storages...>; + +// Type aliases for NewNonOwningRunnableMethod. +template <typename PtrType, typename Method> +using NonOwningRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, RunnableKind::Standard>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::Standard, Storages...>; + +// Type aliases for NonOwningCancelableRunnableMethod +template <typename PtrType, typename Method> +using NonOwningCancelableRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, + RunnableKind::Cancelable>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningCancelableRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::Cancelable, Storages...>; + +// Type aliases for NonOwningIdleRunnableMethod +template <typename PtrType, typename Method> +using NonOwningIdleRunnableMethod = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, RunnableKind::Idle>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningIdleRunnableMethodImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::Idle, Storages...>; + +// Type aliases for NewIdleRunnableMethodWithTimer. +template <typename PtrType, typename Method> +using NonOwningIdleRunnableMethodWithTimer = + typename ::nsRunnableMethodTraits<std::remove_reference_t<PtrType>, Method, + false, + RunnableKind::IdleWithTimer>::base_type; +template <typename PtrType, typename Method, typename... Storages> +using NonOwningIdleRunnableMethodWithTimerImpl = + RunnableMethodImpl<std::remove_reference_t<PtrType>, Method, false, + RunnableKind::IdleWithTimer, Storages...>; + +} // namespace detail + +// NewRunnableMethod and friends +// +// Very often in Gecko, you'll find yourself in a situation where you want +// to invoke a method (with or without arguments) asynchronously. You +// could write a small helper class inheriting from nsRunnable to handle +// all these details, or you could let NewRunnableMethod take care of all +// those details for you. +// +// The simplest use of NewRunnableMethod looks like: +// +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod("description", myObject, +// &MyClass::HandleEvent); +// NS_DispatchToCurrentThread(event); +// +// Statically enforced constraints: +// - myObject must be of (or implicitly convertible to) type MyClass +// - MyClass must define AddRef and Release methods +// +// The "description" string should specify a human-readable name for the +// runnable; the provided string is used by various introspection tools +// in the browser. +// +// The created runnable will take a strong reference to `myObject`. For +// non-refcounted objects, or refcounted objects with unusual refcounting +// requirements, and if and only if you are 110% certain that `myObject` +// will live long enough, you can use NewNonOwningRunnableMethod instead, +// which will, as its name implies, take a non-owning reference. If you +// find yourself having to use this function, you should accompany your use +// with a proof comment describing why the runnable will not lead to +// use-after-frees. +// +// (If you find yourself writing contorted code to Release() an object +// asynchronously on a different thread, you should use the +// NS_ProxyRelease function.) +// +// Invoking a method with arguments takes a little more care. The +// natural extension of the above: +// +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod("description", myObject, +// &MyClass::HandleEvent, +// arg1, arg2, ...); +// +// can lead to security hazards (e.g. passing in raw pointers to refcounted +// objects and storing those raw pointers in the runnable). We therefore +// require you to specify the storage types used by the runnable, just as +// you would if you were writing out the class by hand: +// +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod<RefPtr<T>, nsTArray<U>> +// ("description", myObject, &MyClass::HandleEvent, arg1, arg2); +// +// Please note that you do not have to pass the same argument type as you +// specify in the template arguments. For example, if you want to transfer +// ownership to a runnable, you can write: +// +// RefPtr<T> ptr = ...; +// nsTArray<U> array = ...; +// nsCOMPtr<nsIRunnable> event = +// mozilla::NewRunnableMethod<RefPtr<T>, nsTArray<U>> +// ("description", myObject, &MyClass::DoSomething, +// std::move(ptr), std::move(array)); +// +// and there will be no extra AddRef/Release traffic, or copying of the array. +// +// Each type that you specify as a template argument to NewRunnableMethod +// comes with its own style of storage in the runnable and its own style +// of argument passing to the invoked method. See the comment for +// ParameterStorage above for more details. +// +// If you need to customize the storage type and/or argument passing type, +// you can write your own class to use as a template argument to +// NewRunnableMethod. If you find yourself having to do that frequently, +// please file a bug in Core::XPCOM about adding the custom type to the +// core code in this file, and/or for custom rules for ParameterStorage +// to select that strategy. +// +// For places that require you to use cancelable runnables, such as +// workers, there's also NewCancelableRunnableMethod and its non-owning +// counterpart. The runnables returned by these methods additionally +// implement nsICancelableRunnable. +// +// Finally, all of the functions discussed above have additional overloads +// that do not take a `const char*` as their first parameter; you may see +// these in older code. The `const char*` overload is preferred and +// should be used in new code exclusively. + +template <typename PtrType, typename Method> +already_AddRefed<detail::OwningRunnableMethod<PtrType, Method>> +NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::OwningRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::CancelableRunnableMethod<PtrType, Method>> +NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::CancelableRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::IdleRunnableMethod<PtrType, Method>> +NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::IdleRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::IdleRunnableMethodWithTimer<PtrType, Method>> +NewIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef(new detail::IdleRunnableMethodWithTimerImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningRunnableMethod<PtrType, Method>> +NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::NonOwningRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningCancelableRunnableMethod<PtrType, Method>> +NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef( + new detail::NonOwningCancelableRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningIdleRunnableMethod<PtrType, Method>> +NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef(new detail::NonOwningIdleRunnableMethodImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +template <typename PtrType, typename Method> +already_AddRefed<detail::NonOwningIdleRunnableMethodWithTimer<PtrType, Method>> +NewNonOwningIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef( + new detail::NonOwningIdleRunnableMethodWithTimerImpl<PtrType, Method>( + aName, std::forward<PtrType>(aPtr), aMethod)); +} + +// Similar to NewRunnableMethod. Call like so: +// nsCOMPtr<nsIRunnable> event = +// NewRunnableMethod<Types,...>(myObject, &MyClass::HandleEvent, myArg1,...); +// 'Types' are the stored type for each argument, see ParameterStorage for +// details. +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::OwningRunnableMethod<PtrType, Method>> +NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::OwningRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::NonOwningRunnableMethod<PtrType, Method>> +NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::CancelableRunnableMethod<PtrType, Method>> +NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::CancelableRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::NonOwningCancelableRunnableMethod<PtrType, Method>> +NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod, Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningCancelableRunnableMethodImpl<PtrType, Method, + Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::IdleRunnableMethod<PtrType, Method>> +NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::IdleRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +template <typename... Storages, typename PtrType, typename Method, + typename... Args> +already_AddRefed<detail::NonOwningIdleRunnableMethod<PtrType, Method>> +NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod, Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "<Storages...> size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningIdleRunnableMethodImpl<PtrType, Method, Storages...>( + aName, std::forward<PtrType>(aPtr), aMethod, + std::forward<Args>(aArgs)...)); +} + +} // namespace mozilla + +#endif // XPCOM_GLUE_AVOID_NSPR + +// This class is designed to be used when you have an event class E that has a +// pointer back to resource class R. If R goes away while E is still pending, +// then it is important to "revoke" E so that it does not try use R after R has +// been destroyed. nsRevocableEventPtr makes it easy for R to manage such +// situations: +// +// class R; +// +// class E : public mozilla::Runnable { +// public: +// void Revoke() { +// mResource = nullptr; +// } +// private: +// R *mResource; +// }; +// +// class R { +// public: +// void EventHandled() { +// mEvent.Forget(); +// } +// private: +// nsRevocableEventPtr<E> mEvent; +// }; +// +// void R::PostEvent() { +// // Make sure any pending event is revoked. +// mEvent->Revoke(); +// +// nsCOMPtr<nsIRunnable> event = new E(); +// if (NS_SUCCEEDED(NS_DispatchToCurrentThread(event))) { +// // Keep pointer to event so we can revoke it. +// mEvent = event; +// } +// } +// +// NS_IMETHODIMP E::Run() { +// if (!mResource) +// return NS_OK; +// ... +// mResource->EventHandled(); +// return NS_OK; +// } +// +template <class T> +class nsRevocableEventPtr { + public: + nsRevocableEventPtr() : mEvent(nullptr) {} + ~nsRevocableEventPtr() { Revoke(); } + + const nsRevocableEventPtr& operator=(RefPtr<T>&& aEvent) { + if (mEvent != aEvent) { + Revoke(); + mEvent = std::move(aEvent); + } + return *this; + } + + void Revoke() { + if (mEvent) { + mEvent->Revoke(); + mEvent = nullptr; + } + } + + void Forget() { mEvent = nullptr; } + bool IsPending() { return mEvent != nullptr; } + T* get() { return mEvent; } + + private: + // Not implemented + nsRevocableEventPtr(const nsRevocableEventPtr&); + nsRevocableEventPtr& operator=(const nsRevocableEventPtr&); + + RefPtr<T> mEvent; +}; + +template <class T> +inline already_AddRefed<T> do_AddRef(nsRevocableEventPtr<T>& aObj) { + return do_AddRef(aObj.get()); +} + +/** + * A simple helper to suffix thread pool name + * with incremental numbers. + */ +class nsThreadPoolNaming { + public: + nsThreadPoolNaming() = default; + + /** + * Returns a thread name as "<aPoolName> #<n>" and increments the counter. + */ + nsCString GetNextThreadName(const nsACString& aPoolName); + + template <size_t LEN> + nsCString GetNextThreadName(const char (&aPoolName)[LEN]) { + return GetNextThreadName(nsDependentCString(aPoolName, LEN - 1)); + } + + private: + mozilla::Atomic<uint32_t> mCounter{0}; + + nsThreadPoolNaming(const nsThreadPoolNaming&) = delete; + void operator=(const nsThreadPoolNaming&) = delete; +}; + +/** + * Thread priority in most operating systems affect scheduling, not IO. This + * helper is used to set the current thread to low IO priority for the lifetime + * of the created object. You can only use this low priority IO setting within + * the context of the current thread. + */ +class MOZ_STACK_CLASS nsAutoLowPriorityIO { + public: + nsAutoLowPriorityIO(); + ~nsAutoLowPriorityIO(); + + private: + bool lowIOPrioritySet; +#if defined(XP_MACOSX) + int oldPriority; +#endif +}; + +void NS_SetMainThread(); + +// Used only on cooperatively scheduled "main" threads. Causes the thread to be +// considered a main thread and also causes GetCurrentVirtualThread to return +// aVirtualThread. +void NS_SetMainThread(PRThread* aVirtualThread); + +// Used only on cooperatively scheduled "main" threads. Causes the thread to no +// longer be considered a main thread. Also causes GetCurrentVirtualThread() to +// return a unique value. +void NS_UnsetMainThread(); + +/** + * Return the expiration time of the next timer to run on the current + * thread. If that expiration time is greater than aDefault, then + * return aDefault. aSearchBound specifies a maximum number of timers + * to examine to find a timer on the current thread. If no timer that + * will run on the current thread is found after examining + * aSearchBound timers, return the highest seen expiration time as a + * best effort guess. + * + * Timers with either the type nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY or + * nsITIMER::TYPE_REPEATING_SLACK_LOW_PRIORITY will be skipped when + * searching for the next expiration time. This enables timers to + * have lower priority than callbacks dispatched from + * nsIThread::IdleDispatch. + */ +extern mozilla::TimeStamp NS_GetTimerDeadlineHintOnCurrentThread( + mozilla::TimeStamp aDefault, uint32_t aSearchBound); + +/** + * Dispatches the given event to a background thread. The primary benefit of + * this API is that you do not have to manage the lifetime of your own thread + * for running your own events; the thread manager will take care of the + * background thread's lifetime. Not having to manage your own thread also + * means less resource usage, as the underlying implementation here can manage + * spinning up and shutting down threads appropriately. + * + * NOTE: there is no guarantee that events dispatched via these APIs are run + * serially, in dispatch order; several dispatched events may run in parallel. + * If you depend on serial execution of dispatched events, you should use + * NS_CreateBackgroundTaskQueue instead, and dispatch events to the returned + * event target. + */ +extern nsresult NS_DispatchBackgroundTask( + already_AddRefed<nsIRunnable> aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); +extern "C" nsresult NS_DispatchBackgroundTask( + nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + +/** + * Obtain a new serial event target that dispatches runnables to a background + * thread. In many cases, this is a straight replacement for creating your + * own, private thread, and is generally preferred to creating your own, + * private thread. + */ +extern "C" nsresult NS_CreateBackgroundTaskQueue( + const char* aName, nsISerialEventTarget** aTarget); + +// Predeclaration for logging function below +namespace IPC { +class Message; +} + +class nsTimerImpl; + +namespace mozilla { + +// RAII class that will set the TLS entry to return the currently running +// nsISerialEventTarget. +// It should be used from inner event loop implementation. +class SerialEventTargetGuard { + public: + explicit SerialEventTargetGuard(nsISerialEventTarget* aThread) + : mLastCurrentThread(sCurrentThreadTLS.get()) { + Set(aThread); + } + + ~SerialEventTargetGuard() { sCurrentThreadTLS.set(mLastCurrentThread); } + + static void InitTLS(); + static nsISerialEventTarget* GetCurrentSerialEventTarget() { + return sCurrentThreadTLS.get(); + } + + protected: + friend class ::MessageLoop; + static void Set(nsISerialEventTarget* aThread) { + MOZ_ASSERT(aThread->IsOnCurrentThread()); + sCurrentThreadTLS.set(aThread); + } + + private: + static MOZ_THREAD_LOCAL(nsISerialEventTarget*) sCurrentThreadTLS; + nsISerialEventTarget* mLastCurrentThread; +}; + +// These functions return event targets that can be used to dispatch to the +// current or main thread. They can also be used to test if you're on those +// threads (via IsOnCurrentThread). These functions should be used in preference +// to the nsIThread-based NS_Get{Current,Main}Thread functions since they will +// return more useful answers in the case of threads sharing an event loop. + +nsIEventTarget* GetCurrentEventTarget(); + +nsIEventTarget* GetMainThreadEventTarget(); + +// These variants of the above functions assert that the given thread has a +// serial event target (i.e., that it's not part of a thread pool) and returns +// that. + +nsISerialEventTarget* GetCurrentSerialEventTarget(); + +nsISerialEventTarget* GetMainThreadSerialEventTarget(); + +// Returns a wrapper around the current thread which routes normal dispatches +// through the tail dispatcher. +// This means that they will run at the end of the current task, rather than +// after all the subsequent tasks queued. This is useful to allow MozPromise +// callbacks returned by IPDL methods to avoid an extra trip through the event +// loop, and thus maintain correct ordering relative to other IPC events. The +// current thread implementation must support tail dispatch. +class TailDispatchingTarget : public nsISerialEventTarget { + public: + NS_DECL_THREADSAFE_ISUPPORTS + TailDispatchingTarget() +#if DEBUG + : mOwnerThread(AbstractThread::GetCurrent()) +#endif + { + MOZ_ASSERT(mOwnerThread, "Must be used with AbstractThreads"); + } + + NS_IMETHOD + Dispatch(already_AddRefed<nsIRunnable> event, uint32_t flags) override { + MOZ_ASSERT(flags == DISPATCH_NORMAL); + MOZ_ASSERT( + AbstractThread::GetCurrent() == mOwnerThread, + "TailDispatchingTarget can only be used on the thread upon which it " + "was created - see the comment on the class declaration."); + AbstractThread::DispatchDirectTask(std::move(event)); + return NS_OK; + } + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override { return true; } + NS_IMETHOD IsOnCurrentThread(bool* _retval) override { + *_retval = true; + return NS_OK; + } + NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override { + MOZ_ASSERT_UNREACHABLE("not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable> event, + uint32_t delay) override { + MOZ_ASSERT_UNREACHABLE("not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + private: + virtual ~TailDispatchingTarget() = default; +#if DEBUG + const RefPtr<AbstractThread> mOwnerThread; +#endif +}; + +// Returns the number of CPUs, like PR_GetNumberOfProcessors, except +// that it can return a cached value on platforms where sandboxing +// would prevent reading the current value (currently Linux). CPU +// hotplugging is uncommon, so this is unlikely to make a difference +// in practice. +size_t GetNumberOfProcessors(); + +/** + * A helper class to log tasks dispatch and run with "MOZ_LOG=events:1". The + * output is more machine readable and creates a link between dispatch and run. + * + * Usage example for the concrete template type nsIRunnable. + * To log a dispatch, which means putting an event to a queue: + * LogRunnable::LogDispatch(event); + * theQueue.putEvent(event); + * + * To log execution (running) of the event: + * nsCOMPtr<nsIRunnable> event = theQueue.popEvent(); + * { + * LogRunnable::Run log(event); + * event->Run(); + * event = null; // to include the destructor code in the span + * } + * + * The class is a template so that we can support various specific super-types + * of tasks in the future. We can't use void* because it may cast differently + * and tracking the pointer in logs would then be impossible. + */ +template <typename T> +class LogTaskBase { + public: + LogTaskBase() = delete; + + // Adds a simple log about dispatch of this runnable. + static void LogDispatch(T* aEvent); + // The `aContext` pointer adds another uniqe identifier, nothing more + static void LogDispatch(T* aEvent, void* aContext); + + // Logs dispatch of the message and along that also the PID of the target + // proccess, purposed for uniquely identifying IPC messages. + static void LogDispatchWithPid(T* aEvent, int32_t aPid); + + // This is designed to surround a call to `Run()` or any code representing + // execution of the task body. + // The constructor adds a simple log about start of the runnable execution and + // the destructor adds a log about ending the execution. + class MOZ_RAII Run { + public: + Run() = delete; + explicit Run(T* aEvent, bool aWillRunAgain = false); + explicit Run(T* aEvent, void* aContext, bool aWillRunAgain = false); + ~Run(); + + // When this is called, the log in this RAII dtor will only say + // "interrupted" expecting that the event will run again. + void WillRunAgain() { mWillRunAgain = true; } + + private: + bool mWillRunAgain = false; + }; +}; + +class MicroTaskRunnable; +class Task; // TaskController +class PresShell; +namespace dom { +class FrameRequestCallback; +} // namespace dom + +// Specialized methods must be explicitly predeclared. +template <> +LogTaskBase<nsIRunnable>::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain); +template <> +LogTaskBase<Task>::Run::Run(Task* aTask, bool aWillRunAgain); +template <> +void LogTaskBase<IPC::Message>::LogDispatchWithPid(IPC::Message* aEvent, + int32_t aPid); +template <> +LogTaskBase<IPC::Message>::Run::Run(IPC::Message* aMessage, bool aWillRunAgain); +template <> +LogTaskBase<nsTimerImpl>::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain); + +typedef LogTaskBase<nsIRunnable> LogRunnable; +typedef LogTaskBase<MicroTaskRunnable> LogMicroTaskRunnable; +typedef LogTaskBase<IPC::Message> LogIPCMessage; +typedef LogTaskBase<nsTimerImpl> LogTimerEvent; +typedef LogTaskBase<Task> LogTask; +typedef LogTaskBase<PresShell> LogPresShellObserver; +typedef LogTaskBase<dom::FrameRequestCallback> LogFrameRequestCallback; +// If you add new types don't forget to add: +// `template class LogTaskBase<YourType>;` to nsThreadUtils.cpp + +class DelayedRunnable : public mozilla::Runnable, public nsITimerCallback { + public: + DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget, + already_AddRefed<nsIRunnable> aRunnable, uint32_t aDelay); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSITIMERCALLBACK + + nsresult Init(); + + private: + ~DelayedRunnable() = default; + nsresult DoRun(); + + const nsCOMPtr<nsIEventTarget> mTarget; + nsCOMPtr<nsIRunnable> mWrappedRunnable; + nsCOMPtr<nsITimer> mTimer; + const mozilla::TimeStamp mDelayedFrom; + uint32_t mDelay; +}; + +} // namespace mozilla + +#endif // nsThreadUtils_h__ diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp new file mode 100644 index 0000000000..5e9061c9bd --- /dev/null +++ b/xpcom/threads/nsTimerImpl.cpp @@ -0,0 +1,804 @@ +/* -*- 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 "nsTimerImpl.h" + +#include <utility> + +#include "GeckoProfiler.h" +#include "TimerThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Sprintf.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "pratom.h" +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracerImpl.h" +using namespace mozilla::tasktracer; +#endif + +#ifdef XP_WIN +# include <process.h> +# ifndef getpid +# define getpid _getpid +# endif +#else +# include <unistd.h> +#endif + +using mozilla::Atomic; +using mozilla::LogLevel; +using mozilla::MakeRefPtr; +using mozilla::MutexAutoLock; +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +static TimerThread* gThread = nullptr; + +// This module prints info about the precision of timers. +static mozilla::LazyLogModule sTimerLog("nsTimerImpl"); + +mozilla::LogModule* GetTimerLog() { return sTimerLog; } + +TimeStamp NS_GetTimerDeadlineHintOnCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound) { + return gThread + ? gThread->FindNextFireTimeForCurrentThread(aDefault, aSearchBound) + : TimeStamp(); +} + +already_AddRefed<nsITimer> NS_NewTimer() { return NS_NewTimer(nullptr); } + +already_AddRefed<nsITimer> NS_NewTimer(nsIEventTarget* aTarget) { + return nsTimer::WithEventTarget(aTarget).forget(); +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithObserver( + nsIObserver* aObserver, uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithObserver(getter_AddRefs(timer), aObserver, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithObserver(nsITimer** aTimer, nsIObserver* aObserver, + uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->Init(aObserver, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback( + nsITimerCallback* aCallback, uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback, + uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithCallback(aCallback, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithCallback( + nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback, + const TimeDuration& aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitHighResolutionWithCallback(aCallback, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay, + uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback, + aClosure, aDelay, aType, aNameString, + aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, uint32_t aDelay, + uint32_t aType, const char* aNameString, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithNamedFuncCallback(aCallback, aClosure, aDelay, aType, + aNameString)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result<nsCOMPtr<nsITimer>, nsresult> NS_NewTimerWithFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay, + uint32_t aType, nsTimerNameCallbackFunc aNameCallback, + nsIEventTarget* aTarget) { + nsCOMPtr<nsITimer> timer; + MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback, + aClosure, aDelay, aType, aNameCallback, + aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, uint32_t aDelay, + uint32_t aType, + nsTimerNameCallbackFunc aNameCallback, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithNameableFuncCallback(aCallback, aClosure, aDelay, + aType, aNameCallback)); + timer.forget(aTimer); + return NS_OK; +} + +// This module prints info about which timers are firing, which is useful for +// wakeups for the purposes of power profiling. Set the following environment +// variable before starting the browser. +// +// MOZ_LOG=TimerFirings:4 +// +// Then a line will be printed for every timer that fires. The name used for a +// |Callback::Type::Function| timer depends on the circumstances. +// +// - If it was explicitly named (e.g. it was initialized with +// InitWithNamedFuncCallback()) then that explicit name will be shown. +// +// - Otherwise, if we are on a platform that supports function name lookup +// (Mac or Linux) then the looked-up name will be shown with a +// "[from dladdr]" annotation. On Mac the looked-up name will be immediately +// useful. On Linux it'll need post-processing with `tools/rb/fix_stacks.py`. +// +// - Otherwise, no name will be printed. If many timers hit this case then +// you'll need to re-run the workload on a Mac to find out which timers they +// are, and then give them explicit names. +// +// If you redirect this output to a file called "out", you can then +// post-process it with a command something like the following. +// +// cat out | grep timer | sort | uniq -c | sort -r -n +// +// This will show how often each unique line appears, with the most common ones +// first. +// +// More detailed docs are here: +// https://developer.mozilla.org/en-US/docs/Mozilla/Performance/TimerFirings_logging +// +static mozilla::LazyLogModule sTimerFiringsLog("TimerFirings"); + +static mozilla::LogModule* GetTimerFiringsLog() { return sTimerFiringsLog; } + +#include <math.h> + +double nsTimerImpl::sDeltaSumSquared = 0; +double nsTimerImpl::sDeltaSum = 0; +double nsTimerImpl::sDeltaNum = 0; + +static void myNS_MeanAndStdDev(double n, double sumOfValues, + double sumOfSquaredValues, double* meanResult, + double* stdDevResult) { + double mean = 0.0, var = 0.0, stdDev = 0.0; + if (n > 0.0 && sumOfValues >= 0) { + mean = sumOfValues / n; + double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues); + if (temp < 0.0 || n <= 1) { + var = 0.0; + } else { + var = temp / (n * (n - 1)); + } + // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: + stdDev = var != 0.0 ? sqrt(var) : 0.0; + } + *meanResult = mean; + *stdDevResult = stdDev; +} + +NS_IMPL_QUERY_INTERFACE(nsTimer, nsITimer) +NS_IMPL_ADDREF(nsTimer) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsTimer::Release(void) { + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "nsTimer"); + + if (count == 1) { + // Last ref, in nsTimerImpl::mITimer. Make sure the cycle is broken. + mImpl->CancelImpl(true); + } else if (count == 0) { + delete this; + } + + return count; +} + +nsTimerImpl::nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget) + : mEventTarget(aTarget), + mHolder(nullptr), + mType(0), + mGeneration(0), + mITimer(aTimer), + mMutex("nsTimerImpl::mMutex"), + mFiring(0) { + // XXX some code creates timers during xpcom shutdown, when threads are no + // longer available, so we cannot turn this on yet. + // MOZ_ASSERT(mEventTarget); +} + +// static +nsresult nsTimerImpl::Startup() { + nsresult rv; + + gThread = new TimerThread(); + + NS_ADDREF(gThread); + rv = gThread->InitLocks(); + + if (NS_FAILED(rv)) { + NS_RELEASE(gThread); + } + + return rv; +} + +void nsTimerImpl::Shutdown() { + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + double mean = 0, stddev = 0; + myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", + sDeltaNum, sDeltaSum, sDeltaSumSquared)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("mean: %fms, stddev: %fms\n", mean, stddev)); + } + + if (!gThread) { + return; + } + + gThread->Shutdown(); + NS_RELEASE(gThread); +} + +nsresult nsTimerImpl::InitCommon(uint32_t aDelayMS, uint32_t aType, + Callback&& aNewCallback) { + return InitCommon(TimeDuration::FromMilliseconds(aDelayMS), aType, + std::move(aNewCallback)); +} + +nsresult nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType, + Callback&& newCallback) { + mMutex.AssertCurrentThreadOwns(); + + if (!gThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (!mEventTarget) { + NS_ERROR("mEventTarget is NULL"); + return NS_ERROR_NOT_INITIALIZED; + } + + gThread->RemoveTimer(this); + mCallback.swap(newCallback); + ++mGeneration; + + mType = (uint8_t)aType; + mDelay = aDelay; + mTimeout = TimeStamp::Now() + mDelay; + + return gThread->AddTimer(this); +} + +nsresult nsTimerImpl::InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const Callback::Name& aName) { + if (NS_WARN_IF(!aFunc)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb; // Goes out of scope after the unlock, prevents deadlock + cb.mType = Callback::Type::Function; + cb.mCallback.c = aFunc; + cb.mClosure = aClosure; + cb.mName = aName; + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb)); +} + +nsresult nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, uint32_t aDelay, + uint32_t aType, + const char* aNameString) { + Callback::Name name(aNameString); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + +nsresult nsTimerImpl::InitWithNameableFuncCallback( + nsTimerCallbackFunc aFunc, void* aClosure, uint32_t aDelay, uint32_t aType, + nsTimerNameCallbackFunc aNameFunc) { + Callback::Name name(aNameFunc); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + +nsresult nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback, + uint32_t aDelayInMs, uint32_t aType) { + return InitHighResolutionWithCallback( + aCallback, TimeDuration::FromMilliseconds(aDelayInMs), aType); +} + +nsresult nsTimerImpl::InitHighResolutionWithCallback( + nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType) { + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb; // Goes out of scope after the unlock, prevents deadlock + cb.mType = Callback::Type::Interface; + cb.mCallback.i = aCallback; + NS_ADDREF(cb.mCallback.i); + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb)); +} + +nsresult nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelayInMs, + uint32_t aType) { + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb; // Goes out of scope after the unlock, prevents deadlock + cb.mType = Callback::Type::Observer; + cb.mCallback.o = aObserver; + NS_ADDREF(cb.mCallback.o); + + MutexAutoLock lock(mMutex); + return InitCommon(aDelayInMs, aType, std::move(cb)); +} + +nsresult nsTimerImpl::Cancel() { + CancelImpl(false); + return NS_OK; +} + +void nsTimerImpl::CancelImpl(bool aClearITimer) { + Callback cbTrash; + RefPtr<nsITimer> timerTrash; + + { + MutexAutoLock lock(mMutex); + if (gThread) { + gThread->RemoveTimer(this); + } + + cbTrash.swap(mCallback); + ++mGeneration; + + // Don't clear this if we're firing; once Fire returns, we'll get this call + // again. + if (aClearITimer && !mFiring) { + MOZ_RELEASE_ASSERT( + mITimer, + "mITimer was nulled already! " + "This indicates that someone has messed up the refcount on nsTimer!"); + timerTrash.swap(mITimer); + } + } +} + +nsresult nsTimerImpl::SetDelay(uint32_t aDelay) { + MutexAutoLock lock(mMutex); + if (GetCallback().mType == Callback::Type::Unknown && !IsRepeating()) { + // This may happen if someone tries to re-use a one-shot timer + // by re-setting delay instead of reinitializing the timer. + NS_ERROR( + "nsITimer->SetDelay() called when the " + "one-shot timer is not set up."); + return NS_ERROR_NOT_INITIALIZED; + } + + bool reAdd = false; + if (gThread) { + reAdd = NS_SUCCEEDED(gThread->RemoveTimer(this)); + } + + mDelay = TimeDuration::FromMilliseconds(aDelay); + mTimeout = TimeStamp::Now() + mDelay; + + if (reAdd) { + gThread->AddTimer(this); + } + + return NS_OK; +} + +nsresult nsTimerImpl::GetDelay(uint32_t* aDelay) { + MutexAutoLock lock(mMutex); + *aDelay = mDelay.ToMilliseconds(); + return NS_OK; +} + +nsresult nsTimerImpl::SetType(uint32_t aType) { + MutexAutoLock lock(mMutex); + mType = (uint8_t)aType; + // XXX if this is called, we should change the actual type.. this could effect + // repeating timers. we need to ensure in Fire() that if mType has changed + // during the callback that we don't end up with the timer in the queue twice. + return NS_OK; +} + +nsresult nsTimerImpl::GetType(uint32_t* aType) { + MutexAutoLock lock(mMutex); + *aType = mType; + return NS_OK; +} + +nsresult nsTimerImpl::GetClosure(void** aClosure) { + MutexAutoLock lock(mMutex); + *aClosure = GetCallback().mClosure; + return NS_OK; +} + +nsresult nsTimerImpl::GetCallback(nsITimerCallback** aCallback) { + MutexAutoLock lock(mMutex); + if (GetCallback().mType == Callback::Type::Interface) { + NS_IF_ADDREF(*aCallback = GetCallback().mCallback.i); + } else { + *aCallback = nullptr; + } + + return NS_OK; +} + +nsresult nsTimerImpl::GetTarget(nsIEventTarget** aTarget) { + MutexAutoLock lock(mMutex); + NS_IF_ADDREF(*aTarget = mEventTarget); + return NS_OK; +} + +nsresult nsTimerImpl::SetTarget(nsIEventTarget* aTarget) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(mCallback.mType != Callback::Type::Unknown)) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aTarget) { + mEventTarget = aTarget; + } else { + mEventTarget = mozilla::GetCurrentSerialEventTarget(); + } + return NS_OK; +} + +nsresult nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut) { + *aValueOut = gThread ? gThread->AllowedEarlyFiringMicroseconds() : 0; + return NS_OK; +} + +void nsTimerImpl::Fire(int32_t aGeneration) { + uint8_t oldType; + uint32_t oldDelay; + TimeStamp oldTimeout; + Callback callbackDuringFire; + nsCOMPtr<nsITimer> kungFuDeathGrip; + + { + // Don't fire callbacks or fiddle with refcounts when the mutex is locked. + // If some other thread Cancels/Inits after this, they're just too late. + MutexAutoLock lock(mMutex); + if (aGeneration != mGeneration) { + return; + } + + ++mFiring; + callbackDuringFire = mCallback; + oldType = mType; + oldDelay = mDelay.ToMilliseconds(); + oldTimeout = mTimeout; + // Ensure that the nsITimer does not unhook from the nsTimerImpl during + // Fire; this will cause null pointer crashes if the user of the timer drops + // its reference, and then uses the nsITimer* passed in the callback. + kungFuDeathGrip = mITimer; + } + + AUTO_PROFILER_LABEL("nsTimerImpl::Fire", OTHER); + + TimeStamp now = TimeStamp::Now(); + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + TimeDuration delta = now - oldTimeout; + int32_t d = delta.ToMilliseconds(); // delta in ms + sDeltaSum += abs(d); + sDeltaSumSquared += double(d) * double(d); + sDeltaNum++; + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] expected delay time %4ums\n", this, oldDelay)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] actual delay time %4dms\n", this, oldDelay + d)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] (mType is %d) -------\n", this, oldType)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] delta %4dms\n", this, d)); + } + + if (MOZ_LOG_TEST(GetTimerFiringsLog(), LogLevel::Debug)) { + LogFiring(callbackDuringFire, oldType, oldDelay); + } + + switch (callbackDuringFire.mType) { + case Callback::Type::Function: + callbackDuringFire.mCallback.c(mITimer, callbackDuringFire.mClosure); + break; + case Callback::Type::Interface: + callbackDuringFire.mCallback.i->Notify(mITimer); + break; + case Callback::Type::Observer: + callbackDuringFire.mCallback.o->Observe(mITimer, NS_TIMER_CALLBACK_TOPIC, + nullptr); + break; + default:; + } + + MutexAutoLock lock(mMutex); + if (aGeneration == mGeneration) { + if (IsRepeating()) { + // Repeating timer has not been re-init or canceled; reschedule + if (IsSlack()) { + mTimeout = TimeStamp::Now() + mDelay; + } else { + mTimeout = mTimeout + mDelay; + } + if (gThread) { + gThread->AddTimer(this); + } + } else { + // Non-repeating timer that has not been re-scheduled. Clear. + mCallback.clear(); + } + } + + --mFiring; + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] Took %fms to fire timer callback\n", this, + (TimeStamp::Now() - now).ToMilliseconds())); +} + +#if defined(HAVE_DLADDR) && defined(HAVE___CXA_DEMANGLE) +# define USE_DLADDR 1 +#endif + +#ifdef USE_DLADDR +# include <cxxabi.h> +# include <dlfcn.h> +#endif + +// See the big comment above GetTimerFiringsLog() to understand this code. +void nsTimerImpl::LogFiring(const Callback& aCallback, uint8_t aType, + uint32_t aDelay) { + const char* typeStr; + switch (aType) { + case nsITimer::TYPE_ONE_SHOT: + typeStr = "ONE_SHOT "; + break; + case nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY: + typeStr = "ONE_LOW "; + break; + case nsITimer::TYPE_REPEATING_SLACK: + typeStr = "SLACK "; + break; + case nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY: + typeStr = "SLACK_LOW "; + break; + case nsITimer::TYPE_REPEATING_PRECISE: /* fall through */ + case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP: + typeStr = "PRECISE "; + break; + default: + MOZ_CRASH("bad type"); + } + + switch (aCallback.mType) { + case Callback::Type::Function: { + bool needToFreeName = false; + const char* annotation = ""; + const char* name; + static const size_t buflen = 1024; + char buf[buflen]; + + if (aCallback.mName.is<Callback::NameString>()) { + name = aCallback.mName.as<Callback::NameString>(); + + } else if (aCallback.mName.is<Callback::NameFunc>()) { + aCallback.mName.as<Callback::NameFunc>()( + mITimer, /* aAnonymize = */ false, aCallback.mClosure, buf, buflen); + name = buf; + + } else { + MOZ_ASSERT(aCallback.mName.is<Callback::NameNothing>()); +#ifdef USE_DLADDR + annotation = "[from dladdr] "; + + Dl_info info; + void* addr = reinterpret_cast<void*>(aCallback.mCallback.c); + if (dladdr(addr, &info) == 0) { + name = "???[dladdr: failed]"; + + } else if (info.dli_sname) { + int status; + name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status); + if (status == 0) { + // Success. Because we didn't pass in a buffer to __cxa_demangle it + // allocates its own one with malloc() which we must free() later. + MOZ_ASSERT(name); + needToFreeName = true; + } else if (status == -1) { + name = "???[__cxa_demangle: OOM]"; + } else if (status == -2) { + name = "???[__cxa_demangle: invalid mangled name]"; + } else if (status == -3) { + name = "???[__cxa_demangle: invalid argument]"; + } else { + name = "???[__cxa_demangle: unexpected status value]"; + } + + } else if (info.dli_fname) { + // The "#0: " prefix is necessary for `fix_stacks.py` to interpret + // this string as something to convert. + SprintfLiteral(buf, "#0: ???[%s +0x%" PRIxPTR "]\n", info.dli_fname, + uintptr_t(addr) - uintptr_t(info.dli_fbase)); + name = buf; + + } else { + name = "???[dladdr: no symbol or shared object obtained]"; + } +#else + name = "???[dladdr is unimplemented or doesn't work well on this OS]"; +#endif + } + + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] fn timer (%s %5d ms): %s%s\n", getpid(), typeStr, + aDelay, annotation, name)); + + if (needToFreeName) { + free(const_cast<char*>(name)); + } + + break; + } + + case Callback::Type::Interface: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] iface timer (%s %5d ms): %p\n", getpid(), typeStr, aDelay, + aCallback.mCallback.i)); + break; + } + + case Callback::Type::Observer: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] obs timer (%s %5d ms): %p\n", getpid(), typeStr, aDelay, + aCallback.mCallback.o)); + break; + } + + case Callback::Type::Unknown: + default: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] ??? timer (%s, %5d ms)\n", getpid(), typeStr, aDelay)); + break; + } + } +} + +void nsTimerImpl::GetName(nsACString& aName) { + MutexAutoLock lock(mMutex); + Callback& cb(GetCallback()); + switch (cb.mType) { + case Callback::Type::Function: + if (cb.mName.is<Callback::NameString>()) { + aName.Assign(cb.mName.as<Callback::NameString>()); + } else if (cb.mName.is<Callback::NameFunc>()) { + static const size_t buflen = 1024; + char buf[buflen]; + cb.mName.as<Callback::NameFunc>()(mITimer, /* aAnonymize = */ true, + cb.mClosure, buf, buflen); + aName.Assign(buf); + } else { + MOZ_ASSERT(cb.mName.is<Callback::NameNothing>()); + aName.AssignLiteral("Anonymous_callback_timer"); + } + break; + + case Callback::Type::Interface: + if (nsCOMPtr<nsINamed> named = do_QueryInterface(cb.mCallback.i)) { + named->GetName(aName); + } else { + aName.AssignLiteral("Anonymous_interface_timer"); + } + break; + + case Callback::Type::Observer: + if (nsCOMPtr<nsINamed> named = do_QueryInterface(cb.mCallback.o)) { + named->GetName(aName); + } else { + aName.AssignLiteral("Anonymous_observer_timer"); + } + break; + + case Callback::Type::Unknown: + aName.AssignLiteral("Canceled_timer"); + break; + } +} + +void nsTimerImpl::SetHolder(nsTimerImplHolder* aHolder) { mHolder = aHolder; } + +nsTimer::~nsTimer() = default; + +size_t nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this); +} + +/* static */ +RefPtr<nsTimer> nsTimer::WithEventTarget(nsIEventTarget* aTarget) { + if (!aTarget) { + aTarget = mozilla::GetCurrentSerialEventTarget(); + } + return do_AddRef(new nsTimer(aTarget)); +} + +/* static */ +nsresult nsTimer::XPCOMConstructor(nsISupports* aOuter, REFNSIID aIID, + void** aResult) { + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + auto timer = WithEventTarget(nullptr); + + return timer->QueryInterface(aIID, aResult); +} + +/* static */ +const nsTimerImpl::Callback::NameNothing nsTimerImpl::Callback::Nothing = 0; + +#ifdef MOZ_TASK_TRACER +void nsTimerImpl::GetTLSTraceInfo() { mTracedTask.GetTLSTraceInfo(); } + +TracedTaskCommon nsTimerImpl::GetTracedTask() { return mTracedTask; } + +#endif diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h new file mode 100644 index 0000000000..30bcc76ed3 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.h @@ -0,0 +1,286 @@ +/* -*- 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/. */ + +#ifndef nsTimerImpl_h___ +#define nsTimerImpl_h___ + +#include "nsITimer.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" + +#include "nsCOMPtr.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" + +#ifdef MOZ_TASK_TRACER +# include "TracedTaskCommon.h" +#endif + +extern mozilla::LogModule* GetTimerLog(); + +#define NS_TIMER_CID \ + { /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */ \ + 0x5ff24248, 0x1dd2, 0x11b2, { \ + 0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8 \ + } \ + } + +class nsIObserver; +class nsTimerImplHolder; + +namespace mozilla { +class LogModule; +} + +// TimerThread, nsTimerEvent, and nsTimer have references to these. nsTimer has +// a separate lifecycle so we can Cancel() the underlying timer when the user of +// the nsTimer has let go of its last reference. +class nsTimerImpl { + ~nsTimerImpl() { MOZ_ASSERT(!mHolder); } + + public: + typedef mozilla::TimeStamp TimeStamp; + + nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsTimerImpl) + NS_DECL_NON_VIRTUAL_NSITIMER + + static nsresult Startup(); + static void Shutdown(); + + void SetDelayInternal(uint32_t aDelay, TimeStamp aBase = TimeStamp::Now()); + void CancelImpl(bool aClearITimer); + + void Fire(int32_t aGeneration); + +#ifdef MOZ_TASK_TRACER + void GetTLSTraceInfo(); + mozilla::tasktracer::TracedTaskCommon GetTracedTask(); +#endif + + int32_t GetGeneration() { return mGeneration; } + + struct Callback { + Callback() : mType(Type::Unknown), mName(Nothing), mClosure(nullptr) { + mCallback.c = nullptr; + } + + Callback(const Callback& other) : Callback() { *this = other; } + + enum class Type : uint8_t { + Unknown = 0, + Interface = 1, + Function = 2, + Observer = 3, + }; + + Callback& operator=(const Callback& other) { + if (this != &other) { + clear(); + mType = other.mType; + switch (mType) { + case Type::Unknown: + break; + case Type::Interface: + mCallback.i = other.mCallback.i; + NS_ADDREF(mCallback.i); + break; + case Type::Function: + mCallback.c = other.mCallback.c; + break; + case Type::Observer: + mCallback.o = other.mCallback.o; + NS_ADDREF(mCallback.o); + break; + } + mName = other.mName; + mClosure = other.mClosure; + } + return *this; + } + + ~Callback() { clear(); } + + void clear() { + if (mType == Type::Interface) { + NS_RELEASE(mCallback.i); + } else if (mType == Type::Observer) { + NS_RELEASE(mCallback.o); + } + mType = Type::Unknown; + } + + void swap(Callback& other) { + std::swap(mType, other.mType); + std::swap(mCallback, other.mCallback); + std::swap(mName, other.mName); + std::swap(mClosure, other.mClosure); + } + + Type mType; + + union CallbackUnion { + nsTimerCallbackFunc c; + // These refcounted references are managed manually, as they are in a + // union + nsITimerCallback* MOZ_OWNING_REF i; + nsIObserver* MOZ_OWNING_REF o; + } mCallback; + + // |Name| is a tagged union type representing one of (a) nothing, (b) a + // string, or (c) a function. mozilla::Variant doesn't naturally handle the + // "nothing" case, so we define a dummy type and value (which is unused and + // so the exact value doesn't matter) for it. + typedef const int NameNothing; + typedef const char* NameString; + typedef nsTimerNameCallbackFunc NameFunc; + typedef mozilla::Variant<NameNothing, NameString, NameFunc> Name; + static const NameNothing Nothing; + Name mName; + + void* mClosure; + }; + + nsresult InitCommon(uint32_t aDelayMS, uint32_t aType, + Callback&& newCallback); + + nsresult InitCommon(const mozilla::TimeDuration& aDelay, uint32_t aType, + Callback&& newCallback); + + Callback& GetCallback() { + mMutex.AssertCurrentThreadOwns(); + return mCallback; + } + + bool IsRepeating() const { + static_assert(nsITimer::TYPE_ONE_SHOT < nsITimer::TYPE_REPEATING_SLACK, + "invalid ordering of timer types!"); + static_assert( + nsITimer::TYPE_REPEATING_SLACK < nsITimer::TYPE_REPEATING_PRECISE, + "invalid ordering of timer types!"); + static_assert(nsITimer::TYPE_REPEATING_PRECISE < + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "invalid ordering of timer types!"); + return mType >= nsITimer::TYPE_REPEATING_SLACK && + mType < nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY; + } + + bool IsLowPriority() const { + return mType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + bool IsSlack() const { + return mType == nsITimer::TYPE_REPEATING_SLACK || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + void GetName(nsACString& aName); + + void SetHolder(nsTimerImplHolder* aHolder); + + nsCOMPtr<nsIEventTarget> mEventTarget; + + void LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay); + + nsresult InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc, void* aClosure, + uint32_t aDelay, uint32_t aType, + const Callback::Name& aName); + + // This weak reference must be cleared by the nsTimerImplHolder by calling + // SetHolder(nullptr) before the holder is destroyed. + nsTimerImplHolder* mHolder; + + // These members are set by the initiating thread, when the timer's type is + // changed and during the period where it fires on that thread. + uint8_t mType; + + // The generation number of this timer, re-generated each time the timer is + // initialized so one-shot timers can be canceled and re-initialized by the + // arming thread without any bad race conditions. + // Updated only after this timer has been removed from the timer thread. + int32_t mGeneration; + + mozilla::TimeDuration mDelay; + // Updated only after this timer has been removed from the timer thread. + mozilla::TimeStamp mTimeout; + +#ifdef MOZ_TASK_TRACER + mozilla::tasktracer::TracedTaskCommon mTracedTask; +#endif + + static double sDeltaSum; + static double sDeltaSumSquared; + static double sDeltaNum; + RefPtr<nsITimer> mITimer; + mozilla::Mutex mMutex; + Callback mCallback; + // Counter because in rare cases we can Fire reentrantly + unsigned int mFiring; +}; + +class nsTimer final : public nsITimer { + explicit nsTimer(nsIEventTarget* aTarget) + : mImpl(new nsTimerImpl(this, aTarget)) {} + + virtual ~nsTimer(); + + public: + friend class TimerThread; + friend class nsTimerEvent; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSITIMER(mImpl); + + virtual size_t SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const override; + + // Create a timer targeting the given target. nullptr indicates that the + // current thread should be used as the timer's target. + static RefPtr<nsTimer> WithEventTarget(nsIEventTarget* aTarget); + + static nsresult XPCOMConstructor(nsISupports* aOuter, REFNSIID aIID, + void** aResult); + + private: + // nsTimerImpl holds a strong ref to us. When our refcount goes to 1, we will + // null this to break the cycle. + RefPtr<nsTimerImpl> mImpl; +}; + +// A class that holds on to an nsTimerImpl. This lets the nsTimerImpl object +// directly instruct its holder to forget the timer, avoiding list lookups. +class nsTimerImplHolder { + public: + explicit nsTimerImplHolder(nsTimerImpl* aTimerImpl) : mTimerImpl(aTimerImpl) { + if (mTimerImpl) { + mTimerImpl->SetHolder(this); + } + } + + ~nsTimerImplHolder() { + if (mTimerImpl) { + mTimerImpl->SetHolder(nullptr); + } + } + + void Forget(nsTimerImpl* aTimerImpl) { + if (MOZ_UNLIKELY(!mTimerImpl)) { + return; + } + MOZ_ASSERT(aTimerImpl == mTimerImpl); + mTimerImpl->SetHolder(nullptr); + mTimerImpl = nullptr; + } + + protected: + RefPtr<nsTimerImpl> mTimerImpl; +}; + +#endif /* nsTimerImpl_h___ */ |