diff --git a/src/Messages/OWSMessageSender.m b/src/Messages/OWSMessageSender.m index b45f3ce5c..3b99e24cd 100644 --- a/src/Messages/OWSMessageSender.m +++ b/src/Messages/OWSMessageSender.m @@ -56,6 +56,11 @@ NS_ASSUME_NONNULL_BEGIN success:(void (^)())successHandler failure:(void (^)(NSError *_Nonnull error))failureHandler NS_DESIGNATED_INITIALIZER; +#pragma mark - background task mgmt + +- (void)startBackgroundTask; +- (void)endBackgroundTask; + @end typedef NS_ENUM(NSInteger, OWSSendMessageOperationState) { @@ -83,7 +88,8 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; @property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) void (^successHandler)(); @property (nonatomic, readonly) void (^failureHandler)(NSError *_Nonnull error); -@property (atomic) OWSSendMessageOperationState operationState; +@property (nonatomic) OWSSendMessageOperationState operationState; +@property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier; @end @@ -100,6 +106,7 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; } _operationState = OWSSendMessageOperationStateNew; + _backgroundTaskIdentifier = UIBackgroundTaskInvalid; _message = message; _messageSender = messageSender; @@ -131,6 +138,38 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; return self; } +#pragma mark - background task mgmt + +// We want to make sure to finish sending any in-flight messages when the app is backgrounded. +// We have to call `startBackgroundTask` *before* the task is enqueued, since we can't guarantee when the operation will +// be dequeued. +- (void)startBackgroundTask +{ + AssertIsOnMainThread(); + OWSAssert(self.backgroundTaskIdentifier == UIBackgroundTaskInvalid); + + self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + DDLogWarn(@"%@ Timed out while in background trying to send message: %@", self.tag, self.message); + [self endBackgroundTask]; + }]; +} + +- (void)endBackgroundTask +{ + [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier]; +} + +- (void)setBackgroundTaskIdentifier:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier +{ + AssertIsOnMainThread(); + + // Should only be sent once per operation + OWSAssert(_backgroundTaskIdentifier == UIBackgroundTaskInvalid); + OWSAssert(backgroundTaskIdentifier != UIBackgroundTaskInvalid); + + _backgroundTaskIdentifier = backgroundTaskIdentifier; +} + #pragma mark - NSOperation overrides - (BOOL)isExecuting @@ -145,6 +184,10 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; - (void)start { + // Should call `startBackgroundTask` before enqueuing the operation + // to ensure we don't get suspended before the operation completes. + OWSAssert(self.backgroundTaskIdentifier != UIBackgroundTaskInvalid); + [self willChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; self.operationState = OWSSendMessageOperationStateExecuting; [self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; @@ -182,6 +225,8 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; self.operationState = OWSSendMessageOperationStateFinished; [self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; [self didChangeValueForKey:OWSSendMessageOperationKeyIsFinished]; + + [self endBackgroundTask]; } #pragma mark - Logging @@ -252,6 +297,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; messageSender:self success:successHandler failure:failureHandler]; + + // We call `startBackgroundTask` here to prevent our app from suspending while being backgrounded + // until the operation is completed - at which point the OWSSendMessageOperation ends it's background task. + [sendMessageOperation startBackgroundTask]; [self.sendingQueue addOperation:sendMessageOperation]; }