// 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/base_tracing.h" #include "build/build_config.h" namespace base { BooleanWithOptionalStack::BooleanWithOptionalStack(bool value) : value_(value) { #if CAPTURE_THREAD_RESTRICTIONS_STACK_TRACES() 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_END0("base", "ScopedAllowBlocking"); 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_END0("base", "ScopedAllowBaseSyncPrimitivesOutsideBlockingScope"); 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