| // Copyright 2012 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "base/threading/thread_restrictions.h" | |
| #include "base/check.h" | |
| #include "base/threading/hang_watcher.h" | |
| #include "base/trace_event/interned_args_helper.h" | |
| #include "base/trace_event/typed_macros.h" | |
| #include "build/build_config.h" | |
| namespace base { | |
| BooleanWithOptionalStack::BooleanWithOptionalStack(bool value) : value_(value) { | |
| #if CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES() | |
| // The most useful stack traces are captured when `value` is true. If `value` | |
| // is false we are in a SyncAllow primitive and the asserts that dcheck for | |
| // allowing blocking calls will pass so there is no need to capture a stack | |
| // trace. See https://crbug.com/404645680. | |
| if (value) { | |
| stack_.emplace(); | |
| } | |
| #endif // CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES() | |
| } | |
| std::ostream& operator<<(std::ostream& out, | |
| const BooleanWithOptionalStack& bws) { | |
| out << bws.value_; | |
| #if CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES() | |
| if (bws.stack_.has_value()) { | |
| out << " set by\n" << bws.stack_.value(); | |
| } else { | |
| out << " (value by default)"; | |
| } | |
| #endif // CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES() | |
| return out; | |
| } | |
| // A macro that dumps in official builds (non-fatal) if the condition is false, | |
| // or behaves as DCHECK in DCHECK-enabled builds. Unlike DUMP_WILL_BE_CHECK, | |
| // there is no intent to transform those into CHECKs. Used to report potential | |
| // performance issues. | |
| // | |
| // TODO(crbug.com/363049758): This is temporarily a `DCHECK` to avoid getting a | |
| // lot of crash reports while known issues are being addressed. Change to | |
| // `DUMP_WILL_BE_CHECK` once known issues are addressed. | |
| #define DUMP_OR_DCHECK DCHECK | |
| namespace { | |
| constinit thread_local BooleanWithOptionalStack tls_blocking_disallowed; | |
| constinit thread_local BooleanWithOptionalStack tls_singleton_disallowed; | |
| constinit thread_local BooleanWithOptionalStack | |
| tls_base_sync_primitives_disallowed; | |
| constinit thread_local BooleanWithOptionalStack | |
| tls_cpu_intensive_work_disallowed; | |
| } // namespace | |
| void AssertBlockingAllowed() { | |
| DUMP_OR_DCHECK(!tls_blocking_disallowed) | |
| << "Function marked as blocking was called from a scope that disallows " | |
| "blocking! If this task is running inside the ThreadPool, it needs " | |
| "to have MayBlock() in its TaskTraits. Otherwise, consider making " | |
| "this blocking work asynchronous or, as a last resort, you may use " | |
| "ScopedAllowBlocking (see its documentation for best practices).\n" | |
| << "tls_blocking_disallowed " << tls_blocking_disallowed; | |
| } | |
| void AssertBlockingDisallowedForTesting() { | |
| DCHECK(tls_blocking_disallowed) | |
| << "tls_blocking_disallowed " << tls_blocking_disallowed; | |
| } | |
| void DisallowBlocking() { | |
| tls_blocking_disallowed = BooleanWithOptionalStack(true); | |
| } | |
| ScopedDisallowBlocking::ScopedDisallowBlocking() | |
| : resetter_(&tls_blocking_disallowed, BooleanWithOptionalStack(true)) {} | |
| ScopedDisallowBlocking::~ScopedDisallowBlocking() { | |
| DCHECK(tls_blocking_disallowed) | |
| << "~ScopedDisallowBlocking() running while surprisingly already no " | |
| "longer disallowed.\n" | |
| << "tls_blocking_disallowed " << tls_blocking_disallowed; | |
| } | |
| void DisallowBaseSyncPrimitives() { | |
| tls_base_sync_primitives_disallowed = BooleanWithOptionalStack(true); | |
| } | |
| ScopedDisallowBaseSyncPrimitives::ScopedDisallowBaseSyncPrimitives() | |
| : resetter_(&tls_base_sync_primitives_disallowed, | |
| BooleanWithOptionalStack(true)) {} | |
| ScopedDisallowBaseSyncPrimitives::~ScopedDisallowBaseSyncPrimitives() { | |
| DCHECK(tls_base_sync_primitives_disallowed) | |
| << "~ScopedDisallowBaseSyncPrimitives() running while surprisingly " | |
| "already no longer disallowed.\n" | |
| << "tls_base_sync_primitives_disallowed " | |
| << tls_base_sync_primitives_disallowed; | |
| } | |
| ScopedAllowBaseSyncPrimitives::ScopedAllowBaseSyncPrimitives() | |
| : resetter_(&tls_base_sync_primitives_disallowed, | |
| BooleanWithOptionalStack(false)) { | |
| DCHECK(!tls_blocking_disallowed) | |
| << "To allow //base sync primitives in a scope where blocking is " | |
| "disallowed use ScopedAllowBaseSyncPrimitivesOutsideBlockingScope.\n" | |
| << "tls_blocking_disallowed " << tls_blocking_disallowed; | |
| } | |
| ScopedAllowBaseSyncPrimitives::~ScopedAllowBaseSyncPrimitives() { | |
| DCHECK(!tls_base_sync_primitives_disallowed) | |
| << "~ScopedAllowBaseSyncPrimitives() running while surprisingly already " | |
| "no longer allowed.\n" | |
| << "tls_base_sync_primitives_disallowed " | |
| << tls_base_sync_primitives_disallowed; | |
| } | |
| ScopedAllowBaseSyncPrimitivesForTesting:: | |
| ScopedAllowBaseSyncPrimitivesForTesting() | |
| : resetter_(&tls_base_sync_primitives_disallowed, | |
| BooleanWithOptionalStack(false)) {} | |
| ScopedAllowBaseSyncPrimitivesForTesting:: | |
| ~ScopedAllowBaseSyncPrimitivesForTesting() { | |
| DCHECK(!tls_base_sync_primitives_disallowed) | |
| << "~ScopedAllowBaseSyncPrimitivesForTesting() running while " // IN-TEST | |
| "surprisingly already no longer allowed.\n" | |
| << "tls_base_sync_primitives_disallowed " | |
| << tls_base_sync_primitives_disallowed; | |
| } | |
| ScopedAllowUnresponsiveTasksForTesting::ScopedAllowUnresponsiveTasksForTesting() | |
| : base_sync_resetter_(&tls_base_sync_primitives_disallowed, | |
| BooleanWithOptionalStack(false)), | |
| blocking_resetter_(&tls_blocking_disallowed, | |
| BooleanWithOptionalStack(false)), | |
| cpu_resetter_(&tls_cpu_intensive_work_disallowed, | |
| BooleanWithOptionalStack(false)) {} | |
| ScopedAllowUnresponsiveTasksForTesting:: | |
| ~ScopedAllowUnresponsiveTasksForTesting() { | |
| DCHECK(!tls_base_sync_primitives_disallowed) | |
| << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST | |
| "surprisingly already no longer allowed.\n" | |
| << "tls_base_sync_primitives_disallowed " | |
| << tls_base_sync_primitives_disallowed; | |
| DCHECK(!tls_blocking_disallowed) | |
| << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST | |
| "surprisingly already no longer allowed.\n" | |
| << "tls_blocking_disallowed " << tls_blocking_disallowed; | |
| DCHECK(!tls_cpu_intensive_work_disallowed) | |
| << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST | |
| "surprisingly already no longer allowed.\n" | |
| << "tls_cpu_intensive_work_disallowed " | |
| << tls_cpu_intensive_work_disallowed; | |
| } | |
| namespace internal { | |
| void AssertBaseSyncPrimitivesAllowed() { | |
| DUMP_OR_DCHECK(!tls_base_sync_primitives_disallowed) | |
| << "Waiting on a //base sync primitive is not allowed on this thread to " | |
| "prevent jank and deadlock. If waiting on a //base sync primitive is " | |
| "unavoidable, do it within the scope of a " | |
| "ScopedAllowBaseSyncPrimitives. If in a test, use " | |
| "ScopedAllowBaseSyncPrimitivesForTesting.\n" | |
| << "tls_base_sync_primitives_disallowed " | |
| << tls_base_sync_primitives_disallowed | |
| << "It can be useful to know that tls_blocking_disallowed is " | |
| << tls_blocking_disallowed; | |
| } | |
| void ResetThreadRestrictionsForTesting() { | |
| tls_blocking_disallowed = BooleanWithOptionalStack(false); | |
| tls_singleton_disallowed = BooleanWithOptionalStack(false); | |
| tls_base_sync_primitives_disallowed = BooleanWithOptionalStack(false); | |
| tls_cpu_intensive_work_disallowed = BooleanWithOptionalStack(false); | |
| } | |
| void AssertSingletonAllowed() { | |
| DUMP_OR_DCHECK(!tls_singleton_disallowed) | |
| << "LazyInstance/Singleton is not allowed to be used on this thread. " | |
| "Most likely it's because this thread is not joinable (or the current " | |
| "task is running with TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN " | |
| "semantics), so AtExitManager may have deleted the object on " | |
| "shutdown, leading to a potential shutdown crash. If you need to use " | |
| "the object from this context, it'll have to be updated to use Leaky " | |
| "traits.\n" | |
| << "tls_singleton_disallowed " << tls_singleton_disallowed; | |
| } | |
| } // namespace internal | |
| void DisallowSingleton() { | |
| tls_singleton_disallowed = BooleanWithOptionalStack(true); | |
| } | |
| ScopedDisallowSingleton::ScopedDisallowSingleton() | |
| : resetter_(&tls_singleton_disallowed, BooleanWithOptionalStack(true)) {} | |
| ScopedDisallowSingleton::~ScopedDisallowSingleton() { | |
| DCHECK(tls_singleton_disallowed) | |
| << "~ScopedDisallowSingleton() running while surprisingly already no " | |
| "longer disallowed.\n" | |
| << "tls_singleton_disallowed " << tls_singleton_disallowed; | |
| } | |
| void AssertLongCPUWorkAllowed() { | |
| DUMP_OR_DCHECK(!tls_cpu_intensive_work_disallowed) | |
| << "Function marked as CPU intensive was called from a scope that " | |
| "disallows this kind of work! Consider making this work " | |
| "asynchronous.\n" | |
| << "tls_cpu_intensive_work_disallowed " | |
| << tls_cpu_intensive_work_disallowed; | |
| } | |
| void DisallowUnresponsiveTasks() { | |
| DisallowBlocking(); | |
| DisallowBaseSyncPrimitives(); | |
| tls_cpu_intensive_work_disallowed = BooleanWithOptionalStack(true); | |
| } | |
| // static | |
| void PermanentThreadAllowance::AllowBlocking() { | |
| tls_blocking_disallowed = BooleanWithOptionalStack(false); | |
| } | |
| // static | |
| void PermanentThreadAllowance::AllowBaseSyncPrimitives() { | |
| tls_base_sync_primitives_disallowed = BooleanWithOptionalStack(false); | |
| } | |
| ScopedAllowBlocking::ScopedAllowBlocking(const Location& from_here) | |
| : resetter_(&tls_blocking_disallowed, BooleanWithOptionalStack(false)) { | |
| TRACE_EVENT_BEGIN( | |
| "base", "ScopedAllowBlocking", [&](perfetto::EventContext ctx) { | |
| ctx.event()->set_source_location_iid( | |
| base::trace_event::InternedSourceLocation::Get(&ctx, from_here)); | |
| }); | |
| } | |
| ScopedAllowBlocking::~ScopedAllowBlocking() { | |
| TRACE_EVENT_END("base"); | |
| DCHECK(!tls_blocking_disallowed) | |
| << "~ScopedAllowBlocking() running while surprisingly already no longer " | |
| "allowed.\n" | |
| << "tls_blocking_disallowed " << tls_blocking_disallowed; | |
| } | |
| ScopedAllowBaseSyncPrimitivesOutsideBlockingScope:: | |
| ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(const Location& from_here) | |
| : resetter_(&tls_base_sync_primitives_disallowed, | |
| BooleanWithOptionalStack(false)) { | |
| TRACE_EVENT_BEGIN( | |
| "base", "ScopedAllowBaseSyncPrimitivesOutsideBlockingScope", | |
| [&](perfetto::EventContext ctx) { | |
| ctx.event()->set_source_location_iid( | |
| base::trace_event::InternedSourceLocation::Get(&ctx, from_here)); | |
| }); | |
| // Since this object is used to indicate that sync primitives will be used to | |
| // wait for an event ignore the current operation for hang watching purposes | |
| // since the wait time duration is unknown. | |
| base::HangWatcher::InvalidateActiveExpectations(); | |
| } | |
| ScopedAllowBaseSyncPrimitivesOutsideBlockingScope:: | |
| ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() { | |
| TRACE_EVENT_END("base"); | |
| DCHECK(!tls_base_sync_primitives_disallowed) | |
| << "~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() running while " | |
| "surprisingly already no longer allowed.\n" | |
| << "tls_base_sync_primitives_disallowed " | |
| << tls_base_sync_primitives_disallowed; | |
| } | |
| } // namespace base |