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 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
52 scoped_ptr<DeviceCapabilities> DeviceCapabilities::CreateForTesting() { | 54 scoped_ptr<DeviceCapabilities> DeviceCapabilities::CreateForTesting() { |
53 DeviceCapabilities* capabilities = new DeviceCapabilitiesImpl; | 55 DeviceCapabilities* capabilities = new DeviceCapabilitiesImpl; |
54 capabilities->SetCapability( | 56 capabilities->SetCapability( |
55 kKeyBluetoothSupported, | 57 kKeyBluetoothSupported, |
56 make_scoped_ptr(new base::FundamentalValue(false))); | 58 make_scoped_ptr(new base::FundamentalValue(false))); |
57 capabilities->SetCapability( | 59 capabilities->SetCapability( |
58 kKeyDisplaySupported, make_scoped_ptr(new base::FundamentalValue(true))); | 60 kKeyDisplaySupported, make_scoped_ptr(new base::FundamentalValue(true))); |
59 return make_scoped_ptr(capabilities); | 61 return make_scoped_ptr(capabilities); |
60 } | 62 } |
61 | 63 |
| 64 scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData() { |
| 65 return make_scoped_refptr(new Data); |
| 66 } |
| 67 |
| 68 scoped_refptr<DeviceCapabilities::Data> DeviceCapabilities::CreateData( |
| 69 scoped_ptr<const base::DictionaryValue> dictionary) { |
| 70 DCHECK(dictionary.get()); |
| 71 return make_scoped_refptr(new Data(dictionary.Pass())); |
| 72 } |
| 73 |
62 DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities) | 74 DeviceCapabilities::Validator::Validator(DeviceCapabilities* capabilities) |
63 : capabilities_(capabilities) { | 75 : capabilities_(capabilities) { |
64 DCHECK(capabilities); | 76 DCHECK(capabilities); |
65 } | 77 } |
66 | 78 |
67 void DeviceCapabilities::Validator::SetValidatedValue( | 79 void DeviceCapabilities::Validator::SetValidatedValue( |
68 const std::string& path, | 80 const std::string& path, |
69 scoped_ptr<base::Value> new_value) const { | 81 scoped_ptr<base::Value> new_value) const { |
70 capabilities_->SetValidatedValueInternal(path, new_value.Pass()); | 82 capabilities_->SetValidatedValue(path, new_value.Pass()); |
| 83 } |
| 84 |
| 85 DeviceCapabilities::Data::Data() |
| 86 : dictionary_(new base::DictionaryValue), |
| 87 json_string_(SerializeToJson(*dictionary_)) { |
| 88 DCHECK(json_string_.get()); |
| 89 } |
| 90 |
| 91 DeviceCapabilities::Data::Data( |
| 92 scoped_ptr<const base::DictionaryValue> dictionary) |
| 93 : dictionary_(dictionary.Pass()), |
| 94 json_string_(SerializeToJson(*dictionary_)) { |
| 95 DCHECK(dictionary_.get()); |
| 96 DCHECK(json_string_.get()); |
| 97 } |
| 98 |
| 99 DeviceCapabilitiesImpl::Data::~Data() {} |
| 100 |
| 101 DeviceCapabilitiesImpl::ValidatorInfo::ValidatorInfo(Validator* validator) |
| 102 : validator_(validator), task_runner_(base::ThreadTaskRunnerHandle::Get()) { |
| 103 DCHECK(validator_); |
| 104 DCHECK(task_runner_.get()); |
| 105 } |
| 106 |
| 107 DeviceCapabilitiesImpl::ValidatorInfo::~ValidatorInfo() { |
| 108 // Check that ValidatorInfo is being destroyed on the same thread that it was |
| 109 // constructed on. |
| 110 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 111 } |
| 112 |
| 113 void DeviceCapabilitiesImpl::ValidatorInfo::Validate( |
| 114 const std::string& path, |
| 115 scoped_ptr<base::Value> proposed_value) const { |
| 116 // Check that we are running Validate on the same thread that ValidatorInfo |
| 117 // was constructed on. |
| 118 DCHECK(task_runner_->BelongsToCurrentThread()); |
| 119 validator_->Validate(path, proposed_value.Pass()); |
71 } | 120 } |
72 | 121 |
73 DeviceCapabilitiesImpl::DeviceCapabilitiesImpl() | 122 DeviceCapabilitiesImpl::DeviceCapabilitiesImpl() |
74 : capabilities_(new base::DictionaryValue), | 123 : data_(CreateData()), |
75 capabilities_str_(SerializeToJson(*capabilities_)) { | 124 task_runner_for_writes_(base::ThreadTaskRunnerHandle::Get()), |
76 DCHECK(capabilities_str_.get()); | 125 observer_list_(new base::ObserverListThreadSafe<Observer>) { |
| 126 DCHECK(task_runner_for_writes_.get()); |
77 } | 127 } |
78 | 128 |
79 DeviceCapabilitiesImpl::~DeviceCapabilitiesImpl() { | 129 DeviceCapabilitiesImpl::~DeviceCapabilitiesImpl() { |
80 DCHECK(thread_checker_.CalledOnValidThread()); | |
81 // Make sure that any registered Validators have unregistered at this point | 130 // Make sure that any registered Validators have unregistered at this point |
82 DCHECK(validator_map_.empty()); | 131 DCHECK(validator_map_.empty()); |
| 132 // Make sure that all observers have been removed at this point |
| 133 observer_list_->AssertEmpty(); |
83 } | 134 } |
84 | 135 |
85 void DeviceCapabilitiesImpl::Register(const std::string& key, | 136 void DeviceCapabilitiesImpl::Register(const std::string& key, |
86 Validator* validator) { | 137 Validator* validator) { |
87 DCHECK(thread_checker_.CalledOnValidThread()); | |
88 DCHECK(IsValidRegisterKey(key)); | 138 DCHECK(IsValidRegisterKey(key)); |
89 DCHECK(validator); | 139 DCHECK(validator); |
90 | 140 |
91 bool added = validator_map_.insert(std::make_pair(key, validator)).second; | 141 base::AutoLock auto_lock(validation_lock_); |
| 142 bool added = |
| 143 validator_map_.add(key, make_scoped_ptr(new ValidatorInfo(validator))) |
| 144 .second; |
92 // Check that a validator has not already been registered for this key | 145 // Check that a validator has not already been registered for this key |
93 DCHECK(added); | 146 DCHECK(added); |
94 } | 147 } |
95 | 148 |
96 void DeviceCapabilitiesImpl::Unregister(const std::string& key, | 149 void DeviceCapabilitiesImpl::Unregister(const std::string& key, |
97 const Validator* validator) { | 150 const Validator* validator) { |
98 DCHECK(thread_checker_.CalledOnValidThread()); | 151 base::AutoLock auto_lock(validation_lock_); |
| 152 auto validator_it = validator_map_.find(key); |
| 153 DCHECK(validator_it != validator_map_.end()); |
99 // Check that validator being unregistered matches the original for |key|. | 154 // Check that validator being unregistered matches the original for |key|. |
100 // This prevents managers from accidentally unregistering incorrect | 155 // This prevents managers from accidentally unregistering incorrect |
101 // validators. | 156 // validators. |
102 DCHECK_EQ(validator, GetValidator(key)); | 157 DCHECK_EQ(validator, validator_it->second->validator()); |
103 bool erased = validator_map_.erase(key); | 158 // Check that validator is unregistering on same thread that it was |
104 DCHECK(erased); | 159 // registered on |
| 160 DCHECK(validator_it->second->task_runner()->BelongsToCurrentThread()); |
| 161 validator_map_.erase(validator_it); |
105 } | 162 } |
106 | 163 |
107 DeviceCapabilities::Validator* DeviceCapabilitiesImpl::GetValidator( | 164 DeviceCapabilities::Validator* DeviceCapabilitiesImpl::GetValidator( |
108 const std::string& key) const { | 165 const std::string& key) const { |
| 166 base::AutoLock auto_lock(validation_lock_); |
109 auto validator_it = validator_map_.find(key); | 167 auto validator_it = validator_map_.find(key); |
110 return validator_it == validator_map_.end() ? nullptr : validator_it->second; | 168 return validator_it == validator_map_.end() |
| 169 ? nullptr |
| 170 : validator_it->second->validator(); |
111 } | 171 } |
112 | 172 |
113 bool DeviceCapabilitiesImpl::BluetoothSupported() const { | 173 bool DeviceCapabilitiesImpl::BluetoothSupported() const { |
114 DCHECK(thread_checker_.CalledOnValidThread()); | 174 scoped_refptr<Data> data_ref = GetData(); |
115 bool bluetooth_supported = false; | 175 bool bluetooth_supported = false; |
116 bool found_key = | 176 bool found_key = data_ref->dictionary().GetBoolean(kKeyBluetoothSupported, |
117 capabilities_->GetBoolean(kKeyBluetoothSupported, &bluetooth_supported); | 177 &bluetooth_supported); |
118 DCHECK(found_key); | 178 DCHECK(found_key); |
119 return bluetooth_supported; | 179 return bluetooth_supported; |
120 } | 180 } |
121 | 181 |
122 bool DeviceCapabilitiesImpl::DisplaySupported() const { | 182 bool DeviceCapabilitiesImpl::DisplaySupported() const { |
123 DCHECK(thread_checker_.CalledOnValidThread()); | 183 scoped_refptr<Data> data_ref = GetData(); |
124 bool display_supported = false; | 184 bool display_supported = false; |
125 bool found_key = | 185 bool found_key = data_ref->dictionary().GetBoolean(kKeyDisplaySupported, |
126 capabilities_->GetBoolean(kKeyDisplaySupported, &display_supported); | 186 &display_supported); |
127 DCHECK(found_key); | 187 DCHECK(found_key); |
128 return display_supported; | 188 return display_supported; |
129 } | 189 } |
130 | 190 |
131 bool DeviceCapabilitiesImpl::GetCapability( | 191 scoped_ptr<base::Value> DeviceCapabilitiesImpl::GetCapability( |
132 const std::string& path, | 192 const std::string& path) const { |
133 const base::Value** out_value) const { | 193 scoped_refptr<Data> data_ref = GetData(); |
134 DCHECK(thread_checker_.CalledOnValidThread()); | 194 const base::Value* value = nullptr; |
135 return capabilities_ && capabilities_->Get(path, out_value); | 195 bool found_path = data_ref->dictionary().Get(path, &value); |
| 196 return found_path ? value->CreateDeepCopy() : scoped_ptr<base::Value>(); |
136 } | 197 } |
137 | 198 |
138 const std::string& DeviceCapabilitiesImpl::GetCapabilitiesString() const { | 199 scoped_refptr<DeviceCapabilities::Data> |
139 DCHECK(thread_checker_.CalledOnValidThread()); | 200 DeviceCapabilitiesImpl::GetData() const { |
140 return *capabilities_str_; | 201 // Need to acquire lock here when copy constructing data_ otherwise we could |
141 } | 202 // be concurrently be writing to scoped_refptr in SetValidatedValue(), which |
142 | 203 // could cause a bad scoped_refptr read. |
143 const base::DictionaryValue* DeviceCapabilitiesImpl::GetCapabilities() const { | 204 base::AutoLock auto_lock(data_lock_); |
144 DCHECK(thread_checker_.CalledOnValidThread()); | 205 return data_; |
145 return capabilities_.get(); | |
146 } | 206 } |
147 | 207 |
148 void DeviceCapabilitiesImpl::SetCapability( | 208 void DeviceCapabilitiesImpl::SetCapability( |
149 const std::string& path, | 209 const std::string& path, |
150 scoped_ptr<base::Value> proposed_value) { | 210 scoped_ptr<base::Value> proposed_value) { |
151 DCHECK(thread_checker_.CalledOnValidThread()); | |
152 DCHECK(proposed_value.get()); | 211 DCHECK(proposed_value.get()); |
153 if (!IsValidPath(path)) { | 212 if (!IsValidPath(path)) { |
154 LOG(DFATAL) << "Invalid capability path encountered for SetCapability()"; | 213 LOG(DFATAL) << "Invalid capability path encountered for SetCapability()"; |
155 return; | 214 return; |
156 } | 215 } |
157 | 216 |
158 // Check for Validator registered under first key per the Register() | 217 { |
159 // interface. | 218 base::AutoLock auto_lock(validation_lock_); |
160 auto validator_it = validator_map_.find(GetFirstKey(path)); | 219 // Check for Validator registered under first key per the Register() |
161 if (validator_it == validator_map_.end()) { | 220 // interface. |
162 SetValidatedValueInternal(path, proposed_value.Pass()); | 221 auto validator_it = validator_map_.find(GetFirstKey(path)); |
163 return; | 222 if (validator_it != validator_map_.end()) { |
| 223 // We do not want to post a task directly for the Validator's Validate() |
| 224 // method here because if another thread is in the middle of unregistering |
| 225 // that Validator, there will be an outstanding call to Validate() that |
| 226 // occurs after it has unregistered. Since ValidatorInfo gets destroyed |
| 227 // in Unregister() on same thread that validation should run on, we can |
| 228 // post a task to the Validator's thread with weak_ptr. This way, if the |
| 229 // Validator gets unregistered, the call to Validate will get skipped. |
| 230 validator_it->second->task_runner()->PostTask( |
| 231 FROM_HERE, base::Bind(&ValidatorInfo::Validate, |
| 232 validator_it->second->AsWeakPtr(), path, |
| 233 base::Passed(&proposed_value))); |
| 234 return; |
| 235 } |
164 } | 236 } |
165 | 237 // Since we are done checking for a registered Validator at this point, we |
166 validator_it->second->Validate(path, proposed_value.Pass()); | 238 // can release the lock. All further member access will be for capabilities. |
| 239 SetValidatedValue(path, proposed_value.Pass()); |
167 } | 240 } |
168 | 241 |
169 void DeviceCapabilitiesImpl::MergeDictionary( | 242 void DeviceCapabilitiesImpl::MergeDictionary( |
170 const base::DictionaryValue& dict_value) { | 243 const base::DictionaryValue& dict_value) { |
171 DCHECK(thread_checker_.CalledOnValidThread()); | |
172 for (base::DictionaryValue::Iterator it(dict_value); !it.IsAtEnd(); | 244 for (base::DictionaryValue::Iterator it(dict_value); !it.IsAtEnd(); |
173 it.Advance()) { | 245 it.Advance()) { |
174 SetCapability(it.key(), it.value().CreateDeepCopy()); | 246 SetCapability(it.key(), it.value().CreateDeepCopy()); |
175 } | 247 } |
176 } | 248 } |
177 | 249 |
178 void DeviceCapabilitiesImpl::AddCapabilitiesObserver(Observer* observer) { | 250 void DeviceCapabilitiesImpl::AddCapabilitiesObserver(Observer* observer) { |
179 DCHECK(observer); | 251 DCHECK(observer); |
180 DCHECK(thread_checker_.CalledOnValidThread()); | 252 observer_list_->AddObserver(observer); |
181 observer_list_.AddObserver(observer); | |
182 } | 253 } |
183 | 254 |
184 void DeviceCapabilitiesImpl::RemoveCapabilitiesObserver(Observer* observer) { | 255 void DeviceCapabilitiesImpl::RemoveCapabilitiesObserver(Observer* observer) { |
185 DCHECK(observer); | 256 DCHECK(observer); |
186 DCHECK(thread_checker_.CalledOnValidThread()); | 257 observer_list_->RemoveObserver(observer); |
187 observer_list_.RemoveObserver(observer); | |
188 } | 258 } |
189 | 259 |
190 void DeviceCapabilitiesImpl::SetValidatedValueInternal( | 260 void DeviceCapabilitiesImpl::SetValidatedValue( |
191 const std::string& path, | 261 const std::string& path, |
192 scoped_ptr<base::Value> new_value) { | 262 scoped_ptr<base::Value> new_value) { |
193 DCHECK(thread_checker_.CalledOnValidThread()); | 263 // All internal writes/modifications of capabilities must occur on same |
| 264 // thread to avoid race conditions. |
| 265 if (!task_runner_for_writes_->BelongsToCurrentThread()) { |
| 266 task_runner_for_writes_->PostTask( |
| 267 FROM_HERE, |
| 268 base::Bind(&DeviceCapabilitiesImpl::SetValidatedValue, |
| 269 base::Unretained(this), path, base::Passed(&new_value))); |
| 270 return; |
| 271 } |
| 272 |
194 DCHECK(IsValidPath(path)); | 273 DCHECK(IsValidPath(path)); |
195 DCHECK(new_value.get()); | 274 DCHECK(new_value.get()); |
196 | 275 |
| 276 // We don't need to acquire lock here when reading data_ because we know that |
| 277 // all writes to data_ must occur serially on thread that we're on. |
197 const base::Value* cur_value = nullptr; | 278 const base::Value* cur_value = nullptr; |
198 bool capability_unchaged = | 279 bool capability_unchanged = data_->dictionary().Get(path, &cur_value) && |
199 GetCapability(path, &cur_value) && cur_value->Equals(new_value.get()); | 280 cur_value->Equals(new_value.get()); |
200 if (capability_unchaged) { | 281 if (capability_unchanged) { |
201 VLOG(1) << "Ignoring unchanged capability: " << path; | 282 VLOG(1) << "Ignoring unchanged capability: " << path; |
202 return; | 283 return; |
203 } | 284 } |
204 | 285 |
205 capabilities_->Set(path, new_value.Pass()); | 286 // In this sequence, we create a deep copy, modify the deep copy, and then |
206 UpdateStrAndNotifyChanged(path); | 287 // do a pointer swap. We do this to have minimal time spent in the |
207 } | 288 // data_lock_. If we were to lock and modify the capabilities |
| 289 // dictionary directly, there may be expensive writes that block other |
| 290 // threads. |
| 291 scoped_ptr<base::DictionaryValue> dictionary_deep_copy( |
| 292 data_->dictionary().CreateDeepCopy()); |
| 293 dictionary_deep_copy->Set(path, new_value.Pass()); |
| 294 scoped_refptr<Data> new_data(CreateData(dictionary_deep_copy.Pass())); |
208 | 295 |
209 void DeviceCapabilitiesImpl::UpdateStrAndNotifyChanged( | 296 { |
210 const std::string& path) { | 297 base::AutoLock auto_lock(data_lock_); |
211 // Update capabilities string here since all updates to capabilities must | 298 // Using swap instead of assignment operator here because it's a little |
212 // ultimately call this method no matter where the update originated from. | 299 // faster. Avoids an extra call to AddRef()/Release(). |
213 capabilities_str_ = SerializeToJson(*capabilities_); | 300 data_.swap(new_data); |
214 DCHECK(capabilities_str_.get()); | 301 } |
215 FOR_EACH_OBSERVER(Observer, observer_list_, OnCapabilitiesChanged(path)); | 302 |
| 303 // Even though ObseverListThreadSafe notifications are always asynchronous |
| 304 // (posts task even if to same thread), no locks should be held at this point |
| 305 // in the code. This is just to be safe that no deadlocks occur if Observers |
| 306 // call DeviceCapabilities methods in OnCapabilitiesChanged(). |
| 307 observer_list_->Notify(FROM_HERE, &Observer::OnCapabilitiesChanged, path); |
216 } | 308 } |
217 | 309 |
218 } // namespace chromecast | 310 } // namespace chromecast |
OLD | NEW |