| // 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 |