зеркало из https://github.com/mozilla/gecko-dev.git
192 строки
7.3 KiB
C++
192 строки
7.3 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#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 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 GUARDED_BY(sStackMutex);
|
|
nsCString mStack 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__
|