Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(462)

Side by Side Diff: chromecast/base/device_capabilities_impl.cc

Issue 1409173006: Making DeviceCapabilities threadsafe with locking. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Clean up unit test. Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698