| Index: chromecast/base/device_capabilities_impl.cc
|
| diff --git a/chromecast/base/device_capabilities_impl.cc b/chromecast/base/device_capabilities_impl.cc
|
| index 7361390cfd20c2f031dbd18b60f41f8d79d6465a..5d35913cb3c76966c5f7bd398c7b7ef2d39fd929 100644
|
| --- a/chromecast/base/device_capabilities_impl.cc
|
| +++ b/chromecast/base/device_capabilities_impl.cc
|
| @@ -5,6 +5,8 @@
|
| #include "chromecast/base/device_capabilities_impl.h"
|
|
|
| #include "base/logging.h"
|
| +#include "base/single_thread_task_runner.h"
|
| +#include "base/thread_task_runner_handle.h"
|
| #include "base/values.h"
|
| #include "chromecast/base/serializers.h"
|
|
|
| @@ -59,6 +61,16 @@ scoped_ptr<DeviceCapabilities> DeviceCapabilities::CreateForTesting() {
|
| return make_scoped_ptr(capabilities);
|
| }
|
|
|
| +scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData() {
|
| + return make_scoped_refptr(new Data);
|
| +}
|
| +
|
| +scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData(
|
| + scoped_ptr<const base::DictionaryValue> dictionary) {
|
| + DCHECK(dictionary.get());
|
| + return make_scoped_refptr(new Data(dictionary.Pass()));
|
| +}
|
| +
|
| DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities)
|
| : capabilities_(capabilities) {
|
| DCHECK(capabilities);
|
| @@ -67,108 +79,168 @@ DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities)
|
| void DeviceCapabilities::Validator::SetValidatedValue(
|
| const std::string& path,
|
| scoped_ptr<base::Value> new_value) const {
|
| - capabilities_->SetValidatedValueInternal(path, new_value.Pass());
|
| + capabilities_->SetValidatedValue(path, new_value.Pass());
|
| +}
|
| +
|
| +DeviceCapabilities::Data::Data()
|
| + : dictionary_(new base::DictionaryValue),
|
| + json_string_(SerializeToJson(*dictionary_)) {
|
| + DCHECK(json_string_.get());
|
| +}
|
| +
|
| +DeviceCapabilities::Data::Data(
|
| + scoped_ptr<const base::DictionaryValue> dictionary)
|
| + : dictionary_(dictionary.Pass()),
|
| + json_string_(SerializeToJson(*dictionary_)) {
|
| + DCHECK(dictionary_.get());
|
| + DCHECK(json_string_.get());
|
| +}
|
| +
|
| +DeviceCapabilitiesImpl::Data::~Data() {}
|
| +
|
| +DeviceCapabilitiesImpl::ValidatorInfo::ValidatorInfo(Validator* validator)
|
| + : validator_(validator), task_runner_(base::ThreadTaskRunnerHandle::Get()) {
|
| + DCHECK(validator_);
|
| + DCHECK(task_runner_.get());
|
| +}
|
| +
|
| +DeviceCapabilitiesImpl::ValidatorInfo::~ValidatorInfo() {
|
| + // Check that ValidatorInfo is being destroyed on the same thread that it was
|
| + // constructed on.
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +}
|
| +
|
| +void DeviceCapabilitiesImpl::ValidatorInfo::Validate(
|
| + const std::string& path,
|
| + scoped_ptr<base::Value> proposed_value) const {
|
| + // Check that we are running Validate on the same thread that ValidatorInfo
|
| + // was constructed on.
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + validator_->Validate(path, proposed_value.Pass());
|
| }
|
|
|
| DeviceCapabilitiesImpl::DeviceCapabilitiesImpl()
|
| - : capabilities_(new base::DictionaryValue),
|
| - capabilities_str_(SerializeToJson(*capabilities_)) {
|
| - DCHECK(capabilities_str_.get());
|
| + : data_(CreateData()),
|
| + task_runner_for_writes_(base::ThreadTaskRunnerHandle::Get()),
|
| + observer_list_(new base::ObserverListThreadSafe<Observer>) {
|
| + DCHECK(task_runner_for_writes_.get());
|
| }
|
|
|
| DeviceCapabilitiesImpl::~DeviceCapabilitiesImpl() {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| // Make sure that any registered Validators have unregistered at this point
|
| DCHECK(validator_map_.empty());
|
| + // Make sure that all observers have been removed at this point
|
| + observer_list_->AssertEmpty();
|
| }
|
|
|
| void DeviceCapabilitiesImpl::Register(const std::string& key,
|
| Validator* validator) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| DCHECK(IsValidRegisterKey(key));
|
| DCHECK(validator);
|
|
|
| - bool added = validator_map_.insert(std::make_pair(key, validator)).second;
|
| + base::AutoLock auto_lock(validation_lock_);
|
| + bool added =
|
| + validator_map_.add(key, make_scoped_ptr(new ValidatorInfo(validator)))
|
| + .second;
|
| // Check that a validator has not already been registered for this key
|
| DCHECK(added);
|
| }
|
|
|
| void DeviceCapabilitiesImpl::Unregister(const std::string& key,
|
| const Validator* validator) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| + base::AutoLock auto_lock(validation_lock_);
|
| + auto validator_it = validator_map_.find(key);
|
| + DCHECK(validator_it != validator_map_.end());
|
| // Check that validator being unregistered matches the original for |key|.
|
| // This prevents managers from accidentally unregistering incorrect
|
| // validators.
|
| - DCHECK_EQ(validator, GetValidator(key));
|
| - bool erased = validator_map_.erase(key);
|
| - DCHECK(erased);
|
| + DCHECK_EQ(validator, validator_it->second->validator());
|
| + // Check that validator is unregistering on same thread that it was
|
| + // registered on
|
| + DCHECK(validator_it->second->task_runner()->BelongsToCurrentThread());
|
| + validator_map_.erase(validator_it);
|
| }
|
|
|
| DeviceCapabilities::Validator* DeviceCapabilitiesImpl::GetValidator(
|
| const std::string& key) const {
|
| + base::AutoLock auto_lock(validation_lock_);
|
| auto validator_it = validator_map_.find(key);
|
| - return validator_it == validator_map_.end() ? nullptr : validator_it->second;
|
| + return validator_it == validator_map_.end()
|
| + ? nullptr
|
| + : validator_it->second->validator();
|
| }
|
|
|
| bool DeviceCapabilitiesImpl::BluetoothSupported() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| + scoped_refptr<Data> data_ref = GetData();
|
| bool bluetooth_supported = false;
|
| - bool found_key =
|
| - capabilities_->GetBoolean(kKeyBluetoothSupported, &bluetooth_supported);
|
| + bool found_key = data_ref->dictionary().GetBoolean(kKeyBluetoothSupported,
|
| + &bluetooth_supported);
|
| DCHECK(found_key);
|
| return bluetooth_supported;
|
| }
|
|
|
| bool DeviceCapabilitiesImpl::DisplaySupported() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| + scoped_refptr<Data> data_ref = GetData();
|
| bool display_supported = false;
|
| - bool found_key =
|
| - capabilities_->GetBoolean(kKeyDisplaySupported, &display_supported);
|
| + bool found_key = data_ref->dictionary().GetBoolean(kKeyDisplaySupported,
|
| + &display_supported);
|
| DCHECK(found_key);
|
| return display_supported;
|
| }
|
|
|
| -bool DeviceCapabilitiesImpl::GetCapability(
|
| - const std::string& path,
|
| - const base::Value** out_value) const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return capabilities_ && capabilities_->Get(path, out_value);
|
| +scoped_ptr<base::Value> DeviceCapabilitiesImpl::GetCapability(
|
| + const std::string& path) const {
|
| + scoped_refptr<Data> data_ref = GetData();
|
| + const base::Value* value = nullptr;
|
| + bool found_path = data_ref->dictionary().Get(path, &value);
|
| + return found_path ? value->CreateDeepCopy() : scoped_ptr<base::Value>();
|
| }
|
|
|
| -const std::string& DeviceCapabilitiesImpl::GetCapabilitiesString() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return *capabilities_str_;
|
| -}
|
| -
|
| -const base::DictionaryValue* DeviceCapabilitiesImpl::GetCapabilities() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return capabilities_.get();
|
| +scoped_refptr<DeviceCapabilities::Data>
|
| +DeviceCapabilitiesImpl::GetData() const {
|
| + // Need to acquire lock here when copy constructing data_ otherwise we could
|
| + // be concurrently be writing to scoped_refptr in SetValidatedValue(), which
|
| + // could cause a bad scoped_refptr read.
|
| + base::AutoLock auto_lock(data_lock_);
|
| + return data_;
|
| }
|
|
|
| void DeviceCapabilitiesImpl::SetCapability(
|
| const std::string& path,
|
| scoped_ptr<base::Value> proposed_value) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| DCHECK(proposed_value.get());
|
| if (!IsValidPath(path)) {
|
| LOG(DFATAL) << "Invalid capability path encountered for SetCapability()";
|
| return;
|
| }
|
|
|
| - // Check for Validator registered under first key per the Register()
|
| - // interface.
|
| - auto validator_it = validator_map_.find(GetFirstKey(path));
|
| - if (validator_it == validator_map_.end()) {
|
| - SetValidatedValueInternal(path, proposed_value.Pass());
|
| - return;
|
| + {
|
| + base::AutoLock auto_lock(validation_lock_);
|
| + // Check for Validator registered under first key per the Register()
|
| + // interface.
|
| + auto validator_it = validator_map_.find(GetFirstKey(path));
|
| + if (validator_it != validator_map_.end()) {
|
| + // We do not want to post a task directly for the Validator's Validate()
|
| + // method here because if another thread is in the middle of unregistering
|
| + // that Validator, there will be an outstanding call to Validate() that
|
| + // occurs after it has unregistered. Since ValidatorInfo gets destroyed
|
| + // in Unregister() on same thread that validation should run on, we can
|
| + // post a task to the Validator's thread with weak_ptr. This way, if the
|
| + // Validator gets unregistered, the call to Validate will get skipped.
|
| + validator_it->second->task_runner()->PostTask(
|
| + FROM_HERE, base::Bind(&ValidatorInfo::Validate,
|
| + validator_it->second->AsWeakPtr(), path,
|
| + base::Passed(&proposed_value)));
|
| + return;
|
| + }
|
| }
|
| -
|
| - validator_it->second->Validate(path, proposed_value.Pass());
|
| + // Since we are done checking for a registered Validator at this point, we
|
| + // can release the lock. All further member access will be for capabilities.
|
| + SetValidatedValue(path, proposed_value.Pass());
|
| }
|
|
|
| void DeviceCapabilitiesImpl::MergeDictionary(
|
| const base::DictionaryValue& dict_value) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| for (base::DictionaryValue::Iterator it(dict_value); !it.IsAtEnd();
|
| it.Advance()) {
|
| SetCapability(it.key(), it.value().CreateDeepCopy());
|
| @@ -177,42 +249,62 @@ void DeviceCapabilitiesImpl::MergeDictionary(
|
|
|
| void DeviceCapabilitiesImpl::AddCapabilitiesObserver(Observer* observer) {
|
| DCHECK(observer);
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - observer_list_.AddObserver(observer);
|
| + observer_list_->AddObserver(observer);
|
| }
|
|
|
| void DeviceCapabilitiesImpl::RemoveCapabilitiesObserver(Observer* observer) {
|
| DCHECK(observer);
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - observer_list_.RemoveObserver(observer);
|
| + observer_list_->RemoveObserver(observer);
|
| }
|
|
|
| -void DeviceCapabilitiesImpl::SetValidatedValueInternal(
|
| +void DeviceCapabilitiesImpl::SetValidatedValue(
|
| const std::string& path,
|
| scoped_ptr<base::Value> new_value) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| + // All internal writes/modifications of capabilities must occur on same
|
| + // thread to avoid race conditions.
|
| + if (!task_runner_for_writes_->BelongsToCurrentThread()) {
|
| + task_runner_for_writes_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&DeviceCapabilitiesImpl::SetValidatedValue,
|
| + base::Unretained(this), path, base::Passed(&new_value)));
|
| + return;
|
| + }
|
| +
|
| DCHECK(IsValidPath(path));
|
| DCHECK(new_value.get());
|
|
|
| + // We don't need to acquire lock here when reading data_ because we know that
|
| + // all writes to data_ must occur serially on thread that we're on.
|
| const base::Value* cur_value = nullptr;
|
| - bool capability_unchaged =
|
| - GetCapability(path, &cur_value) && cur_value->Equals(new_value.get());
|
| - if (capability_unchaged) {
|
| + bool capability_unchanged = data_->dictionary().Get(path, &cur_value) &&
|
| + cur_value->Equals(new_value.get());
|
| + if (capability_unchanged) {
|
| VLOG(1) << "Ignoring unchanged capability: " << path;
|
| return;
|
| }
|
|
|
| - capabilities_->Set(path, new_value.Pass());
|
| - UpdateStrAndNotifyChanged(path);
|
| -}
|
| + // In this sequence, we create a deep copy, modify the deep copy, and then
|
| + // do a pointer swap. We do this to have minimal time spent in the
|
| + // data_lock_. If we were to lock and modify the capabilities
|
| + // dictionary directly, there may be expensive writes that block other
|
| + // threads.
|
| + scoped_ptr<base::DictionaryValue> dictionary_deep_copy(
|
| + data_->dictionary().CreateDeepCopy());
|
| + dictionary_deep_copy->Set(path, new_value.Pass());
|
| + scoped_refptr<Data> new_data(CreateData(dictionary_deep_copy.Pass()));
|
| +
|
| + {
|
| + base::AutoLock auto_lock(data_lock_);
|
| + // Using swap instead of assignment operator here because it's a little
|
| + // faster. Avoids an extra call to AddRef()/Release().
|
| + data_.swap(new_data);
|
| + }
|
|
|
| -void DeviceCapabilitiesImpl::UpdateStrAndNotifyChanged(
|
| - const std::string& path) {
|
| - // Update capabilities string here since all updates to capabilities must
|
| - // ultimately call this method no matter where the update originated from.
|
| - capabilities_str_ = SerializeToJson(*capabilities_);
|
| - DCHECK(capabilities_str_.get());
|
| - FOR_EACH_OBSERVER(Observer, observer_list_, OnCapabilitiesChanged(path));
|
| + // Even though ObseverListThreadSafe notifications are always asynchronous
|
| + // (posts task even if to same thread), no locks should be held at this point
|
| + // in the code. This is just to be safe that no deadlocks occur if Observers
|
| + // call DeviceCapabilities methods in OnCapabilitiesChanged().
|
| + observer_list_->Notify(FROM_HERE, &Observer::OnCapabilitiesChanged, path);
|
| }
|
|
|
| } // namespace chromecast
|
|
|