Address deadlocks in profile manager.

// FREEBIE
pull/1/head
Matthew Chen 7 years ago
parent 907ddd4d36
commit cb365d0a58

@ -16,9 +16,9 @@ extern NSString *const kNSNotificationKey_ProfileGroupId;
extern const NSUInteger kOWSProfileManager_NameDataLength;
extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;
@class TSThread;
@class OWSAES256Key;
@class OWSMessageSender;
@class TSThread;
// This class can be safely accessed and used from any thread.
@interface OWSProfileManager : NSObject <ProfileManagerProtocol>
@ -63,10 +63,6 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter;
- (void)addThreadToProfileWhitelist:(TSThread *)thread;
- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread;
- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId;
- (void)setContactRecipientIds:(NSArray<NSString *> *)contactRecipientIds;
#pragma mark - Other User's Profiles

@ -26,20 +26,20 @@ NS_ASSUME_NONNULL_BEGIN
// UserProfile properties may be read from any thread, but should
// only be mutated when synchronized on the profile manager.
@interface UserProfile : TSYapDatabaseObject
@interface UserProfile : TSYapDatabaseObject <NSCopying>
@property (atomic, readonly) NSString *recipientId;
@property (atomic, nullable) OWSAES256Key *profileKey;
@property (nonatomic, nullable) NSString *profileName;
@property (nonatomic, nullable) NSString *avatarUrlPath;
@property (atomic, nullable) NSString *profileName;
@property (atomic, nullable) NSString *avatarUrlPath;
// This filename is relative to OWSProfileManager.profileAvatarsDirPath.
@property (nonatomic, nullable) NSString *avatarFileName;
@property (atomic, nullable) NSString *avatarFileName;
// This should reflect when either:
//
// * The last successful update finished.
// * The current in-flight update began.
@property (nonatomic, nullable) NSDate *lastUpdateDate;
@property (atomic, nullable) NSDate *lastUpdateDate;
- (instancetype)init NS_UNAVAILABLE;
@ -49,6 +49,8 @@ NS_ASSUME_NONNULL_BEGIN
@implementation UserProfile
@synthesize profileName = _profileName;
- (instancetype)initWithRecipientId:(NSString *)recipientId
{
self = [super initWithUniqueId:recipientId];
@ -63,23 +65,67 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (nullable NSString *)profileName
{
@synchronized(self)
{
return _profileName;
}
}
- (void)setProfileName:(nullable NSString *)profileName
{
_profileName = [profileName ows_stripped];
@synchronized(self)
{
_profileName = [profileName ows_stripped];
}
}
#pragma mark - NSCopying
- (id)copyWithZone:(nullable NSZone *)zone
{
UserProfile *copy = [[UserProfile alloc] initWithRecipientId:self.recipientId];
copy.profileKey = self.profileKey;
copy.profileName = self.profileName;
copy.avatarUrlPath = self.avatarUrlPath;
copy.avatarFileName = self.avatarFileName;
copy.lastUpdateDate = self.lastUpdateDate;
return copy;
}
#pragma mark - NSObject
- (BOOL)isEqual:(UserProfile *)other
- (BOOL)areEqualStrings:(NSString *)left other:(NSString *)right
{
return ([other isKindOfClass:[UserProfile class]] && [self.recipientId isEqualToString:other.recipientId] &&
[self.profileName isEqualToString:other.profileName] && [self.avatarUrlPath isEqualToString:other.avatarUrlPath] &&
[self.avatarFileName isEqualToString:other.avatarFileName]);
if (!left && !right) {
return YES;
}
if (!left || !right) {
return NO;
}
return [left isEqualToString:right];
}
- (NSUInteger)hash
- (BOOL)areEqualKeys:(OWSAES256Key *)left other:(OWSAES256Key *)right
{
return self.recipientId.hash ^ self.profileName.hash ^ self.avatarUrlPath.hash ^ self.avatarFileName.hash;
if (!left && !right) {
return YES;
}
if (!left || !right) {
return NO;
}
return [left.keyData isEqual:right.keyData];
}
- (BOOL)isEqual:(UserProfile *)other
{
// Don't bother comparing lastUpdateDate property.
return ([other isKindOfClass:[UserProfile class]] && [self areEqualStrings:self.recipientId other:other.recipientId]
&& [self areEqualStrings:self.profileName other:other.profileName] &&
[self areEqualKeys:self.profileKey other:other.profileKey] &&
[self areEqualStrings:self.avatarUrlPath other:other.avatarUrlPath] &&
[self areEqualStrings:self.avatarFileName other:other.avatarFileName]);
}
@end
@ -234,42 +280,48 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640;
{
OWSAssert(userProfile);
// Make a copy to use inside the transaction.
// To avoid deadlock, we want to avoid creating a new transaction while sync'd on self.
UserProfile *userProfileCopy;
@synchronized(self)
{
// Make sure to save on the local db connection for consistency.
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[userProfile saveWithTransaction:transaction];
}];
userProfileCopy = [userProfile copy];
// Other threads may modify this profile's properties
OWSAssert([userProfile isEqual:userProfileCopy]);
}
BOOL isLocalUserProfile = userProfile == self.localUserProfile;
// Make sure to save on the local db connection for consistency.
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[userProfileCopy saveWithTransaction:transaction];
}];
dispatch_async(dispatch_get_main_queue(), ^{
if (isLocalUserProfile) {
[MultiDeviceProfileKeyUpdateJob runWithProfileKey:userProfile.profileKey
identityManager:self.identityManager
messageSender:self.messageSender
profileManager:self];
BOOL isLocalUserProfile = userProfile == self.localUserProfile;
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
} else {
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_OtherUsersProfileWillChange
object:nil
userInfo:@{
kNSNotificationKey_ProfileRecipientId : userProfile.recipientId,
}];
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_OtherUsersProfileDidChange
object:nil
userInfo:@{
kNSNotificationKey_ProfileRecipientId : userProfile.recipientId,
}];
}
});
}
dispatch_async(dispatch_get_main_queue(), ^{
if (isLocalUserProfile) {
[MultiDeviceProfileKeyUpdateJob runWithProfileKey:userProfile.profileKey
identityManager:self.identityManager
messageSender:self.messageSender
profileManager:self];
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
} else {
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_OtherUsersProfileWillChange
object:nil
userInfo:@{
kNSNotificationKey_ProfileRecipientId : userProfile.recipientId,
}];
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_OtherUsersProfileDidChange
object:nil
userInfo:@{
kNSNotificationKey_ProfileRecipientId : userProfile.recipientId,
}];
}
});
}
- (void)ensureLocalProfileCached
@ -295,9 +347,6 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640;
DDLogInfo(@"%@ Building local profile.", self.tag);
_localUserProfile = [[UserProfile alloc] initWithRecipientId:kLocalProfileUniqueId];
_localUserProfile.profileKey = [OWSAES256Key generateRandomKey];
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[_localUserProfile saveWithTransaction:transaction];
}];
// Sync local profile to any linked device
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self)

Loading…
Cancel
Save