From bbc7c44c9354184b97cc3e61691fbca6fefa6f33 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 15 Jun 2017 10:23:55 -0400 Subject: [PATCH] Use transactions in the jobs. // FREEBIE --- src/Messages/Interactions/TSOutgoingMessage.h | 2 + src/Messages/Interactions/TSOutgoingMessage.m | 16 +- src/Messages/OWSDisappearingMessagesFinder.h | 25 +-- src/Messages/OWSDisappearingMessagesFinder.m | 143 ++++++++---------- src/Messages/OWSDisappearingMessagesJob.m | 76 ++++++---- .../OWSFailedAttachmentDownloadsJob.m | 47 +++--- src/Messages/OWSFailedMessagesJob.m | 55 ++++--- src/Storage/TSStorageManager.m | 3 +- 8 files changed, 202 insertions(+), 165 deletions(-) diff --git a/src/Messages/Interactions/TSOutgoingMessage.h b/src/Messages/Interactions/TSOutgoingMessage.h index 60215e710..a001b8dc0 100644 --- a/src/Messages/Interactions/TSOutgoingMessage.h +++ b/src/Messages/Interactions/TSOutgoingMessage.h @@ -157,6 +157,8 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) { // This isn't a perfect arrangement, but in practice this will prevent // data loss and will resolve all known issues. - (void)updateWithMessageState:(TSOutgoingMessageState)messageState; +- (void)updateWithMessageState:(TSOutgoingMessageState)messageState + transaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)updateWithSendingError:(NSError *)error; - (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript; - (void)updateWithCustomMessage:(NSString *)customMessage transaction:(YapDatabaseReadWriteTransaction *)transaction; diff --git a/src/Messages/Interactions/TSOutgoingMessage.m b/src/Messages/Interactions/TSOutgoingMessage.m index 07b1b765a..ad3d1a8f1 100644 --- a/src/Messages/Interactions/TSOutgoingMessage.m +++ b/src/Messages/Interactions/TSOutgoingMessage.m @@ -257,13 +257,21 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec - (void)updateWithMessageState:(TSOutgoingMessageState)messageState { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self applyChangeToSelfAndLatestOutgoingMessage:transaction - changeBlock:^(TSOutgoingMessage *message) { - [message setMessageState:messageState]; - }]; + [self updateWithMessageState:messageState transaction:transaction]; }]; } +- (void)updateWithMessageState:(TSOutgoingMessageState)messageState + transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + OWSAssert(transaction); + + [self applyChangeToSelfAndLatestOutgoingMessage:transaction + changeBlock:^(TSOutgoingMessage *message) { + [message setMessageState:messageState]; + }]; +} + - (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { diff --git a/src/Messages/OWSDisappearingMessagesFinder.h b/src/Messages/OWSDisappearingMessagesFinder.h index 01e22bf07..5dccfeba0 100644 --- a/src/Messages/OWSDisappearingMessagesFinder.h +++ b/src/Messages/OWSDisappearingMessagesFinder.h @@ -1,38 +1,39 @@ -// Created by Michael Kirk on 9/23/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// NS_ASSUME_NONNULL_BEGIN @class TSStorageManager; @class TSMessage; @class TSThread; +@class YapDatabaseReadTransaction; +@class YapDatabaseReadWriteTransaction; @interface OWSDisappearingMessagesFinder : NSObject -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager NS_DESIGNATED_INITIALIZER; - -+ (instancetype)defaultInstance; - -- (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block; -- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread block:(void (^_Nonnull)(TSMessage *message))block; +- (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction; +- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread + block:(void (^_Nonnull)(TSMessage *message))block + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction; /** * @return * uint64_t millisecond timestamp wrapped in a number. Retrieve with `unsignedLongLongvalue`. * or nil if there are no upcoming expired messages */ -- (nullable NSNumber *)nextExpirationTimestamp; +- (nullable NSNumber *)nextExpirationTimestampWithTransaction:(YapDatabaseReadTransaction *_Nonnull)transaction; /** * Database extensions required for class to work. */ -- (void)asyncRegisterDatabaseExtensions; ++ (void)asyncRegisterDatabaseExtensions:(TSStorageManager *)storageManager; /** * Only use the sync version for testing, generally we'll want to register extensions async */ -- (void)blockingRegisterDatabaseExtensions; ++ (void)blockingRegisterDatabaseExtensions:(TSStorageManager *)storageManager; @end diff --git a/src/Messages/OWSDisappearingMessagesFinder.m b/src/Messages/OWSDisappearingMessagesFinder.m index 7f59cc8cb..7590ae8c4 100644 --- a/src/Messages/OWSDisappearingMessagesFinder.m +++ b/src/Messages/OWSDisappearingMessagesFinder.m @@ -1,5 +1,6 @@ -// Created by Michael Kirk on 9/23/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "OWSDisappearingMessagesFinder.h" #import "NSDate+millisecondTimeStamp.h" @@ -17,40 +18,13 @@ static NSString *const OWSDisappearingMessageFinderThreadIdColumn = @"thread_id" static NSString *const OWSDisappearingMessageFinderExpiresAtColumn = @"expires_at"; static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_messages_on_expires_at_and_thread_id_v2"; -@interface OWSDisappearingMessagesFinder () - -@property (nonatomic, readonly) TSStorageManager *storageManager; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - @implementation OWSDisappearingMessagesFinder -- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager -{ - self = [super init]; - if (!self) { - return self; - } - - _storageManager = storageManager; - _dbConnection = [storageManager newDatabaseConnection]; - - return self; -} - -+ (instancetype)defaultInstance -{ - static OWSDisappearingMessagesFinder *defaultInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultInstance = [[self alloc] initWithStorageManager:[TSStorageManager sharedManager]]; - }); - return defaultInstance; -} - - (NSArray *)fetchUnstartedExpiringMessageIdsInThread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + NSMutableArray *messageIds = [NSMutableArray new]; NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ = 0 AND %@ = \"%@\"", OWSDisappearingMessageFinderExpiresAtColumn, @@ -58,19 +32,19 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess thread.uniqueId]; YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [messageIds addObject:key]; - }]; - }]; + [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] + enumerateKeysMatchingQuery:query + usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { + [messageIds addObject:key]; + }]; return [messageIds copy]; } -- (NSArray *)fetchExpiredMessageIds +- (NSArray *)fetchExpiredMessageIdsWithTransaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + NSMutableArray *messageIds = [NSMutableArray new]; uint64_t now = [NSDate ows_millisecondTimeStamp]; @@ -80,33 +54,31 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess OWSDisappearingMessageFinderExpiresAtColumn, now]; YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [messageIds addObject:key]; - }]; - }]; + [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] + enumerateKeysMatchingQuery:query + usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { + [messageIds addObject:key]; + }]; return [messageIds copy]; } -- (nullable NSNumber *)nextExpirationTimestamp +- (nullable NSNumber *)nextExpirationTimestampWithTransaction:(YapDatabaseReadTransaction *_Nonnull)transaction { + OWSAssert(transaction); + NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ > 0 ORDER BY %@ ASC", OWSDisappearingMessageFinderExpiresAtColumn, OWSDisappearingMessageFinderExpiresAtColumn]; YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; __block TSMessage *firstMessage; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] - enumerateKeysAndObjectsMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) { - firstMessage = (TSMessage *)object; - *stop = YES; - }]; - }]; + [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] + enumerateKeysAndObjectsMatchingQuery:query + usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) { + firstMessage = (TSMessage *)object; + *stop = YES; + }]; if (firstMessage && firstMessage.expiresAt > 0) { return [NSNumber numberWithUnsignedLongLong:firstMessage.expiresAt]; @@ -115,10 +87,15 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess return nil; } -- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread block:(void (^_Nonnull)(TSMessage *message))block +- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread + block:(void (^_Nonnull)(TSMessage *message))block + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { - for (NSString *expiringMessageId in [self fetchUnstartedExpiringMessageIdsInThread:thread]) { - TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId]; + OWSAssert(transaction); + + for (NSString *expiringMessageId in + [self fetchUnstartedExpiringMessageIdsInThread:thread transaction:transaction]) { + TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId transaction:transaction]; if ([message isKindOfClass:[TSMessage class]]) { block(message); } else { @@ -132,23 +109,30 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess * We don't want to instantiate potentially many messages at once. */ - (NSArray *)fetchUnstartedExpiringMessagesInThread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + NSMutableArray *messages = [NSMutableArray new]; [self enumerateUnstartedExpiringMessagesInThread:thread block:^(TSMessage *_Nonnull message) { [messages addObject:message]; - }]; + } + transaction:transaction]; return [messages copy]; } - (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + // Since we can't directly mutate the enumerated expired messages, we store only their ids in hopes of saving a // little memory and then enumerate the (larger) TSMessage objects one at a time. - for (NSString *expiredMessageId in [self fetchExpiredMessageIds]) { - TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiredMessageId]; + for (NSString *expiredMessageId in [self fetchExpiredMessageIdsWithTransaction:transaction]) { + TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiredMessageId transaction:transaction]; if ([message isKindOfClass:[TSMessage class]]) { block(message); } else { @@ -161,19 +145,22 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess * Don't use this in production. Useful for testing. * We don't want to instantiate potentially many messages at once. */ -- (NSArray *)fetchExpiredMessages +- (NSArray *)fetchExpiredMessagesWithTransaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + NSMutableArray *messages = [NSMutableArray new]; [self enumerateExpiredMessagesWithBlock:^(TSMessage *_Nonnull message) { [messages addObject:message]; - }]; + } + transaction:transaction]; return [messages copy]; } #pragma mark - YapDatabaseExtension -- (YapDatabaseSecondaryIndex *)indexDatabaseExtension ++ (YapDatabaseSecondaryIndex *)indexDatabaseExtension { YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; [setup addColumn:OWSDisappearingMessageFinderExpiresAtColumn withType:YapDatabaseSecondaryIndexTypeInteger]; @@ -202,23 +189,23 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess } // Useful for tests, don't use in app startup path because it's slow. -- (void)blockingRegisterDatabaseExtensions ++ (void)blockingRegisterDatabaseExtensions:(TSStorageManager *)storageManager { - [self.storageManager.database registerExtension:[self indexDatabaseExtension] - withName:OWSDisappearingMessageFinderExpiresAtIndex]; + [storageManager.database registerExtension:[self indexDatabaseExtension] + withName:OWSDisappearingMessageFinderExpiresAtIndex]; } -- (void)asyncRegisterDatabaseExtensions ++ (void)asyncRegisterDatabaseExtensions:(TSStorageManager *)storageManager { - [self.storageManager.database asyncRegisterExtension:[self indexDatabaseExtension] - withName:OWSDisappearingMessageFinderExpiresAtIndex - completionBlock:^(BOOL ready) { - if (ready) { - DDLogDebug(@"%@ completed registering extension async.", self.tag); - } else { - DDLogError(@"%@ failed registering extension async.", self.tag); - } - }]; + [storageManager.database asyncRegisterExtension:[self indexDatabaseExtension] + withName:OWSDisappearingMessageFinderExpiresAtIndex + completionBlock:^(BOOL ready) { + if (ready) { + DDLogDebug(@"%@ completed registering extension async.", self.tag); + } else { + DDLogError(@"%@ failed registering extension async.", self.tag); + } + }]; } #pragma mark - Logging diff --git a/src/Messages/OWSDisappearingMessagesJob.m b/src/Messages/OWSDisappearingMessagesJob.m index 6a8d86cc1..ad8b9561f 100644 --- a/src/Messages/OWSDisappearingMessagesJob.m +++ b/src/Messages/OWSDisappearingMessagesJob.m @@ -17,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSDisappearingMessagesJob () +// This property should only be accessed on the serialQueue. +@property (nonatomic, readonly) TSStorageManager *storageManager; + // This property should only be accessed on the serialQueue. @property (nonatomic, readonly) OWSDisappearingMessagesFinder *disappearingMessagesFinder; @@ -48,7 +51,8 @@ NS_ASSUME_NONNULL_BEGIN return self; } - _disappearingMessagesFinder = [[OWSDisappearingMessagesFinder alloc] initWithStorageManager:storageManager]; + _storageManager = storageManager; + _disappearingMessagesFinder = [OWSDisappearingMessagesFinder new]; OWSSingletonAssert(); @@ -84,16 +88,20 @@ NS_ASSUME_NONNULL_BEGIN uint64_t now = [NSDate ows_millisecondTimeStamp]; __block uint expirationCount = 0; - [self.disappearingMessagesFinder enumerateExpiredMessagesWithBlock:^(TSMessage *message) { - // sanity check - if (message.expiresAt > now) { - DDLogError(@"%@ Refusing to remove message which doesn't expire until: %lld", self.tag, message.expiresAt); - return; + [self.storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [self.disappearingMessagesFinder enumerateExpiredMessagesWithBlock:^(TSMessage *message) { + // sanity check + if (message.expiresAt > now) { + DDLogError( + @"%@ Refusing to remove message which doesn't expire until: %lld", self.tag, message.expiresAt); + return; + } + + DDLogDebug(@"%@ Removing message which expired at: %lld", self.tag, message.expiresAt); + [message removeWithTransaction:transaction]; + expirationCount++; } - - DDLogDebug(@"%@ Removing message which expired at: %lld", self.tag, message.expiresAt); - [message remove]; - expirationCount++; + transaction:transaction]; }]; DDLogDebug(@"%@ Removed %u expired messages", self.tag, expirationCount); @@ -106,7 +114,11 @@ NS_ASSUME_NONNULL_BEGIN [self run]; uint64_t now = [NSDate ows_millisecondTimeStamp]; - NSNumber *nextExpirationTimestampNumber = [self.disappearingMessagesFinder nextExpirationTimestamp]; + __block NSNumber *nextExpirationTimestampNumber; + [self.storageManager.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + nextExpirationTimestampNumber = + [self.disappearingMessagesFinder nextExpirationTimestampWithTransaction:transaction]; + }]; if (!nextExpirationTimestampNumber) { // In theory we could kill the loop here. It should resume when the next expiring message is saved, // But this is a safeguard for any race conditions that exist while running the job as a new message is saved. @@ -142,15 +154,19 @@ NS_ASSUME_NONNULL_BEGIN [self setExpirationForMessage:message expirationStartedAt:[NSDate ows_millisecondTimeStamp]]; } -+ (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt +- (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt { - dispatch_async(self.serialQueue, ^{ - [[self sharedJob] setExpirationForMessage:message expirationStartedAt:expirationStartedAt]; - }); + [self.storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [self setExpirationForMessage:message expirationStartedAt:expirationStartedAt transaction:transaction]; + }]; } -- (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt +- (void)setExpirationForMessage:(TSMessage *)message + expirationStartedAt:(uint64_t)expirationStartedAt + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + if (!message.isExpiringMessage) { return; } @@ -161,7 +177,7 @@ NS_ASSUME_NONNULL_BEGIN // Don't clobber if multiple actions simultaneously triggered expiration. if (message.expireStartedAt == 0 || message.expireStartedAt > expirationStartedAt) { message.expireStartedAt = expirationStartedAt; - [message save]; + [message saveWithTransaction:transaction]; } // Necessary that the async expiration run happens *after* the message is saved with expiration configuration. @@ -178,16 +194,22 @@ NS_ASSUME_NONNULL_BEGIN - (void)setExpirationsForThread:(TSThread *)thread { uint64_t now = [NSDate ows_millisecondTimeStamp]; - [self.disappearingMessagesFinder - enumerateUnstartedExpiringMessagesInThread:thread - block:^(TSMessage *_Nonnull message) { - DDLogWarn(@"%@ Starting expiring message which should have already " - @"been started.", - self.tag); - // specify "now" in case D.M. have since been disabled, but we have - // existing unstarted expiring messages that still need to expire. - [self setExpirationForMessage:message expirationStartedAt:now]; - }]; + [self.storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [self.disappearingMessagesFinder + enumerateUnstartedExpiringMessagesInThread:thread + block:^(TSMessage *_Nonnull message) { + DDLogWarn( + @"%@ Starting expiring message which should have already " + @"been started.", + self.tag); + // specify "now" in case D.M. have since been disabled, but we have + // existing unstarted expiring messages that still need to expire. + [self setExpirationForMessage:message + expirationStartedAt:now + transaction:transaction]; + } + transaction:transaction]; + }]; } + (void)becomeConsistentWithConfigurationForMessage:(TSMessage *)message diff --git a/src/Messages/OWSFailedAttachmentDownloadsJob.m b/src/Messages/OWSFailedAttachmentDownloadsJob.m index ccd215513..2715406fc 100644 --- a/src/Messages/OWSFailedAttachmentDownloadsJob.m +++ b/src/Messages/OWSFailedAttachmentDownloadsJob.m @@ -36,33 +36,36 @@ static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateIndex = @"i return self; } -- (NSArray *)fetchAttemptingOutAttachmentIds:(YapDatabaseConnection *)dbConnection +- (NSArray *)fetchAttemptingOutAttachmentIdsWithTransaction: + (YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + NSMutableArray *attachmentIds = [NSMutableArray new]; NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ != %d", OWSFailedAttachmentDownloadsJobAttachmentStateColumn, (int)TSAttachmentPointerStateFailed]; YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [[transaction ext:OWSFailedAttachmentDownloadsJobAttachmentStateIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [attachmentIds addObject:key]; - }]; - }]; + [[transaction ext:OWSFailedAttachmentDownloadsJobAttachmentStateIndex] + enumerateKeysMatchingQuery:query + usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { + [attachmentIds addObject:key]; + }]; return [attachmentIds copy]; } - (void)enumerateAttemptingOutAttachmentsWithBlock:(void (^_Nonnull)(TSAttachmentPointer *attachment))block + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { - YapDatabaseConnection *dbConnection = [self.storageManager newDatabaseConnection]; + OWSAssert(transaction); // Since we can't directly mutate the enumerated attachments, we store only their ids in hopes // of saving a little memory and then enumerate the (larger) TSAttachment objects one at a time. - for (NSString *attachmentId in [self fetchAttemptingOutAttachmentIds:dbConnection]) { - TSAttachmentPointer *_Nullable attachment = [TSAttachmentPointer fetchObjectWithUniqueID:attachmentId]; + for (NSString *attachmentId in [self fetchAttemptingOutAttachmentIdsWithTransaction:transaction]) { + TSAttachmentPointer *_Nullable attachment = + [TSAttachmentPointer fetchObjectWithUniqueID:attachmentId transaction:transaction]; if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { block(attachment); } else { @@ -74,15 +77,19 @@ static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateIndex = @"i - (void)run { __block uint count = 0; - [self enumerateAttemptingOutAttachmentsWithBlock:^(TSAttachmentPointer *attachment) { - // sanity check - if (attachment.state != TSAttachmentPointerStateFailed) { - DDLogDebug(@"%@ marking attachment as failed", self.tag); - attachment.state = TSAttachmentPointerStateFailed; - [attachment save]; - count++; - } - }]; + [[self.storageManager newDatabaseConnection] + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [self enumerateAttemptingOutAttachmentsWithBlock:^(TSAttachmentPointer *attachment) { + // sanity check + if (attachment.state != TSAttachmentPointerStateFailed) { + DDLogDebug(@"%@ marking attachment as failed", self.tag); + attachment.state = TSAttachmentPointerStateFailed; + [attachment saveWithTransaction:transaction]; + count++; + } + } + transaction:transaction]; + }]; DDLogDebug(@"%@ Marked %u attachments as unsent", self.tag, count); } diff --git a/src/Messages/OWSFailedMessagesJob.m b/src/Messages/OWSFailedMessagesJob.m index 546a52950..e3d31f553 100644 --- a/src/Messages/OWSFailedMessagesJob.m +++ b/src/Messages/OWSFailedMessagesJob.m @@ -37,33 +37,36 @@ static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_m return self; } -- (NSArray *)fetchAttemptingOutMessageIds:(YapDatabaseConnection *)dbConnection +- (NSArray *)fetchAttemptingOutMessageIdsWithTransaction: + (YapDatabaseReadWriteTransaction *_Nonnull)transaction { + OWSAssert(transaction); + NSMutableArray *messageIds = [NSMutableArray new]; NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ == %d", OWSFailedMessagesJobMessageStateColumn, (int)TSOutgoingMessageStateAttemptingOut]; YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [[transaction ext:OWSFailedMessagesJobMessageStateIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [messageIds addObject:key]; - }]; - }]; + [[transaction ext:OWSFailedMessagesJobMessageStateIndex] + enumerateKeysMatchingQuery:query + usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { + [messageIds addObject:key]; + }]; return [messageIds copy]; } - (void)enumerateAttemptingOutMessagesWithBlock:(void (^_Nonnull)(TSOutgoingMessage *message))block + transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction { - YapDatabaseConnection *dbConnection = [self.storageManager newDatabaseConnection]; + OWSAssert(transaction); // Since we can't directly mutate the enumerated "attempting out" expired messages, we store only their ids in hopes // of saving a little memory and then enumerate the (larger) TSMessage objects one at a time. - for (NSString *expiredMessageId in [self fetchAttemptingOutMessageIds:dbConnection]) { - TSOutgoingMessage *_Nullable message = [TSOutgoingMessage fetchObjectWithUniqueID:expiredMessageId]; + for (NSString *expiredMessageId in [self fetchAttemptingOutMessageIdsWithTransaction:transaction]) { + TSOutgoingMessage *_Nullable message = + [TSOutgoingMessage fetchObjectWithUniqueID:expiredMessageId transaction:transaction]; if ([message isKindOfClass:[TSOutgoingMessage class]]) { block(message); } else { @@ -75,18 +78,26 @@ static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_m - (void)run { __block uint count = 0; - [self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) { - // sanity check - OWSAssert(message.messageState == TSOutgoingMessageStateAttemptingOut); - if (message.messageState != TSOutgoingMessageStateAttemptingOut) { - DDLogError(@"%@ Refusing to mark as unsent message with state: %d", self.tag, (int)message.messageState); - return; - } - DDLogDebug(@"%@ marking message as unsent", self.tag); - [message updateWithMessageState:TSOutgoingMessageStateUnsent]; - count++; - }]; + [[self.storageManager newDatabaseConnection] + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) { + // sanity check + OWSAssert(message.messageState == TSOutgoingMessageStateAttemptingOut); + if (message.messageState != TSOutgoingMessageStateAttemptingOut) { + DDLogError( + @"%@ Refusing to mark as unsent message with state: %d", self.tag, (int)message.messageState); + return; + } + + DDLogDebug(@"%@ marking message as unsent: %@", self.tag, message.uniqueId); + [message updateWithMessageState:TSOutgoingMessageStateUnsent transaction:transaction]; + OWSAssert(message.messageState == TSOutgoingMessageStateUnsent); + + count++; + } + transaction:transaction]; + }]; DDLogDebug(@"%@ Marked %u messages as unsent", self.tag, count); } diff --git a/src/Storage/TSStorageManager.m b/src/Storage/TSStorageManager.m index aa14826d7..b435a07d3 100644 --- a/src/Storage/TSStorageManager.m +++ b/src/Storage/TSStorageManager.m @@ -206,8 +206,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; [[OWSIncomingMessageFinder new] asyncRegisterExtension]; [TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView]; [OWSReadReceipt asyncRegisterIndexOnSenderIdAndTimestampWithDatabase:self.database]; - OWSDisappearingMessagesFinder *finder = [[OWSDisappearingMessagesFinder alloc] initWithStorageManager:self]; - [finder asyncRegisterDatabaseExtensions]; + [OWSDisappearingMessagesFinder asyncRegisterDatabaseExtensions:self]; OWSFailedMessagesJob *failedMessagesJob = [[OWSFailedMessagesJob alloc] initWithStorageManager:self]; [failedMessagesJob asyncRegisterDatabaseExtensions]; OWSFailedAttachmentDownloadsJob *failedAttachmentDownloadsMessagesJob =