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:
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
}