summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/SpinEventLoopUntil.h
blob: a28117726879f2dda5ac74c05a4f92820f886b5e (plain)
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__