1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/threading/thread_restrictions.h"
6
7 #include "base/check.h"
8 #include "base/threading/hang_watcher.h"
9 #include "base/trace_event/base_tracing.h"
10 #include "build/build_config.h"
11
12 namespace base {
13
BooleanWithOptionalStack(bool value)14 BooleanWithOptionalStack::BooleanWithOptionalStack(bool value) : value_(value) {
15 #if CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES()
16 stack_.emplace();
17 #endif // CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES()
18 }
19
operator <<(std::ostream & out,const BooleanWithOptionalStack & bws)20 std::ostream& operator<<(std::ostream& out,
21 const BooleanWithOptionalStack& bws) {
22 out << bws.value_;
23 #if CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES()
24 if (bws.stack_.has_value()) {
25 out << " set by\n" << bws.stack_.value();
26 } else {
27 out << " (value by default)";
28 }
29 #endif // CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES()
30 return out;
31 }
32
33 // A macro that dumps in official builds (non-fatal) if the condition is false,
34 // or behaves as DCHECK in DCHECK-enabled builds. Unlike DUMP_WILL_BE_CHECK,
35 // there is no intent to transform those into CHECKs. Used to report potential
36 // performance issues.
37 //
38 // TODO(crbug.com/363049758): This is temporarily a `DCHECK` to avoid getting a
39 // lot of crash reports while known issues are being addressed. Change to
40 // `DUMP_WILL_BE_CHECK` once known issues are addressed.
41 #define DUMP_OR_DCHECK DCHECK
42
43 namespace {
44
45 constinit thread_local BooleanWithOptionalStack tls_blocking_disallowed;
46 constinit thread_local BooleanWithOptionalStack tls_singleton_disallowed;
47 constinit thread_local BooleanWithOptionalStack
48 tls_base_sync_primitives_disallowed;
49 constinit thread_local BooleanWithOptionalStack
50 tls_cpu_intensive_work_disallowed;
51
52 } // namespace
53
AssertBlockingAllowed()54 void AssertBlockingAllowed() {
55 DUMP_OR_DCHECK(!tls_blocking_disallowed)
56 << "Function marked as blocking was called from a scope that disallows "
57 "blocking! If this task is running inside the ThreadPool, it needs "
58 "to have MayBlock() in its TaskTraits. Otherwise, consider making "
59 "this blocking work asynchronous or, as a last resort, you may use "
60 "ScopedAllowBlocking (see its documentation for best practices).\n"
61 << "tls_blocking_disallowed " << tls_blocking_disallowed;
62 }
63
AssertBlockingDisallowedForTesting()64 void AssertBlockingDisallowedForTesting() {
65 DCHECK(tls_blocking_disallowed)
66 << "tls_blocking_disallowed " << tls_blocking_disallowed;
67 }
68
DisallowBlocking()69 void DisallowBlocking() {
70 tls_blocking_disallowed = BooleanWithOptionalStack(true);
71 }
72
ScopedDisallowBlocking()73 ScopedDisallowBlocking::ScopedDisallowBlocking()
74 : resetter_(&tls_blocking_disallowed, BooleanWithOptionalStack(true)) {}
75
~ScopedDisallowBlocking()76 ScopedDisallowBlocking::~ScopedDisallowBlocking() {
77 DCHECK(tls_blocking_disallowed)
78 << "~ScopedDisallowBlocking() running while surprisingly already no "
79 "longer disallowed.\n"
80 << "tls_blocking_disallowed " << tls_blocking_disallowed;
81 }
82
DisallowBaseSyncPrimitives()83 void DisallowBaseSyncPrimitives() {
84 tls_base_sync_primitives_disallowed = BooleanWithOptionalStack(true);
85 }
86
ScopedDisallowBaseSyncPrimitives()87 ScopedDisallowBaseSyncPrimitives::ScopedDisallowBaseSyncPrimitives()
88 : resetter_(&tls_base_sync_primitives_disallowed,
89 BooleanWithOptionalStack(true)) {}
90
~ScopedDisallowBaseSyncPrimitives()91 ScopedDisallowBaseSyncPrimitives::~ScopedDisallowBaseSyncPrimitives() {
92 DCHECK(tls_base_sync_primitives_disallowed)
93 << "~ScopedDisallowBaseSyncPrimitives() running while surprisingly "
94 "already no longer disallowed.\n"
95 << "tls_base_sync_primitives_disallowed "
96 << tls_base_sync_primitives_disallowed;
97 }
98
ScopedAllowBaseSyncPrimitives()99 ScopedAllowBaseSyncPrimitives::ScopedAllowBaseSyncPrimitives()
100 : resetter_(&tls_base_sync_primitives_disallowed,
101 BooleanWithOptionalStack(false)) {
102 DCHECK(!tls_blocking_disallowed)
103 << "To allow //base sync primitives in a scope where blocking is "
104 "disallowed use ScopedAllowBaseSyncPrimitivesOutsideBlockingScope.\n"
105 << "tls_blocking_disallowed " << tls_blocking_disallowed;
106 }
107
~ScopedAllowBaseSyncPrimitives()108 ScopedAllowBaseSyncPrimitives::~ScopedAllowBaseSyncPrimitives() {
109 DCHECK(!tls_base_sync_primitives_disallowed)
110 << "~ScopedAllowBaseSyncPrimitives() running while surprisingly already "
111 "no longer allowed.\n"
112 << "tls_base_sync_primitives_disallowed "
113 << tls_base_sync_primitives_disallowed;
114 }
115
116 ScopedAllowBaseSyncPrimitivesForTesting::
ScopedAllowBaseSyncPrimitivesForTesting()117 ScopedAllowBaseSyncPrimitivesForTesting()
118 : resetter_(&tls_base_sync_primitives_disallowed,
119 BooleanWithOptionalStack(false)) {}
120
121 ScopedAllowBaseSyncPrimitivesForTesting::
~ScopedAllowBaseSyncPrimitivesForTesting()122 ~ScopedAllowBaseSyncPrimitivesForTesting() {
123 DCHECK(!tls_base_sync_primitives_disallowed)
124 << "~ScopedAllowBaseSyncPrimitivesForTesting() running while " // IN-TEST
125 "surprisingly already no longer allowed.\n"
126 << "tls_base_sync_primitives_disallowed "
127 << tls_base_sync_primitives_disallowed;
128 }
129
ScopedAllowUnresponsiveTasksForTesting()130 ScopedAllowUnresponsiveTasksForTesting::ScopedAllowUnresponsiveTasksForTesting()
131 : base_sync_resetter_(&tls_base_sync_primitives_disallowed,
132 BooleanWithOptionalStack(false)),
133 blocking_resetter_(&tls_blocking_disallowed,
134 BooleanWithOptionalStack(false)),
135 cpu_resetter_(&tls_cpu_intensive_work_disallowed,
136 BooleanWithOptionalStack(false)) {}
137
138 ScopedAllowUnresponsiveTasksForTesting::
~ScopedAllowUnresponsiveTasksForTesting()139 ~ScopedAllowUnresponsiveTasksForTesting() {
140 DCHECK(!tls_base_sync_primitives_disallowed)
141 << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST
142 "surprisingly already no longer allowed.\n"
143 << "tls_base_sync_primitives_disallowed "
144 << tls_base_sync_primitives_disallowed;
145 DCHECK(!tls_blocking_disallowed)
146 << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST
147 "surprisingly already no longer allowed.\n"
148 << "tls_blocking_disallowed " << tls_blocking_disallowed;
149 DCHECK(!tls_cpu_intensive_work_disallowed)
150 << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST
151 "surprisingly already no longer allowed.\n"
152 << "tls_cpu_intensive_work_disallowed "
153 << tls_cpu_intensive_work_disallowed;
154 }
155
156 namespace internal {
157
AssertBaseSyncPrimitivesAllowed()158 void AssertBaseSyncPrimitivesAllowed() {
159 DUMP_OR_DCHECK(!tls_base_sync_primitives_disallowed)
160 << "Waiting on a //base sync primitive is not allowed on this thread to "
161 "prevent jank and deadlock. If waiting on a //base sync primitive is "
162 "unavoidable, do it within the scope of a "
163 "ScopedAllowBaseSyncPrimitives. If in a test, use "
164 "ScopedAllowBaseSyncPrimitivesForTesting.\n"
165 << "tls_base_sync_primitives_disallowed "
166 << tls_base_sync_primitives_disallowed
167 << "It can be useful to know that tls_blocking_disallowed is "
168 << tls_blocking_disallowed;
169 }
170
ResetThreadRestrictionsForTesting()171 void ResetThreadRestrictionsForTesting() {
172 tls_blocking_disallowed = BooleanWithOptionalStack(false);
173 tls_singleton_disallowed = BooleanWithOptionalStack(false);
174 tls_base_sync_primitives_disallowed = BooleanWithOptionalStack(false);
175 tls_cpu_intensive_work_disallowed = BooleanWithOptionalStack(false);
176 }
177
AssertSingletonAllowed()178 void AssertSingletonAllowed() {
179 DUMP_OR_DCHECK(!tls_singleton_disallowed)
180 << "LazyInstance/Singleton is not allowed to be used on this thread. "
181 "Most likely it's because this thread is not joinable (or the current "
182 "task is running with TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN "
183 "semantics), so AtExitManager may have deleted the object on "
184 "shutdown, leading to a potential shutdown crash. If you need to use "
185 "the object from this context, it'll have to be updated to use Leaky "
186 "traits.\n"
187 << "tls_singleton_disallowed " << tls_singleton_disallowed;
188 }
189
190 } // namespace internal
191
DisallowSingleton()192 void DisallowSingleton() {
193 tls_singleton_disallowed = BooleanWithOptionalStack(true);
194 }
195
ScopedDisallowSingleton()196 ScopedDisallowSingleton::ScopedDisallowSingleton()
197 : resetter_(&tls_singleton_disallowed, BooleanWithOptionalStack(true)) {}
198
~ScopedDisallowSingleton()199 ScopedDisallowSingleton::~ScopedDisallowSingleton() {
200 DCHECK(tls_singleton_disallowed)
201 << "~ScopedDisallowSingleton() running while surprisingly already no "
202 "longer disallowed.\n"
203 << "tls_singleton_disallowed " << tls_singleton_disallowed;
204 }
205
AssertLongCPUWorkAllowed()206 void AssertLongCPUWorkAllowed() {
207 DUMP_OR_DCHECK(!tls_cpu_intensive_work_disallowed)
208 << "Function marked as CPU intensive was called from a scope that "
209 "disallows this kind of work! Consider making this work "
210 "asynchronous.\n"
211 << "tls_cpu_intensive_work_disallowed "
212 << tls_cpu_intensive_work_disallowed;
213 }
214
DisallowUnresponsiveTasks()215 void DisallowUnresponsiveTasks() {
216 DisallowBlocking();
217 DisallowBaseSyncPrimitives();
218 tls_cpu_intensive_work_disallowed = BooleanWithOptionalStack(true);
219 }
220
221 // static
AllowBlocking()222 void PermanentThreadAllowance::AllowBlocking() {
223 tls_blocking_disallowed = BooleanWithOptionalStack(false);
224 }
225
226 // static
AllowBaseSyncPrimitives()227 void PermanentThreadAllowance::AllowBaseSyncPrimitives() {
228 tls_base_sync_primitives_disallowed = BooleanWithOptionalStack(false);
229 }
230
ScopedAllowBlocking(const Location & from_here)231 ScopedAllowBlocking::ScopedAllowBlocking(const Location& from_here)
232 : resetter_(&tls_blocking_disallowed, BooleanWithOptionalStack(false)) {
233 TRACE_EVENT_BEGIN(
234 "base", "ScopedAllowBlocking", [&](perfetto::EventContext ctx) {
235 ctx.event()->set_source_location_iid(
236 base::trace_event::InternedSourceLocation::Get(&ctx, from_here));
237 });
238 }
239
~ScopedAllowBlocking()240 ScopedAllowBlocking::~ScopedAllowBlocking() {
241 TRACE_EVENT_END0("base", "ScopedAllowBlocking");
242
243 DCHECK(!tls_blocking_disallowed)
244 << "~ScopedAllowBlocking() running while surprisingly already no longer "
245 "allowed.\n"
246 << "tls_blocking_disallowed " << tls_blocking_disallowed;
247 }
248
249 ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(const Location & from_here)250 ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(const Location& from_here)
251 : resetter_(&tls_base_sync_primitives_disallowed,
252 BooleanWithOptionalStack(false)) {
253 TRACE_EVENT_BEGIN(
254 "base", "ScopedAllowBaseSyncPrimitivesOutsideBlockingScope",
255 [&](perfetto::EventContext ctx) {
256 ctx.event()->set_source_location_iid(
257 base::trace_event::InternedSourceLocation::Get(&ctx, from_here));
258 });
259
260 // Since this object is used to indicate that sync primitives will be used to
261 // wait for an event ignore the current operation for hang watching purposes
262 // since the wait time duration is unknown.
263 base::HangWatcher::InvalidateActiveExpectations();
264 }
265
266 ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()267 ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() {
268 TRACE_EVENT_END0("base", "ScopedAllowBaseSyncPrimitivesOutsideBlockingScope");
269
270 DCHECK(!tls_base_sync_primitives_disallowed)
271 << "~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() running while "
272 "surprisingly already no longer allowed.\n"
273 << "tls_base_sync_primitives_disallowed "
274 << tls_base_sync_primitives_disallowed;
275 }
276
277 } // namespace base
278