diff options
Diffstat (limited to '')
-rw-r--r-- | xpcom/threads/SpinEventLoopUntil.h | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/xpcom/threads/SpinEventLoopUntil.h b/xpcom/threads/SpinEventLoopUntil.h new file mode 100644 index 0000000000..a281177268 --- /dev/null +++ b/xpcom/threads/SpinEventLoopUntil.h @@ -0,0 +1,191 @@ +/* -*- 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 "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StaticMutex.h" +#include "nsString.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, +}; + +// SpinEventLoopUntil is a dangerous operation that can result in hangs. +// In particular during shutdown we want to know if we are hanging +// inside a nested event loop on the main thread. +// This is a helper annotation class to keep track of this. +struct MOZ_STACK_CLASS AutoNestedEventLoopAnnotation { + explicit AutoNestedEventLoopAnnotation(const nsACString& aEntry) + : mPrev(nullptr) { + if (NS_IsMainThread()) { + StaticMutexAutoLock lock(sStackMutex); + mPrev = sCurrent; + sCurrent = this; + if (mPrev) { + mStack = mPrev->mStack + "|"_ns + aEntry; + } else { + mStack = aEntry; + } + AnnotateXPCOMSpinEventLoopStack(mStack); + } + } + + ~AutoNestedEventLoopAnnotation() { + if (NS_IsMainThread()) { + StaticMutexAutoLock lock(sStackMutex); + MOZ_ASSERT(sCurrent == this); + sCurrent = mPrev; + if (mPrev) { + AnnotateXPCOMSpinEventLoopStack(mPrev->mStack); + } else { + AnnotateXPCOMSpinEventLoopStack(""_ns); + } + } + } + + static void CopyCurrentStack(nsCString& aNestedSpinStack) { + // We need to copy this behind a mutex as the + // memory for our instances is stack-bound and + // can go away at any time. + StaticMutexAutoLock lock(sStackMutex); + if (sCurrent) { + aNestedSpinStack = sCurrent->mStack; + } else { + aNestedSpinStack = "(no nested event loop active)"_ns; + } + } + + private: + AutoNestedEventLoopAnnotation(const AutoNestedEventLoopAnnotation&) = delete; + AutoNestedEventLoopAnnotation& operator=( + const AutoNestedEventLoopAnnotation&) = delete; + + // The declarations of these statics live in nsThreadManager.cpp. + static AutoNestedEventLoopAnnotation* sCurrent MOZ_GUARDED_BY(sStackMutex); + static StaticMutex sStackMutex; + + // We need this to avoid the inclusion of nsExceptionHandler.h here + // which can include windows.h which disturbs some dom/media/gtest. + // The implementation lives in nsThreadManager.cpp. + static void AnnotateXPCOMSpinEventLoopStack(const nsACString& aStack); + + AutoNestedEventLoopAnnotation* mPrev MOZ_GUARDED_BY(sStackMutex); + nsCString mStack MOZ_GUARDED_BY(sStackMutex); +}; + +// Please see the above notes for the Behavior template parameter. +// +// aVeryGoodReasonToDoThis is usually a literal string unique to each +// caller that can be recognized in the XPCOMSpinEventLoopStack +// annotation. +// aPredicate is the condition we wait for. +// aThread can be used to specify a thread, see the above introduction. +// It defaults to the current thread. +template < + ProcessFailureBehavior Behavior = ProcessFailureBehavior::ReportToCaller, + typename Pred> +bool SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis, + Pred&& aPredicate, nsIThread* aThread = nullptr) { + // Prepare the annotations + AutoNestedEventLoopAnnotation annotation(aVeryGoodReasonToDoThis); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "SpinEventLoopUntil", OTHER, aVeryGoodReasonToDoThis); + AUTO_PROFILER_MARKER_TEXT("SpinEventLoop", OTHER, MarkerStack::Capture(), + aVeryGoodReasonToDoThis); + + 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__ |