// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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"

#if DCHECK_IS_ON()

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/threading/thread_local.h"

namespace base {

namespace {

LazyInstance<ThreadLocalBoolean>::Leaky g_blocking_disallowed =
    LAZY_INSTANCE_INITIALIZER;

LazyInstance<ThreadLocalBoolean>::Leaky
    g_singleton_disallowed = LAZY_INSTANCE_INITIALIZER;

LazyInstance<ThreadLocalBoolean>::Leaky g_base_sync_primitives_disallowed =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

void AssertBlockingAllowed() {
  DCHECK(!g_blocking_disallowed.Get().Get())
      << "Function marked as blocking was called from a scope that disallows "
         "blocking! If this task is running inside the TaskScheduler, it needs "
         "to have MayBlock() in its TaskTraits. Otherwise, consider making "
         "this blocking work asynchronous or, as a last resort, you may use "
         "ScopedAllowBlocking in a narrow scope.";
}

void DisallowBlocking() {
  g_blocking_disallowed.Get().Set(true);
}

ScopedDisallowBlocking::ScopedDisallowBlocking()
    : was_disallowed_(g_blocking_disallowed.Get().Get()) {
  g_blocking_disallowed.Get().Set(true);
}

ScopedDisallowBlocking::~ScopedDisallowBlocking() {
  DCHECK(g_blocking_disallowed.Get().Get());
  g_blocking_disallowed.Get().Set(was_disallowed_);
}

ScopedAllowBlocking::ScopedAllowBlocking()
    : was_disallowed_(g_blocking_disallowed.Get().Get()) {
  g_blocking_disallowed.Get().Set(false);
}

ScopedAllowBlocking::~ScopedAllowBlocking() {
  DCHECK(!g_blocking_disallowed.Get().Get());
  g_blocking_disallowed.Get().Set(was_disallowed_);
}

void DisallowBaseSyncPrimitives() {
  g_base_sync_primitives_disallowed.Get().Set(true);
}

ScopedAllowBaseSyncPrimitives::ScopedAllowBaseSyncPrimitives()
    : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
  DCHECK(!g_blocking_disallowed.Get().Get())
      << "To allow //base sync primitives in a scope where blocking is "
         "disallowed use ScopedAllowBaseSyncPrimitivesOutsideBlockingScope.";
  g_base_sync_primitives_disallowed.Get().Set(false);
}

ScopedAllowBaseSyncPrimitives::~ScopedAllowBaseSyncPrimitives() {
  DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
  g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
}

ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
    ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()
    : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
  g_base_sync_primitives_disallowed.Get().Set(false);
}

ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
    ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() {
  DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
  g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
}

ScopedAllowBaseSyncPrimitivesForTesting::
    ScopedAllowBaseSyncPrimitivesForTesting()
    : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
  g_base_sync_primitives_disallowed.Get().Set(false);
}

ScopedAllowBaseSyncPrimitivesForTesting::
    ~ScopedAllowBaseSyncPrimitivesForTesting() {
  DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
  g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
}

namespace internal {

void AssertBaseSyncPrimitivesAllowed() {
  DCHECK(!g_base_sync_primitives_disallowed.Get().Get())
      << "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.";
}

void ResetThreadRestrictionsForTesting() {
  g_blocking_disallowed.Get().Set(false);
  g_singleton_disallowed.Get().Set(false);
  g_base_sync_primitives_disallowed.Get().Set(false);
}

}  // namespace internal

ThreadRestrictions::ScopedAllowIO::ScopedAllowIO()
    : was_allowed_(SetIOAllowed(true)) {}

ThreadRestrictions::ScopedAllowIO::~ScopedAllowIO() {
  SetIOAllowed(was_allowed_);
}

// static
bool ThreadRestrictions::SetIOAllowed(bool allowed) {
  bool previous_disallowed = g_blocking_disallowed.Get().Get();
  g_blocking_disallowed.Get().Set(!allowed);
  return !previous_disallowed;
}

// static
bool ThreadRestrictions::SetSingletonAllowed(bool allowed) {
  bool previous_disallowed = g_singleton_disallowed.Get().Get();
  g_singleton_disallowed.Get().Set(!allowed);
  return !previous_disallowed;
}

// static
void ThreadRestrictions::AssertSingletonAllowed() {
  if (g_singleton_disallowed.Get().Get()) {
    NOTREACHED() << "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.";
  }
}

// static
void ThreadRestrictions::DisallowWaiting() {
  DisallowBaseSyncPrimitives();
}

bool ThreadRestrictions::SetWaitAllowed(bool allowed) {
  bool previous_disallowed = g_base_sync_primitives_disallowed.Get().Get();
  g_base_sync_primitives_disallowed.Get().Set(!allowed);
  return !previous_disallowed;
}

ThreadRestrictions::ScopedAllowWait::ScopedAllowWait()
    : was_allowed_(SetWaitAllowed(true)) {}

ThreadRestrictions::ScopedAllowWait::~ScopedAllowWait() {
  SetWaitAllowed(was_allowed_);
}

}  // namespace base

#endif  // DCHECK_IS_ON()
