Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chromecast/base/device_capabilities_impl.h" | 5 #include "chromecast/base/device_capabilities_impl.h" |
| 6 | 6 |
| 7 #include "base/logging.h" | 7 #include "base/logging.h" |
| 8 #include "base/single_thread_task_runner.h" | |
| 9 #include "base/thread_task_runner_handle.h" | |
| 8 #include "base/values.h" | 10 #include "base/values.h" |
| 9 #include "chromecast/base/serializers.h" | 11 #include "chromecast/base/serializers.h" |
| 10 | 12 |
| 11 namespace chromecast { | 13 namespace chromecast { |
| 12 | 14 |
| 13 namespace { | 15 namespace { |
| 14 | 16 |
| 15 const char kPathSeparator = '.'; | 17 const char kPathSeparator = '.'; |
| 16 | 18 |
| 17 // Determines if a key passed to Register() is valid. No path separators can | 19 // Determines if a key passed to Register() is valid. No path separators can |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 60 } | 62 } |
| 61 | 63 |
| 62 DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities) | 64 DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities) |
| 63 : capabilities_(capabilities) { | 65 : capabilities_(capabilities) { |
| 64 DCHECK(capabilities); | 66 DCHECK(capabilities); |
| 65 } | 67 } |
| 66 | 68 |
| 67 void DeviceCapabilities::Validator::SetValidatedValue( | 69 void DeviceCapabilities::Validator::SetValidatedValue( |
| 68 const std::string& path, | 70 const std::string& path, |
| 69 scoped_ptr<base::Value> new_value) const { | 71 scoped_ptr<base::Value> new_value) const { |
| 70 capabilities_->SetValidatedValueInternal(path, new_value.Pass()); | 72 capabilities_->SetValidatedValue(path, new_value.Pass()); |
| 73 } | |
| 74 | |
| 75 template <class T> | |
| 76 DeviceCapabilitiesImpl::ImmutableRefCounted<T>::ImmutableRefCounted() | |
| 77 : data(new T) {} | |
| 78 | |
| 79 template <class T> | |
| 80 DeviceCapabilitiesImpl::ImmutableRefCounted<T>::ImmutableRefCounted( | |
| 81 scoped_ptr<const T> data_arg) | |
| 82 : data(data_arg.Pass()) { | |
| 83 DCHECK(data.get()); | |
| 84 } | |
| 85 | |
| 86 DeviceCapabilitiesImpl::ValidatorInfo::ValidatorInfo(Validator* validator_arg) | |
| 87 : validator(validator_arg), | |
| 88 task_runner(base::ThreadTaskRunnerHandle::Get()) { | |
| 89 DCHECK(validator); | |
| 90 DCHECK(task_runner.get()); | |
| 91 } | |
| 92 | |
| 93 DeviceCapabilitiesImpl::ValidatorInfo::~ValidatorInfo() { | |
| 94 // Check that ValidatorInfo is being destroyed on the same thread that it was | |
| 95 // constructed on. | |
| 96 DCHECK(task_runner->BelongsToCurrentThread()); | |
| 71 } | 97 } |
| 72 | 98 |
| 73 DeviceCapabilitiesImpl::DeviceCapabilitiesImpl() | 99 DeviceCapabilitiesImpl::DeviceCapabilitiesImpl() |
| 74 : capabilities_(new base::DictionaryValue), | 100 : capabilities_(new ImmutableRefCountedDictionary), |
| 75 capabilities_str_(SerializeToJson(*capabilities_)) { | 101 capabilities_str_( |
| 102 new ImmutableRefCountedString(SerializeToJson(*capabilities_->data))), | |
| 103 task_runner_for_writes_(base::ThreadTaskRunnerHandle::Get()), | |
| 104 observer_list_(new base::ObserverListThreadSafe<Observer>) { | |
| 76 DCHECK(capabilities_str_.get()); | 105 DCHECK(capabilities_str_.get()); |
| 106 DCHECK(task_runner_for_writes_.get()); | |
| 77 } | 107 } |
| 78 | 108 |
| 79 DeviceCapabilitiesImpl::~DeviceCapabilitiesImpl() { | 109 DeviceCapabilitiesImpl::~DeviceCapabilitiesImpl() { |
| 80 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 81 // Make sure that any registered Validators have unregistered at this point | 110 // Make sure that any registered Validators have unregistered at this point |
| 82 DCHECK(validator_map_.empty()); | 111 DCHECK(validator_map_.empty()); |
| 112 // Make sure that all observers have been removed at this point | |
| 113 observer_list_->AssertEmpty(); | |
| 83 } | 114 } |
| 84 | 115 |
| 85 void DeviceCapabilitiesImpl::Register(const std::string& key, | 116 void DeviceCapabilitiesImpl::Register(const std::string& key, |
| 86 Validator* validator) { | 117 Validator* validator) { |
| 87 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 88 DCHECK(IsValidRegisterKey(key)); | 118 DCHECK(IsValidRegisterKey(key)); |
| 89 DCHECK(validator); | 119 DCHECK(validator); |
| 90 | 120 |
| 91 bool added = validator_map_.insert(std::make_pair(key, validator)).second; | 121 base::AutoLock auto_lock(validation_lock_); |
| 122 bool added = | |
| 123 validator_map_.insert(std::make_pair(key, ValidatorInfo(validator))) | |
| 124 .second; | |
| 92 // Check that a validator has not already been registered for this key | 125 // Check that a validator has not already been registered for this key |
| 93 DCHECK(added); | 126 DCHECK(added); |
| 94 } | 127 } |
| 95 | 128 |
| 96 void DeviceCapabilitiesImpl::Unregister(const std::string& key, | 129 void DeviceCapabilitiesImpl::Unregister(const std::string& key, |
| 97 const Validator* validator) { | 130 const Validator* validator) { |
| 98 DCHECK(thread_checker_.CalledOnValidThread()); | 131 base::AutoLock auto_lock(validation_lock_); |
| 132 auto validator_it = validator_map_.find(key); | |
| 133 DCHECK(validator_it != validator_map_.end()); | |
| 99 // Check that validator being unregistered matches the original for |key|. | 134 // Check that validator being unregistered matches the original for |key|. |
| 100 // This prevents managers from accidentally unregistering incorrect | 135 // This prevents managers from accidentally unregistering incorrect |
| 101 // validators. | 136 // validators. |
| 102 DCHECK_EQ(validator, GetValidator(key)); | 137 DCHECK_EQ(validator, validator_it->second.validator); |
| 138 // Check that validator is unregistering on same thread that it was | |
| 139 // registered on | |
| 140 DCHECK(validator_it->second.task_runner->BelongsToCurrentThread()); | |
| 103 bool erased = validator_map_.erase(key); | 141 bool erased = validator_map_.erase(key); |
| 104 DCHECK(erased); | 142 DCHECK(erased); |
| 105 } | 143 } |
| 106 | 144 |
| 107 DeviceCapabilities::Validator* DeviceCapabilitiesImpl::GetValidator( | 145 DeviceCapabilities::Validator* DeviceCapabilitiesImpl::GetValidator( |
| 108 const std::string& key) const { | 146 const std::string& key) const { |
| 147 base::AutoLock auto_lock(validation_lock_); | |
| 109 auto validator_it = validator_map_.find(key); | 148 auto validator_it = validator_map_.find(key); |
| 110 return validator_it == validator_map_.end() ? nullptr : validator_it->second; | 149 return validator_it == validator_map_.end() ? nullptr |
| 150 : validator_it->second.validator; | |
| 111 } | 151 } |
| 112 | 152 |
| 113 bool DeviceCapabilitiesImpl::BluetoothSupported() const { | 153 bool DeviceCapabilitiesImpl::BluetoothSupported() const { |
| 114 DCHECK(thread_checker_.CalledOnValidThread()); | 154 scoped_refptr<ImmutableRefCountedDictionary> capabilities_ref = |
| 155 GetCapabilitiesReference(); | |
| 115 bool bluetooth_supported = false; | 156 bool bluetooth_supported = false; |
| 116 bool found_key = | 157 bool found_key = capabilities_ref->data->GetBoolean(kKeyBluetoothSupported, |
| 117 capabilities_->GetBoolean(kKeyBluetoothSupported, &bluetooth_supported); | 158 &bluetooth_supported); |
| 118 DCHECK(found_key); | 159 DCHECK(found_key); |
| 119 return bluetooth_supported; | 160 return bluetooth_supported; |
| 120 } | 161 } |
| 121 | 162 |
| 122 bool DeviceCapabilitiesImpl::DisplaySupported() const { | 163 bool DeviceCapabilitiesImpl::DisplaySupported() const { |
| 123 DCHECK(thread_checker_.CalledOnValidThread()); | 164 scoped_refptr<ImmutableRefCountedDictionary> capabilities_ref = |
| 165 GetCapabilitiesReference(); | |
| 124 bool display_supported = false; | 166 bool display_supported = false; |
| 125 bool found_key = | 167 bool found_key = capabilities_ref->data->GetBoolean(kKeyDisplaySupported, |
| 126 capabilities_->GetBoolean(kKeyDisplaySupported, &display_supported); | 168 &display_supported); |
| 127 DCHECK(found_key); | 169 DCHECK(found_key); |
| 128 return display_supported; | 170 return display_supported; |
| 129 } | 171 } |
| 130 | 172 |
| 131 bool DeviceCapabilitiesImpl::GetCapability( | 173 scoped_ptr<base::Value> DeviceCapabilitiesImpl::GetCapability( |
| 132 const std::string& path, | 174 const std::string& path) const { |
| 133 const base::Value** out_value) const { | 175 scoped_refptr<ImmutableRefCountedDictionary> capabilities_ref = |
| 134 DCHECK(thread_checker_.CalledOnValidThread()); | 176 GetCapabilitiesReference(); |
| 135 return capabilities_ && capabilities_->Get(path, out_value); | 177 const base::Value* value = nullptr; |
| 178 bool found_path = capabilities_ref->data->Get(path, &value); | |
| 179 return found_path ? value->CreateDeepCopy() : scoped_ptr<base::Value>(); | |
| 136 } | 180 } |
| 137 | 181 |
| 138 const std::string& DeviceCapabilitiesImpl::GetCapabilitiesString() const { | 182 std::string DeviceCapabilitiesImpl::GetCapabilitiesString() const { |
| 139 DCHECK(thread_checker_.CalledOnValidThread()); | 183 scoped_refptr<ImmutableRefCountedString> capabilities_str_ref = |
| 140 return *capabilities_str_; | 184 GetCapabilitiesStrReference(); |
| 185 return *capabilities_str_ref->data; | |
| 141 } | 186 } |
| 142 | 187 |
| 143 const base::DictionaryValue* DeviceCapabilitiesImpl::GetCapabilities() const { | 188 scoped_ptr<base::DictionaryValue> DeviceCapabilitiesImpl::GetCapabilities() |
|
byungchul
2015/10/27 20:38:22
wrap line after scoped<>
esum
2015/10/28 00:29:50
Done.
| |
| 144 DCHECK(thread_checker_.CalledOnValidThread()); | 189 const { |
| 145 return capabilities_.get(); | 190 scoped_refptr<ImmutableRefCountedDictionary> capabilities_ref = |
| 191 GetCapabilitiesReference(); | |
| 192 return capabilities_ref->data->CreateDeepCopy(); | |
| 146 } | 193 } |
| 147 | 194 |
| 148 void DeviceCapabilitiesImpl::SetCapability( | 195 void DeviceCapabilitiesImpl::SetCapability( |
| 149 const std::string& path, | 196 const std::string& path, |
| 150 scoped_ptr<base::Value> proposed_value) { | 197 scoped_ptr<base::Value> proposed_value) { |
| 151 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 152 DCHECK(proposed_value.get()); | 198 DCHECK(proposed_value.get()); |
| 153 if (!IsValidPath(path)) { | 199 if (!IsValidPath(path)) { |
| 154 LOG(DFATAL) << "Invalid capability path encountered for SetCapability()"; | 200 LOG(DFATAL) << "Invalid capability path encountered for SetCapability()"; |
| 155 return; | 201 return; |
| 156 } | 202 } |
| 157 | 203 |
| 158 // Check for Validator registered under first key per the Register() | 204 { |
| 159 // interface. | 205 base::AutoLock auto_lock(validation_lock_); |
| 160 auto validator_it = validator_map_.find(GetFirstKey(path)); | 206 // Check for Validator registered under first key per the Register() |
| 161 if (validator_it == validator_map_.end()) { | 207 // interface. |
| 162 SetValidatedValueInternal(path, proposed_value.Pass()); | 208 auto validator_it = validator_map_.find(GetFirstKey(path)); |
| 163 return; | 209 if (validator_it != validator_map_.end()) { |
| 210 // We do not want to post a task directly for the Validator's Validate() | |
| 211 // method here because if another thread is in the middle of unregistering | |
| 212 // that Validator, there will be an outstanding call to Validate() that | |
| 213 // occurs after it has unregistered. Posting a task on Validator's | |
| 214 // task_runner that checks if Validator is still registered before calling | |
| 215 // Validate fixes this. | |
| 216 validator_it->second.task_runner->PostTask( | |
|
maclellant
2015/10/27 21:00:32
You can move this outside the lock once you have t
esum
2015/10/27 21:14:25
If you cache the ValidatorInfo reference and relea
maclellant
2015/10/27 21:20:07
Well before you were posting unretained pointer an
| |
| 217 FROM_HERE, | |
| 218 base::Bind(&DeviceCapabilitiesImpl::RunValidateOnCurrentThread, | |
| 219 base::Unretained(this), path, | |
|
byungchul
2015/10/27 20:38:22
It is not safe to use Unretained() here. This one
| |
| 220 base::Passed(&proposed_value), | |
| 221 validator_it->second.validator)); | |
| 222 return; | |
| 223 } | |
| 164 } | 224 } |
| 225 // Since we are done checking for a registered Validator at this point, we | |
| 226 // can release the lock. All further member access will be for capabilities. | |
| 227 SetValidatedValue(path, proposed_value.Pass()); | |
| 228 } | |
| 165 | 229 |
| 166 validator_it->second->Validate(path, proposed_value.Pass()); | 230 void DeviceCapabilitiesImpl::RunValidateOnCurrentThread( |
| 231 const std::string& path, | |
| 232 scoped_ptr<base::Value> proposed_value, | |
| 233 Validator* validator) { | |
| 234 { | |
| 235 base::AutoLock auto_lock(validation_lock_); | |
| 236 auto validator_it = validator_map_.find(GetFirstKey(path)); | |
| 237 // Checking that |validator| is still registered because it may have been | |
| 238 // unregistered, or even another Validator got registered in its place from | |
| 239 // the time that the RunValidateOnCurrentThread() task was posted in | |
| 240 // SetCapability(). In either case, we just want to abort call to | |
| 241 // Validate() by returning here. | |
| 242 if (validator_it == validator_map_.end() || | |
| 243 validator_it->second.validator != validator) | |
| 244 return; | |
| 245 | |
| 246 // Check that we are on same thread that Validator registered on before | |
| 247 // calling Validate(). | |
| 248 DCHECK(validator_it->second.task_runner->BelongsToCurrentThread()); | |
| 249 | |
| 250 // We can release the lock here because we know that the Validator has to | |
| 251 // get unregistered on the thread that we're on right now. So there cannot | |
| 252 // concurrently be a call to Unregister() here. | |
| 253 } | |
|
byungchul
2015/10/27 20:38:22
Lock twice doesn't look great. What about using a
esum
2015/10/27 20:57:45
Oh wow didn't think of that. I like this a lot mor
maclellant
2015/10/27 21:00:32
Would there be any concerns with accessing the wea
byungchul
2015/10/27 21:07:39
You can get weak_ptr on any thread, but can access
esum
2015/10/27 21:14:25
Yes SetCapability() will run on the capabilities t
maclellant
2015/10/27 21:20:07
+1
Got it, wasn't sure if weak_ptr was accessed o
esum
2015/10/28 00:29:51
Done.
| |
| 254 // No locks should be held at this point in the code to ensure that there | |
| 255 // are no deadlocks if Validate() calls DeviceCapabilities methods. | |
| 256 validator->Validate(path, proposed_value.Pass()); | |
| 167 } | 257 } |
| 168 | 258 |
| 169 void DeviceCapabilitiesImpl::MergeDictionary( | 259 void DeviceCapabilitiesImpl::MergeDictionary( |
| 170 const base::DictionaryValue& dict_value) { | 260 const base::DictionaryValue& dict_value) { |
| 171 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 172 for (base::DictionaryValue::Iterator it(dict_value); !it.IsAtEnd(); | 261 for (base::DictionaryValue::Iterator it(dict_value); !it.IsAtEnd(); |
| 173 it.Advance()) { | 262 it.Advance()) { |
| 174 SetCapability(it.key(), it.value().CreateDeepCopy()); | 263 SetCapability(it.key(), it.value().CreateDeepCopy()); |
| 175 } | 264 } |
| 176 } | 265 } |
| 177 | 266 |
| 178 void DeviceCapabilitiesImpl::AddCapabilitiesObserver(Observer* observer) { | 267 void DeviceCapabilitiesImpl::AddCapabilitiesObserver(Observer* observer) { |
| 179 DCHECK(observer); | 268 DCHECK(observer); |
| 180 DCHECK(thread_checker_.CalledOnValidThread()); | 269 observer_list_->AddObserver(observer); |
| 181 observer_list_.AddObserver(observer); | |
| 182 } | 270 } |
| 183 | 271 |
| 184 void DeviceCapabilitiesImpl::RemoveCapabilitiesObserver(Observer* observer) { | 272 void DeviceCapabilitiesImpl::RemoveCapabilitiesObserver(Observer* observer) { |
| 185 DCHECK(observer); | 273 DCHECK(observer); |
| 186 DCHECK(thread_checker_.CalledOnValidThread()); | 274 observer_list_->RemoveObserver(observer); |
| 187 observer_list_.RemoveObserver(observer); | |
| 188 } | 275 } |
| 189 | 276 |
| 190 void DeviceCapabilitiesImpl::SetValidatedValueInternal( | 277 void DeviceCapabilitiesImpl::SetValidatedValue( |
| 191 const std::string& path, | 278 const std::string& path, |
| 192 scoped_ptr<base::Value> new_value) { | 279 scoped_ptr<base::Value> new_value) { |
| 193 DCHECK(thread_checker_.CalledOnValidThread()); | 280 // All internal writes/modifications of capabilities must occur on same |
| 281 // thread to avoid race conditions. | |
| 282 if (!task_runner_for_writes_->BelongsToCurrentThread()) { | |
| 283 task_runner_for_writes_->PostTask( | |
| 284 FROM_HERE, | |
| 285 base::Bind(&DeviceCapabilitiesImpl::SetValidatedValue, | |
| 286 base::Unretained(this), path, base::Passed(&new_value))); | |
| 287 return; | |
| 288 } | |
| 289 | |
| 194 DCHECK(IsValidPath(path)); | 290 DCHECK(IsValidPath(path)); |
| 195 DCHECK(new_value.get()); | 291 DCHECK(new_value.get()); |
| 196 | 292 |
| 293 // We don't need to acquire lock here when reading capabilities_ because | |
| 294 // we know that all writes to capabilities_ must occur serially on thread | |
| 295 // that we're on. | |
| 197 const base::Value* cur_value = nullptr; | 296 const base::Value* cur_value = nullptr; |
| 198 bool capability_unchaged = | 297 bool capability_unchanged = capabilities_->data->Get(path, &cur_value) && |
| 199 GetCapability(path, &cur_value) && cur_value->Equals(new_value.get()); | 298 cur_value->Equals(new_value.get()); |
| 200 if (capability_unchaged) { | 299 if (capability_unchanged) { |
| 201 VLOG(1) << "Ignoring unchanged capability: " << path; | 300 VLOG(1) << "Ignoring unchanged capability: " << path; |
| 202 return; | 301 return; |
| 203 } | 302 } |
| 204 | 303 |
| 205 capabilities_->Set(path, new_value.Pass()); | 304 // In this sequence, we create a deep copy, modify the deep copy, and then |
| 206 UpdateStrAndNotifyChanged(path); | 305 // do a pointer swap. We do this to have minimal time spent in the |
| 306 // capabilities_lock_. If we were to lock and modify the capabilities | |
| 307 // dictionary directly, there may be expensive writes that block other | |
| 308 // threads. | |
| 309 scoped_ptr<base::DictionaryValue> capabilities_deep_copy( | |
| 310 capabilities_->data->CreateDeepCopy()); | |
| 311 capabilities_deep_copy->Set(path, new_value.Pass()); | |
| 312 | |
| 313 scoped_refptr<ImmutableRefCountedString> new_capabilities_str( | |
| 314 new ImmutableRefCountedString(SerializeToJson(*capabilities_deep_copy))); | |
| 315 DCHECK(capabilities_str_->data.get()); | |
| 316 scoped_refptr<ImmutableRefCountedDictionary> new_capabilities( | |
| 317 new ImmutableRefCountedDictionary(capabilities_deep_copy.Pass())); | |
| 318 | |
| 319 { | |
| 320 base::AutoLock auto_lock(capabilities_lock_); | |
| 321 // Using swap instead of assignment operator here because it's a little | |
| 322 // faster. Avoids an extra call to AddRef()/Release(). | |
| 323 capabilities_.swap(new_capabilities); | |
|
maclellant
2015/10/27 21:00:32
Since you always update these two fields together,
esum
2015/10/27 21:14:25
Sure, SGTM.
esum
2015/10/28 00:29:50
Done.
| |
| 324 capabilities_str_.swap(new_capabilities_str); | |
| 325 } | |
| 326 | |
| 327 // Even though ObseverListThreadSafe notifications are always asynchronous | |
| 328 // (posts task even if to same thread), no locks should be held at this point | |
| 329 // in the code. This is just to be safe that no deadlocks occur if Observers | |
| 330 // call DeviceCapabilities methods in OnCapabilitiesChanged(). | |
| 331 observer_list_->Notify(FROM_HERE, &Observer::OnCapabilitiesChanged, path); | |
| 207 } | 332 } |
| 208 | 333 |
| 209 void DeviceCapabilitiesImpl::UpdateStrAndNotifyChanged( | 334 scoped_refptr<DeviceCapabilitiesImpl::ImmutableRefCountedDictionary> |
| 210 const std::string& path) { | 335 DeviceCapabilitiesImpl::GetCapabilitiesReference() const { |
| 211 // Update capabilities string here since all updates to capabilities must | 336 // Need to acquire lock here when copy constructing capabilities_ otherwise |
| 212 // ultimately call this method no matter where the update originated from. | 337 // we could be concurrently be writing to scoped_refptr in |
| 213 capabilities_str_ = SerializeToJson(*capabilities_); | 338 // SetValidatedValue(), which could cause a bad scoped_refptr read. |
| 214 DCHECK(capabilities_str_.get()); | 339 base::AutoLock auto_lock(capabilities_lock_); |
| 215 FOR_EACH_OBSERVER(Observer, observer_list_, OnCapabilitiesChanged(path)); | 340 return capabilities_; |
| 341 } | |
| 342 | |
| 343 scoped_refptr<DeviceCapabilitiesImpl::ImmutableRefCountedString> | |
| 344 DeviceCapabilitiesImpl::GetCapabilitiesStrReference() const { | |
| 345 base::AutoLock auto_lock(capabilities_lock_); | |
| 346 return capabilities_str_; | |
| 216 } | 347 } |
| 217 | 348 |
| 218 } // namespace chromecast | 349 } // namespace chromecast |
| OLD | NEW |