shlist

share and manage lists between multiple people
Log | Files | Refs

commit 1022463dc33325984e837b505a8f68b91a77cc6b
parent 4fa7f7baac7418a7850c3dd49186a15908103528
Author: Kyle Milz <kyle@Kyles-MacBook-Pro.local>
Date:   Tue, 15 Sep 2015 22:04:39 -0600

ios: fix subtle thread race condition in address book start

Diffstat:
Mios-ng/shlist/AddressBook.h | 4++--
Mios-ng/shlist/AddressBook.m | 92+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mios-ng/shlist/ShlistServer.m | 2+-
3 files changed, 55 insertions(+), 43 deletions(-)

diff --git a/ios-ng/shlist/AddressBook.h b/ios-ng/shlist/AddressBook.h @@ -3,14 +3,14 @@ @interface AddressBook : NSObject @property (strong, retain) NSMutableArray *contacts; -@property int num_contacts; +@property unsigned long num_contacts; // returns singleton instance + (id) shared_address_book; - (void) wait_for_ready; -@property int ready; +@property volatile uint32_t ready; @end diff --git a/ios-ng/shlist/AddressBook.m b/ios-ng/shlist/AddressBook.m @@ -2,8 +2,12 @@ #include <AddressBook/AddressBook.h> #import <UIKit/UIKit.h> +#include "libkern/OSAtomic.h" + @interface AddressBook () +// - (void) get_contacts_or_fail; + @end // empty implementation @@ -18,7 +22,6 @@ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ address_book = [[self alloc] init]; - address_book.ready = 0; }); return address_book; } @@ -29,53 +32,63 @@ if (self) { _contacts = [[NSMutableArray alloc] init]; - ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus(); + _ready = 0; + [self get_contacts_or_fail]; + } + return self; +} - if (status == kABAuthorizationStatusDenied || status == kABAuthorizationStatusRestricted) { - // if you got here, user had previously denied/revoked permission for your - // app to access the contacts, and all you can do is handle this gracefully, - // perhaps telling the user that they have to go to settings to grant access - // to contacts +- (void) get_contacts_or_fail +{ + ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus(); - [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; - return self; - } + if (status == kABAuthorizationStatusDenied || status == kABAuthorizationStatusRestricted) { + // if you got here, user had previously denied/revoked permission for your + // app to access the contacts, and all you can do is handle this gracefully, + // perhaps telling the user that they have to go to settings to grant access + // to contacts - CFErrorRef error = NULL; - ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error); + [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; + return; + } - if (!addressBook) { - NSLog(@"ABAddressBookCreateWithOptions error: %@", CFBridgingRelease(error)); - return self; - } + CFErrorRef error = NULL; + ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error); - // ABAddressBookRegisterExternalChangeCallback(<#ABAddressBookRef addressBook#>, <#ABExternalChangeCallback callback#>, <#void *context#>) + if (!addressBook) { + NSLog(@"ABAddressBookCreateWithOptions error: %@", CFBridgingRelease(error)); + return; + } - ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { - if (error) { - NSLog(@"ABAddressBookRequestAccessWithCompletion error: %@", CFBridgingRelease(error)); - } + // ABAddressBookRegisterExternalChangeCallback(<#ABAddressBookRef addressBook#>, <#ABExternalChangeCallback callback#>, <#void *context#>) - if (granted) { - // if they gave you permission, then just carry on + ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { + if (error) { + NSLog(@"ABAddressBookRequestAccessWithCompletion error: %@", CFBridgingRelease(error)); + } - [self listPeopleInAddressBook:addressBook]; - } else { - // however, if they didn't give you permission, handle it gracefully, for example... + if (granted) { + // if they gave you permission, then just carry on + [self listPeopleInAddressBook:addressBook]; - dispatch_async(dispatch_get_main_queue(), ^{ - // BTW, this is not on the main thread, so dispatch UI updates back to the main queue + // atomically set the ready flag; this completion handler + // can be run on an arbitrary thread + OSAtomicIncrement32((volatile int32_t *)&_ready); + } else { + // however, if they didn't give you permission, handle it gracefully, for example... - [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; - }); - } - - CFRelease(addressBook); - }); - } - return self; + dispatch_async(dispatch_get_main_queue(), ^{ + // BTW, this is not on the main thread, so dispatch UI updates back to the main queue + + [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; + }); + } + + CFRelease(addressBook); + }); } + - (void)listPeopleInAddressBook:(ABAddressBookRef)addressBook { NSArray *allPeople = CFBridgingRelease(ABAddressBookCopyArrayOfAllPeople(addressBook)); @@ -111,17 +124,16 @@ } _num_contacts = [_contacts count]; - _ready = 1; - - NSLog(@"info: address book: %i contacts found", _num_contacts); + NSLog(@"info: address book: %lu contacts found", _num_contacts); } +// call this to make the address book authorization block - (void) wait_for_ready { int cumulative_ms = 0; int sleep_for_ms = 10; // wait for the database to become ready, no upper bound - while (!_ready) { + while (!OSAtomicAnd32(0xffff, &_ready)) { usleep(sleep_for_ms * 1000); cumulative_ms += sleep_for_ms; diff --git a/ios-ng/shlist/ShlistServer.m b/ios-ng/shlist/ShlistServer.m @@ -253,6 +253,7 @@ if (list.list_name == output) { [shlist_tvc.indirect_lists addObject:list]; [shlist_tvc.shared_lists removeObject:list]; + break; } } @@ -402,7 +403,6 @@ [outputShlistStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - inputShlistStream = nil; // stream is ivar, so reinit it outputShlistStream = nil; // stream is ivar, so reinit it }