LEFT | RIGHT |
1 // Copyright 2012 Google Inc. All Rights Reserved. | 1 // Copyright 2012 Google Inc. All Rights Reserved. |
2 // Use of this source code is governed by an Apache-style license that can be | 2 // Use of this source code is governed by an Apache-style license that can be |
3 // found in the COPYING file. | 3 // found in the COPYING file. |
4 | 4 |
5 #include "rlz/mac/lib/rlz_value_store_mac.h" | 5 #include "rlz/mac/lib/rlz_value_store_mac.h" |
6 | 6 |
7 #include "base/mac/foundation_util.h" | 7 #include "base/mac/foundation_util.h" |
8 #include "base/file_path.h" | 8 #include "base/file_path.h" |
9 #include "base/logging.h" | 9 #include "base/logging.h" |
10 #include "base/sys_string_conversions.h" | 10 #include "base/sys_string_conversions.h" |
11 #include "rlz/lib/assert.h" | 11 #include "rlz/lib/assert.h" |
12 #include "rlz/lib/lib_values.h" | 12 #include "rlz/lib/lib_values.h" |
13 #include "rlz/lib/rlz_lib.h" | 13 #include "rlz/lib/rlz_lib.h" |
14 | 14 |
15 #import <Foundation/Foundation.h> | 15 #import <Foundation/Foundation.h> |
| 16 #include <pthread.h> |
16 | 17 |
17 using base::mac::ObjCCast; | 18 using base::mac::ObjCCast; |
18 | 19 |
19 namespace rlz_lib { | 20 namespace rlz_lib { |
20 | 21 |
21 // These are written to disk and should not be changed. | 22 // These are written to disk and should not be changed. |
22 NSString* const kPingTimeKey = @"pingTime"; | 23 NSString* const kPingTimeKey = @"pingTime"; |
23 NSString* const kAccessPointKey = @"accessPoints"; | 24 NSString* const kAccessPointKey = @"accessPoints"; |
24 NSString* const kProductEventKey = @"productEvents"; | 25 NSString* const kProductEventKey = @"productEvents"; |
25 NSString* const kStatefulEventKey = @"statefulEvents"; | 26 NSString* const kStatefulEventKey = @"statefulEvents"; |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
208 return GetOrCreateDict(dict_.get(), brand_ns); | 209 return GetOrCreateDict(dict_.get(), brand_ns); |
209 } | 210 } |
210 | 211 |
211 NSMutableDictionary* RlzValueStoreMac::ProductDict(Product p) { | 212 NSMutableDictionary* RlzValueStoreMac::ProductDict(Product p) { |
212 return GetOrCreateDict(WorkingDict(), GetNSProductName(p)); | 213 return GetOrCreateDict(WorkingDict(), GetNSProductName(p)); |
213 } | 214 } |
214 | 215 |
215 | 216 |
216 namespace { | 217 namespace { |
217 | 218 |
| 219 // Creating a recursive cross-process mutex on windows is one line. On mac, |
| 220 // there's no primitve for that, so this lock is emulated by an in-process |
| 221 // mutex to get the recursive part, followed by a cross-process lock for the |
| 222 // cross-process part. |
| 223 |
| 224 // This is a struct so that it doesn't need a static initializer. |
| 225 struct RecursiveCrossProcessLock { |
| 226 // Tries to acquire a recursive cross-process lock. Note that this _always_ |
| 227 // acquires the in-process lock (if it wasn't already acquired). The parent |
| 228 // directory of |lock_file| must exist. |
| 229 bool TryGetCrossProcessLock(NSString* lock_filename); |
| 230 |
| 231 // Releases the lock. Should always be called, even if |
| 232 // TryGetCrossProcessLock() returns false. |
| 233 void ReleaseLock(); |
| 234 |
| 235 pthread_mutex_t recursive_lock_; |
| 236 pthread_t locking_thread_; |
| 237 |
| 238 NSDistributedLock* file_lock_; |
| 239 } g_recursive_lock = { |
| 240 // PTHREAD_RECURSIVE_MUTEX_INITIALIZER doesn't exist before 10.7 and is buggy |
| 241 // on 10.7 (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51906#c34), so emulate |
| 242 // recursive locking with a normal non-recursive mutex. |
| 243 PTHREAD_MUTEX_INITIALIZER |
| 244 }; |
| 245 |
| 246 bool RecursiveCrossProcessLock::TryGetCrossProcessLock( |
| 247 NSString* lock_filename) { |
| 248 bool just_got_lock = false; |
| 249 |
| 250 // Emulate a recursive mutex with a non-recursive one. |
| 251 if (pthread_mutex_trylock(&recursive_lock_) == EBUSY) { |
| 252 if (pthread_equal(pthread_self(), locking_thread_) == 0) { |
| 253 // Some other thread has the lock, wait for it. |
| 254 pthread_mutex_lock(&recursive_lock_); |
| 255 CHECK(locking_thread_ == 0); |
| 256 just_got_lock = true; |
| 257 } |
| 258 } else { |
| 259 just_got_lock = true; |
| 260 } |
| 261 |
| 262 locking_thread_ = pthread_self(); |
| 263 |
| 264 // Try to acquire file lock. |
| 265 if (just_got_lock) { |
| 266 const int kMaxTimeoutMS = 5000; // Matches windows. |
| 267 const int kSleepPerTryMS = 200; |
| 268 |
| 269 CHECK(!file_lock_); |
| 270 file_lock_ = [[NSDistributedLock alloc] initWithPath:lock_filename]; |
| 271 |
| 272 BOOL got_file_lock = NO; |
| 273 int elapsedMS = 0; |
| 274 while (!(got_file_lock = [file_lock_ tryLock]) && |
| 275 elapsedMS < kMaxTimeoutMS) { |
| 276 usleep(kSleepPerTryMS * 1000); |
| 277 elapsedMS += kSleepPerTryMS; |
| 278 } |
| 279 |
| 280 if (!got_file_lock) { |
| 281 [file_lock_ release]; |
| 282 file_lock_ = nil; |
| 283 return false; |
| 284 } |
| 285 return true; |
| 286 } else { |
| 287 return file_lock_ != nil; |
| 288 } |
| 289 } |
| 290 |
| 291 void RecursiveCrossProcessLock::ReleaseLock() { |
| 292 if (file_lock_) { |
| 293 [file_lock_ unlock]; |
| 294 [file_lock_ release]; |
| 295 file_lock_ = nil; |
| 296 } |
| 297 |
| 298 locking_thread_ = 0; |
| 299 pthread_mutex_unlock(&recursive_lock_); |
| 300 } |
| 301 |
| 302 |
218 // This is set during test execution, to write RLZ files into a temporary | 303 // This is set during test execution, to write RLZ files into a temporary |
219 // directory instead of the user's Application Support folder. | 304 // directory instead of the user's Application Support folder. |
220 NSString* g_test_folder; | 305 NSString* g_test_folder; |
221 | 306 |
222 // RlzValueStoreMac keeps its data in memory and only writes it to disk when | 307 // RlzValueStoreMac keeps its data in memory and only writes it to disk when |
223 // ScopedRlzValueStoreLock goes out of scope. Hence, if several | 308 // ScopedRlzValueStoreLock goes out of scope. Hence, if several |
224 // ScopedRlzValueStoreLocks are nested, they all need to use the same store | 309 // ScopedRlzValueStoreLocks are nested, they all need to use the same store |
225 // object. | 310 // object. |
226 | 311 |
227 // This counts the nesting depth. | 312 // This counts the nesting depth. |
(...skipping 24 matching lines...) Expand all Loading... |
252 return folder; | 337 return folder; |
253 } | 338 } |
254 | 339 |
255 // Returns the path of the rlz plist store, also creates the parent directory | 340 // Returns the path of the rlz plist store, also creates the parent directory |
256 // path if it doesn't exist. | 341 // path if it doesn't exist. |
257 NSString* RlzPlistFilename() { | 342 NSString* RlzPlistFilename() { |
258 NSString* const kRlzFile = @"RlzStore.plist"; | 343 NSString* const kRlzFile = @"RlzStore.plist"; |
259 return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile]; | 344 return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile]; |
260 } | 345 } |
261 | 346 |
| 347 // Returns the path of the rlz lock file, also creates the parent directory |
| 348 // path if it doesn't exist. |
| 349 NSString* RlzLockFilename() { |
| 350 NSString* const kRlzFile = @"lockfile"; |
| 351 return [CreateRlzDirectory() stringByAppendingPathComponent:kRlzFile]; |
| 352 } |
| 353 |
262 } // namespace | 354 } // namespace |
263 | 355 |
264 ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() { | 356 ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() { |
265 // TODO(thakis): Try to take a recursive cross-process lock for 5 minutes, | 357 bool got_distributed_lock = |
266 // set store_ to NULL if that fails and return. | 358 g_recursive_lock.TryGetCrossProcessLock(RlzLockFilename()); |
267 | 359 // At this point, we hold the in-process lock, no matter the value of |
268 // NSRecursiveLock (or posix: | 360 // |got_distributed_lock|. |
269 // static pthread_mutex_t mtx = PTHREAD_RECURSIVE_MUTEX_INITIALIZER | |
270 // pthread_mutex_lock(&mtx) | |
271 // pthread_mutex_unlock(&mtx) | |
272 // ) | |
273 | |
274 // NSDistributedLock* for the file lock. | |
275 | |
276 // Timeout after waiting 5 min (XXX on which lock?) | |
277 | |
278 // Think about failing to take the lock and then a nested lock. | |
279 // Should never happen in practice as everything errors out early if | |
280 // failing to take a lock, so just CHECK() I guess. | |
281 | |
282 | 361 |
283 ++g_lock_depth; | 362 ++g_lock_depth; |
284 if (g_lock_depth > 1) { | 363 if (g_lock_depth > 1) { |
285 // Reuse the already existing store object. | 364 // Reuse the already existing store object. |
| 365 // All user code early-exits when a lock fails, so a recursive lock will |
| 366 // never end up with |g_store_object| that is NULL. |
| 367 CHECK(got_distributed_lock); |
286 CHECK(g_store_object); | 368 CHECK(g_store_object); |
287 store_.reset(g_store_object); | 369 store_.reset(g_store_object); |
| 370 return; |
| 371 } |
| 372 |
| 373 if (!got_distributed_lock) { |
| 374 // Give up. |store_| isn't set, which signals to callers that acquiring |
| 375 // the lock failed. |g_recursive_lock| will be released by the |
| 376 // destructor. |
288 return; | 377 return; |
289 } | 378 } |
290 | 379 |
291 CHECK(!g_store_object); | 380 CHECK(!g_store_object); |
292 | 381 |
293 NSString* plist = RlzPlistFilename(); | 382 NSString* plist = RlzPlistFilename(); |
294 | 383 |
295 // Create an empty file if none exists yet. | 384 // Create an empty file if none exists yet. |
296 NSFileManager* manager = [NSFileManager defaultManager]; | 385 NSFileManager* manager = [NSFileManager defaultManager]; |
297 if (![manager fileExistsAtPath:plist isDirectory:NULL]) | 386 if (![manager fileExistsAtPath:plist isDirectory:NULL]) |
298 [[NSDictionary dictionary] writeToFile:plist atomically:YES]; | 387 [[NSDictionary dictionary] writeToFile:plist atomically:YES]; |
299 | 388 |
300 NSMutableDictionary* dict = | 389 NSMutableDictionary* dict = |
301 [NSMutableDictionary dictionaryWithContentsOfFile:plist]; | 390 [NSMutableDictionary dictionaryWithContentsOfFile:plist]; |
302 VERIFY(dict); | 391 VERIFY(dict); |
303 | 392 |
304 if (dict) { | 393 if (dict) { |
305 store_.reset(new RlzValueStoreMac(dict, plist)); | 394 store_.reset(new RlzValueStoreMac(dict, plist)); |
306 g_store_object = (RlzValueStoreMac*)store_.get(); | 395 g_store_object = (RlzValueStoreMac*)store_.get(); |
307 } | 396 } |
308 } | 397 } |
309 | 398 |
310 ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() { | 399 ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() { |
311 --g_lock_depth; | 400 --g_lock_depth; |
312 | 401 CHECK(g_lock_depth >= 0); |
313 if (g_lock_depth) { | 402 |
| 403 if (g_lock_depth > 0) { |
314 // Other locks are still using store_, don't free it yet. | 404 // Other locks are still using store_, don't free it yet. |
315 ignore_result(store_.release()); | 405 ignore_result(store_.release()); |
316 return; | 406 return; |
317 } | 407 } |
318 | 408 |
319 if (store_.get()) { | 409 if (store_.get()) { |
320 g_store_object = NULL; | 410 g_store_object = NULL; |
321 | 411 |
322 NSString* folder = CreateRlzDirectory(); | 412 NSString* folder = CreateRlzDirectory(); |
323 NSDictionary* dict = | 413 NSDictionary* dict = |
324 static_cast<RlzValueStoreMac*>(store_.get())->dictionary(); | 414 static_cast<RlzValueStoreMac*>(store_.get())->dictionary(); |
325 VERIFY([dict writeToFile:RlzPlistFilename() atomically:YES]); | 415 VERIFY([dict writeToFile:RlzPlistFilename() atomically:YES]); |
326 } | 416 } |
| 417 |
| 418 // Check that "store_ set" => "file_lock acquired". The converse isn't true, |
| 419 // for example if the rlz data file can't be read. |
| 420 if (store_.get()) |
| 421 CHECK(g_recursive_lock.file_lock_); |
| 422 if (!g_recursive_lock.file_lock_) |
| 423 CHECK(!store_.get()); |
| 424 |
| 425 g_recursive_lock.ReleaseLock(); |
327 } | 426 } |
328 | 427 |
329 RlzValueStore* ScopedRlzValueStoreLock::GetStore() { | 428 RlzValueStore* ScopedRlzValueStoreLock::GetStore() { |
330 return store_.get(); | 429 return store_.get(); |
331 } | 430 } |
332 | 431 |
333 namespace testing { | 432 namespace testing { |
334 | 433 |
335 void SetRlzStoreDirectory(const FilePath& directory) { | 434 void SetRlzStoreDirectory(const FilePath& directory) { |
336 base::mac::ScopedNSAutoreleasePool pool; | 435 base::mac::ScopedNSAutoreleasePool pool; |
337 | 436 |
338 [g_test_folder release]; | 437 [g_test_folder release]; |
339 if (directory.empty()) { | 438 if (directory.empty()) { |
340 g_test_folder = nil; | 439 g_test_folder = nil; |
341 } else { | 440 } else { |
342 // Not Unsafe on OS X. | 441 // Not Unsafe on OS X. |
343 g_test_folder = | 442 g_test_folder = |
344 [[NSString alloc] initWithUTF8String:directory.AsUTF8Unsafe().c_str()]; | 443 [[NSString alloc] initWithUTF8String:directory.AsUTF8Unsafe().c_str()]; |
345 } | 444 } |
346 } | 445 } |
347 | 446 |
348 } // namespace testing | 447 } // namespace testing |
349 | 448 |
350 } // namespace rlz_lib | 449 } // namespace rlz_lib |
LEFT | RIGHT |