1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 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__
|