blob: f82b205082e724e5c2aa06aa83c70b27b748c6b8 [file] [log] [blame] [edit]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/base/component/component.h"
#include <set>
#include <utility>
#include "base/atomicops.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"
namespace chromecast {
namespace {
const base::subtle::AtomicWord kEnabledBit = 0x40000000;
} // namespace
namespace subtle {
class DependencyCount : public base::RefCountedThreadSafe<DependencyCount> {
public:
explicit DependencyCount(ComponentBase* component)
: component_(component),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
dep_count_(0),
disabling_(false) {
DCHECK(component_);
}
DependencyCount(const DependencyCount&) = delete;
DependencyCount& operator=(const DependencyCount&) = delete;
void Detach() {
DCHECK(task_runner_->BelongsToCurrentThread());
component_ = nullptr;
}
void Disable() {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!disabling_);
disabling_ = true;
std::set<DependencyBase*> dependents(strong_dependents_);
for (DependencyBase* dependent : dependents)
dependent->Disable();
while (true) {
AtomicWord deps = base::subtle::NoBarrier_Load(&dep_count_);
AtomicWord old_deps = base::subtle::Acquire_CompareAndSwap(
&dep_count_, deps, deps & ~kEnabledBit);
if (old_deps == deps) {
if ((deps & ~kEnabledBit) == 0)
DisableComplete();
return;
}
}
}
void Enable() {
DCHECK(task_runner_->BelongsToCurrentThread());
disabling_ = false;
while (true) {
AtomicWord deps = base::subtle::NoBarrier_Load(&dep_count_);
DCHECK(!(deps & kEnabledBit));
AtomicWord old_deps = base::subtle::Release_CompareAndSwap(
&dep_count_, deps, deps | kEnabledBit);
if (old_deps == deps)
break;
}
for (DependencyBase* dependent : strong_dependents_)
dependent->Ready(component_);
}
ComponentBase* WeakAcquireDep() {
while (true) {
AtomicWord deps = base::subtle::NoBarrier_Load(&dep_count_);
if (!(deps & kEnabledBit))
return nullptr;
AtomicWord old_deps =
base::subtle::Acquire_CompareAndSwap(&dep_count_, deps, deps + 1);
// We depend on the fact that a component must be disabled (meaning that
// we will never reach this point) before it is destroyed. Therefore if
// we do reach this point, it is safe to return the raw pointer.
if (old_deps == deps)
return component_;
}
}
void StrongAcquireDep(DependencyBase* dependent) {
DCHECK(dependent);
DCHECK(task_runner_->BelongsToCurrentThread());
if (!component_) {
dependent->Disable();
return;
}
strong_dependents_.insert(dependent);
AtomicWord count = base::subtle::NoBarrier_AtomicIncrement(&dep_count_, 1);
DCHECK_GT(count, 0);
if (count & kEnabledBit) {
dependent->Ready(component_);
} else {
component_->Enable();
}
}
void StrongReleaseDep(DependencyBase* dependent) {
DCHECK(dependent);
DCHECK(task_runner_->BelongsToCurrentThread());
strong_dependents_.erase(dependent);
ReleaseDep();
}
void ReleaseDep() {
AtomicWord after = base::subtle::Barrier_AtomicIncrement(&dep_count_, -1);
DCHECK_GE(after, 0);
if (after == 0)
DisableComplete();
}
bool DependsOn(ComponentBase* component) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!component_)
return false;
if (component_ == component)
return true;
return component_->DependsOn(component);
}
private:
friend class base::RefCountedThreadSafe<DependencyCount>;
using AtomicWord = base::subtle::AtomicWord;
~DependencyCount() {}
void DisableComplete() {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DependencyCount::DisableComplete, this));
return;
}
// Need to make sure that Enable() was not called in the meantime.
if (base::subtle::NoBarrier_Load(&dep_count_) != 0 || !disabling_)
return;
// Ensure that we don't call DisableComplete() more than once per Disable().
disabling_ = false;
DCHECK(component_);
DCHECK(strong_dependents_.empty());
component_->DependencyCountDisableComplete();
}
ComponentBase* component_;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
AtomicWord dep_count_;
bool disabling_;
std::set<DependencyBase*> strong_dependents_;
};
DependencyBase::DependencyBase(const WeakReferenceBase& dependency,
ComponentBase* dependent)
: dependent_(dependent),
dependency_(nullptr),
counter_(dependency.counter_) {
DCHECK(dependent_);
dependent_->AddDependency(this);
}
DependencyBase::~DependencyBase() {}
void DependencyBase::StartUsing() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!dependency_);
counter_->StrongAcquireDep(this);
}
void DependencyBase::StopUsing() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!dependency_)
return;
dependency_ = nullptr;
counter_->StrongReleaseDep(this);
}
void DependencyBase::Ready(ComponentBase* dependency) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!dependency_);
DCHECK(dependency);
dependency_ = dependency;
dependent_->DependencyReady();
}
void DependencyBase::Disable() {
DCHECK(thread_checker_.CalledOnValidThread());
dependent_->Disable();
}
bool DependencyBase::DependsOn(ComponentBase* component) {
return counter_->DependsOn(component);
}
WeakReferenceBase::WeakReferenceBase(const ComponentBase& dependency)
: counter_(dependency.counter_) {
DCHECK(counter_);
}
WeakReferenceBase::WeakReferenceBase(const DependencyBase& dependency)
: counter_(dependency.counter_) {
DCHECK(counter_);
}
WeakReferenceBase::WeakReferenceBase(const WeakReferenceBase& other)
: counter_(other.counter_) {
DCHECK(counter_);
}
WeakReferenceBase::WeakReferenceBase(WeakReferenceBase&& other)
: counter_(std::move(other.counter_)) {
DCHECK(counter_);
}
WeakReferenceBase::~WeakReferenceBase() {}
ScopedReferenceBase::ScopedReferenceBase(
const scoped_refptr<DependencyCount>& counter)
: counter_(counter) {
DCHECK(counter_);
dependency_ = counter_->WeakAcquireDep();
}
ScopedReferenceBase::ScopedReferenceBase(ScopedReferenceBase&& other)
: counter_(std::move(other.counter_)), dependency_(other.dependency_) {
DCHECK(counter_);
other.dependency_ = nullptr;
}
ScopedReferenceBase::~ScopedReferenceBase() {
if (dependency_)
counter_->ReleaseDep();
}
} // namespace subtle
ComponentBase::ComponentBase()
: task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
state_(kStateDisabled),
async_call_in_progress_(false),
pending_dependency_count_(0),
observers_(new base::ObserverListThreadSafe<Observer>()) {
counter_ = new subtle::DependencyCount(this);
}
ComponentBase::~ComponentBase() {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(kStateDisabled, state_) << "Components must be disabled "
<< "before being destroyed";
counter_->Detach();
}
void ComponentBase::Enable() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ == kStateEnabling || state_ == kStateEnabled ||
state_ == kStateDestroying) {
return;
}
state_ = kStateEnabling;
if (strong_dependencies_.empty()) {
TryOnEnable();
} else {
// Enable all strong dependencies first.
pending_dependency_count_ = strong_dependencies_.size();
for (subtle::DependencyBase* dependency : strong_dependencies_)
dependency->StartUsing();
}
}
void ComponentBase::DependencyReady() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ != kStateEnabling)
return;
DCHECK_GT(pending_dependency_count_, 0);
--pending_dependency_count_;
if (pending_dependency_count_ == 0)
TryOnEnable();
}
void ComponentBase::TryOnEnable() {
DCHECK_EQ(kStateEnabling, state_);
if (async_call_in_progress_)
return;
async_call_in_progress_ = true;
OnEnable();
}
void ComponentBase::OnEnableComplete(bool success) {
// Always post a task, to prevent the stack from getting too deep.
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ComponentBase::OnEnableCompleteInternal,
base::Unretained(this), success));
}
void ComponentBase::OnEnableCompleteInternal(bool success) {
async_call_in_progress_ = false;
DCHECK(state_ == kStateEnabling || state_ == kStateDisabling ||
state_ == kStateDestroying);
if (state_ != kStateEnabling) {
if (success) {
TryOnDisable();
} else {
OnDisableCompleteInternal();
}
return;
}
if (success) {
state_ = kStateEnabled;
counter_->Enable();
} else {
Disable();
}
observers_->Notify(FROM_HERE, &Observer::OnComponentEnabled, this, success);
}
void ComponentBase::Destroy() {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_NE(kStateDestroying, state_);
if (state_ == kStateDisabled) {
delete this;
} else {
bool should_disable = (state_ != kStateDisabling);
state_ = kStateDestroying;
if (should_disable)
counter_->Disable();
}
}
void ComponentBase::AddObserver(Observer* observer) {
DCHECK(observer);
observers_->AddObserver(observer);
}
void ComponentBase::RemoveObserver(Observer* observer) {
observers_->RemoveObserver(observer);
}
void ComponentBase::Disable() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ == kStateDisabling || state_ == kStateDisabled ||
state_ == kStateDestroying) {
return;
}
state_ = kStateDisabling;
counter_->Disable();
}
void ComponentBase::DependencyCountDisableComplete() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ == kStateDisabling || state_ == kStateDestroying)
TryOnDisable();
}
void ComponentBase::TryOnDisable() {
DCHECK(state_ == kStateDisabling || state_ == kStateDestroying);
if (async_call_in_progress_)
return;
async_call_in_progress_ = true;
OnDisable();
}
void ComponentBase::OnDisableComplete() {
// Always post a task, to prevent calls to Disable() from within Enable().
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ComponentBase::OnDisableCompleteInternal,
base::Unretained(this)));
}
void ComponentBase::OnDisableCompleteInternal() {
async_call_in_progress_ = false;
DCHECK(state_ == kStateEnabling || state_ == kStateDisabling ||
state_ == kStateDestroying);
if (state_ == kStateEnabling) {
TryOnEnable();
return;
}
if (state_ == kStateDestroying) {
StopUsingDependencies();
observers_->Notify(FROM_HERE, &Observer::OnComponentDisabled, this);
state_ = kStateDisabled;
delete this;
} else {
state_ = kStateDisabled;
StopUsingDependencies();
observers_->Notify(FROM_HERE, &Observer::OnComponentDisabled, this);
}
}
void ComponentBase::AddDependency(subtle::DependencyBase* dependency) {
DCHECK_EQ(kStateDisabled, state_);
DCHECK(!dependency->DependsOn(this)) << "Circular dependency detected";
strong_dependencies_.push_back(dependency);
}
void ComponentBase::StopUsingDependencies() {
for (subtle::DependencyBase* dependency : strong_dependencies_)
dependency->StopUsing();
}
bool ComponentBase::DependsOn(ComponentBase* component) {
for (subtle::DependencyBase* dependency : strong_dependencies_) {
if (dependency->DependsOn(component))
return true;
}
return false;
}
} // namespace chromecast