// 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/threading/hang_watcher.h" #include "base/trace_event/base_tracing.h" #include "build/build_config.h" #if DCHECK_IS_ON() #include "base/check_op.h" #include "base/no_destructor.h" #include "base/threading/thread_local.h" // NaCL doesn't support stack sampling and Android is slow at stack sampling and // this causes timeouts (crbug.com/959139). #if BUILDFLAG(IS_NACL) || BUILDFLAG(IS_ANDROID) constexpr bool kCaptureStackTraces = false; #else // Always disabled when !EXPENSIVE_DCHECKS_ARE_ON() because user-facing builds // typically drop log strings anyways. constexpr bool kCaptureStackTraces = EXPENSIVE_DCHECKS_ARE_ON(); #endif namespace base { BooleanWithStack::BooleanWithStack(bool value) : value_(value) { if (kCaptureStackTraces) { stack_.emplace(); } } std::ostream& operator<<(std::ostream& out, const BooleanWithStack& bws) { out << bws.value_; if (kCaptureStackTraces) { if (bws.stack_.has_value()) { out << " set by\n" << bws.stack_.value(); } else { out << " (value by default)"; } } return out; } namespace { // TODO(crbug.com/1423437): Change these to directly-accessed, namespace-scope // `thread_local BooleanWithStack`s when doing so doesn't cause crashes. BooleanWithStack& GetBlockingDisallowedTls() { static NoDestructor> instance; auto& tls = *instance; if (!tls.Get()) { tls.Set(std::make_unique()); } return *tls; } BooleanWithStack& GetSingletonDisallowedTls() { static NoDestructor> instance; auto& tls = *instance; if (!tls.Get()) { tls.Set(std::make_unique()); } return *tls; } BooleanWithStack& GetBaseSyncPrimitivesDisallowedTls() { static NoDestructor> instance; auto& tls = *instance; if (!tls.Get()) { tls.Set(std::make_unique()); } return *tls; } BooleanWithStack& GetCPUIntensiveWorkDisallowedTls() { static NoDestructor> instance; auto& tls = *instance; if (!tls.Get()) { tls.Set(std::make_unique()); } return *tls; } } // namespace namespace internal { void AssertBlockingAllowed() { DCHECK(!GetBlockingDisallowedTls()) << "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" << "blocking_disallowed " << GetBlockingDisallowedTls(); } void AssertBlockingDisallowedForTesting() { DCHECK(GetBlockingDisallowedTls()) << "blocking_disallowed " << GetBlockingDisallowedTls(); } } // namespace internal void DisallowBlocking() { GetBlockingDisallowedTls() = BooleanWithStack(true); } ScopedDisallowBlocking::ScopedDisallowBlocking() : resetter_(&GetBlockingDisallowedTls(), BooleanWithStack(true)) {} ScopedDisallowBlocking::~ScopedDisallowBlocking() { DCHECK(GetBlockingDisallowedTls()) << "~ScopedDisallowBlocking() running while surprisingly already no " "longer disallowed.\n" << "blocking_disallowed " << GetBlockingDisallowedTls(); } void DisallowBaseSyncPrimitives() { GetBaseSyncPrimitivesDisallowedTls() = BooleanWithStack(true); } ScopedDisallowBaseSyncPrimitives::ScopedDisallowBaseSyncPrimitives() : resetter_(&GetBaseSyncPrimitivesDisallowedTls(), BooleanWithStack(true)) { } ScopedDisallowBaseSyncPrimitives::~ScopedDisallowBaseSyncPrimitives() { DCHECK(GetBaseSyncPrimitivesDisallowedTls()) << "~ScopedDisallowBaseSyncPrimitives() running while surprisingly " "already no longer disallowed.\n" << "base_sync_primitives_disallowed " << GetBaseSyncPrimitivesDisallowedTls(); } ScopedAllowBaseSyncPrimitives::ScopedAllowBaseSyncPrimitives() : resetter_(&GetBaseSyncPrimitivesDisallowedTls(), BooleanWithStack(false)) { DCHECK(!GetBlockingDisallowedTls()) << "To allow //base sync primitives in a scope where blocking is " "disallowed use ScopedAllowBaseSyncPrimitivesOutsideBlockingScope.\n" << "blocking_disallowed " << GetBlockingDisallowedTls(); } ScopedAllowBaseSyncPrimitives::~ScopedAllowBaseSyncPrimitives() { DCHECK(!GetBaseSyncPrimitivesDisallowedTls()) << "~ScopedAllowBaseSyncPrimitives() running while surprisingly already " "no longer allowed.\n" << "base_sync_primitives_disallowed " << GetBaseSyncPrimitivesDisallowedTls(); } ScopedAllowBaseSyncPrimitivesForTesting:: ScopedAllowBaseSyncPrimitivesForTesting() : resetter_(&GetBaseSyncPrimitivesDisallowedTls(), BooleanWithStack(false)) {} ScopedAllowBaseSyncPrimitivesForTesting:: ~ScopedAllowBaseSyncPrimitivesForTesting() { DCHECK(!GetBaseSyncPrimitivesDisallowedTls()) << "~ScopedAllowBaseSyncPrimitivesForTesting() running while " // IN-TEST "surprisingly already no longer allowed.\n" << "base_sync_primitives_disallowed " << GetBaseSyncPrimitivesDisallowedTls(); } ScopedAllowUnresponsiveTasksForTesting::ScopedAllowUnresponsiveTasksForTesting() : base_sync_resetter_(&GetBaseSyncPrimitivesDisallowedTls(), BooleanWithStack(false)), blocking_resetter_(&GetBlockingDisallowedTls(), BooleanWithStack(false)), cpu_resetter_(&GetCPUIntensiveWorkDisallowedTls(), BooleanWithStack(false)) {} ScopedAllowUnresponsiveTasksForTesting:: ~ScopedAllowUnresponsiveTasksForTesting() { DCHECK(!GetBaseSyncPrimitivesDisallowedTls()) << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST "surprisingly already no longer allowed.\n" << "base_sync_primitives_disallowed " << GetBaseSyncPrimitivesDisallowedTls(); DCHECK(!GetBlockingDisallowedTls()) << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST "surprisingly already no longer allowed.\n" << "blocking_disallowed " << GetBlockingDisallowedTls(); DCHECK(!GetCPUIntensiveWorkDisallowedTls()) << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST "surprisingly already no longer allowed.\n" << "cpu_intensive_work_disallowed " << GetCPUIntensiveWorkDisallowedTls(); } namespace internal { void AssertBaseSyncPrimitivesAllowed() { DCHECK(!GetBaseSyncPrimitivesDisallowedTls()) << "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" << "base_sync_primitives_disallowed " << GetBaseSyncPrimitivesDisallowedTls() << "It can be useful to know that blocking_disallowed is " << GetBlockingDisallowedTls(); } void ResetThreadRestrictionsForTesting() { GetBlockingDisallowedTls() = BooleanWithStack(false); GetSingletonDisallowedTls() = BooleanWithStack(false); GetBaseSyncPrimitivesDisallowedTls() = BooleanWithStack(false); GetCPUIntensiveWorkDisallowedTls() = BooleanWithStack(false); } void AssertSingletonAllowed() { DCHECK(!GetSingletonDisallowedTls()) << "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" << "singleton_disallowed " << GetSingletonDisallowedTls(); } } // namespace internal void DisallowSingleton() { GetSingletonDisallowedTls() = BooleanWithStack(true); } ScopedDisallowSingleton::ScopedDisallowSingleton() : resetter_(&GetSingletonDisallowedTls(), BooleanWithStack(true)) {} ScopedDisallowSingleton::~ScopedDisallowSingleton() { DCHECK(GetSingletonDisallowedTls()) << "~ScopedDisallowSingleton() running while surprisingly already no " "longer disallowed.\n" << "singleton_disallowed " << GetSingletonDisallowedTls(); } void AssertLongCPUWorkAllowed() { DCHECK(!GetCPUIntensiveWorkDisallowedTls()) << "Function marked as CPU intensive was called from a scope that " "disallows this kind of work! Consider making this work " "asynchronous.\n" << "cpu_intensive_work_disallowed " << GetCPUIntensiveWorkDisallowedTls(); } void DisallowUnresponsiveTasks() { DisallowBlocking(); DisallowBaseSyncPrimitives(); GetCPUIntensiveWorkDisallowedTls() = BooleanWithStack(true); } // static void PermanentThreadAllowance::AllowBlocking() { GetBlockingDisallowedTls() = BooleanWithStack(false); } // static void PermanentThreadAllowance::AllowBaseSyncPrimitives() { GetBaseSyncPrimitivesDisallowedTls() = BooleanWithStack(false); } } // namespace base #endif // DCHECK_IS_ON() namespace base { ScopedAllowBlocking::ScopedAllowBlocking(const Location& from_here) #if DCHECK_IS_ON() : resetter_(&GetBlockingDisallowedTls(), BooleanWithStack(false)) #endif { 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"); #if DCHECK_IS_ON() DCHECK(!GetBlockingDisallowedTls()) << "~ScopedAllowBlocking() running while surprisingly already no longer " "allowed.\n" << "blocking_disallowed " << GetBlockingDisallowedTls(); #endif } ScopedAllowBaseSyncPrimitivesOutsideBlockingScope:: ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(const Location& from_here) #if DCHECK_IS_ON() : resetter_(&GetBaseSyncPrimitivesDisallowedTls(), BooleanWithStack(false)) #endif { 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"); #if DCHECK_IS_ON() DCHECK(!GetBaseSyncPrimitivesDisallowedTls()) << "~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() running while " "surprisingly already no longer allowed.\n" << "base_sync_primitives_disallowed " << GetBaseSyncPrimitivesDisallowedTls(); #endif } } // namespace base