// 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));
      });
#if BUILDFLAG(IS_ARKWEB) && BUILDFLAG(ARKWEB_DFX_TRACING)
    OHOS_TRACE_BEGIN("base", "ScopedAllowBlocking");
#endif
}

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