diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 969ac9375..9fbf5c033 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -445,7 +445,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; - (void)applicationWillResignActive:(UIApplication *)application { DDLogWarn(@"%@ applicationWillResignActive.", self.logTag); - UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:nil]; + __block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([TSAccountManager isRegistered]) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -454,8 +455,11 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [self showScreenProtection]; } [SignalApp.sharedApp.homeViewController updateInboxCountLabel]; - [application endBackgroundTask:bgTask]; + + backgroundTask = nil; }); + } else { + backgroundTask = nil; } }); diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 3987ca017..398df3b68 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -73,6 +73,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 743eb5d80..090d067b7 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -584,16 +584,19 @@ protocol CallServiceObserver: class { self.call = newCall - let backgroundTask = UIApplication.shared.beginBackgroundTask { + var backgroundTask = OWSBackgroundTask(label:"\(#function)", completionBlock: { [weak self] _ in + AssertIsOnMainThread() + guard let strongSelf = self else { + return + } let timeout = CallError.timeout(description: "background task time ran out before call connected.") - DispatchQueue.main.async { - guard self.call == newCall else { - Logger.warn("\(self.logTag) ignoring obsolete call in \(#function)") - return - } - self.handleFailedCall(failedCall: newCall, error: timeout) + + guard strongSelf.call == newCall else { + Logger.warn("\(strongSelf.logTag) ignoring obsolete call in \(#function)") + return } - } + strongSelf.handleFailedCall(failedCall: newCall, error: timeout) + }) let incomingCallPromise = firstly { return getIceServers() @@ -674,7 +677,8 @@ protocol CallServiceObserver: class { } }.always { Logger.debug("\(self.logTag) ending background task awaiting inbound call connection") - UIApplication.shared.endBackgroundTask(backgroundTask) + + backgroundTask = nil } incomingCallPromise.retainUntilComplete() } diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m index 551ab0cfe..ba2ef4572 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -157,7 +157,7 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; { OWSAssert(transaction); - __block OWSBackgroundTask *backgroundTask = [[OWSBackgroundTask alloc] initWithLabelStr:__PRETTY_FUNCTION__]; + __block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; [self setAttachment:attachment isDownloadingInMessage:message transaction:transaction]; diff --git a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m index 94d86b727..d666098a1 100644 --- a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m +++ b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m @@ -336,7 +336,7 @@ NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJo return; } - OWSBackgroundTask *backgroundTask = [[OWSBackgroundTask alloc] initWithLabelStr:__PRETTY_FUNCTION__]; + OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; [self processJobs:jobs]; diff --git a/SignalServiceKit/src/Messages/OWSMessageReceiver.m b/SignalServiceKit/src/Messages/OWSMessageReceiver.m index c80fb9452..fa718181a 100644 --- a/SignalServiceKit/src/Messages/OWSMessageReceiver.m +++ b/SignalServiceKit/src/Messages/OWSMessageReceiver.m @@ -309,7 +309,7 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin return; } - __block OWSBackgroundTask *backgroundTask = [[OWSBackgroundTask alloc] initWithLabelStr:__PRETTY_FUNCTION__]; + __block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; [self processJob:job completion:^(BOOL success) { diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 325d93eb7..6c0a778c9 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -7,6 +7,7 @@ #import "ContactsUpdater.h" #import "NSData+keyVersionByte.h" #import "NSData+messagePadding.h" +#import "OWSBackgroundTask.h" #import "OWSBlockingManager.h" #import "OWSDevice.h" #import "OWSDisappearingMessagesJob.h" @@ -122,11 +123,6 @@ static void *kNSError_MessageSender_IsFatal = &kNSError_MessageSender_IsFatal; success:(void (^)(void))successHandler failure:(void (^)(NSError *_Nonnull error))failureHandler NS_DESIGNATED_INITIALIZER; -#pragma mark - background task mgmt - -- (void)startBackgroundTask; -- (void)endBackgroundTask; - @end #pragma mark - @@ -159,7 +155,7 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; @property (nonatomic, readonly) void (^successHandler)(void); @property (nonatomic, readonly) void (^failureHandler)(NSError *_Nonnull error); @property (nonatomic) OWSSendMessageOperationState operationState; -@property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier; +@property (nonatomic) OWSBackgroundTask *backgroundTask; @end @@ -178,7 +174,7 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; } _operationState = OWSSendMessageOperationStateNew; - _backgroundTaskIdentifier = UIBackgroundTaskInvalid; + self.backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; _message = message; _messageSender = messageSender; @@ -216,42 +212,6 @@ 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 = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ - DDLogWarn(@"%@ Timed out while in background trying to send message: %@", self.logTag, self.message); - [self endBackgroundTask]; - }]; -} - -- (void)endBackgroundTask -{ - if (self.backgroundTaskIdentifier == UIBackgroundTaskInvalid) { - return; - } - [CurrentAppContext() endBackgroundTask:self.backgroundTaskIdentifier]; - self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; -} - -- (void)setBackgroundTaskIdentifier:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier -{ - AssertIsOnMainThread(); - - // Should only be sent once per operation - OWSAssert(!CurrentAppContext().isMainApp || _backgroundTaskIdentifier == UIBackgroundTaskInvalid); - OWSAssert(!CurrentAppContext().isMainApp || backgroundTaskIdentifier != UIBackgroundTaskInvalid); - - _backgroundTaskIdentifier = backgroundTaskIdentifier; -} - #pragma mark - NSOperation overrides - (BOOL)isExecuting @@ -266,11 +226,6 @@ 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(!CurrentAppContext().isMainApp || self.backgroundTaskIdentifier != UIBackgroundTaskInvalid); - [self willChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; self.operationState = OWSSendMessageOperationStateExecuting; [self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; @@ -343,8 +298,6 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4; [self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting]; [self didChangeValueForKey:OWSSendMessageOperationKeyIsFinished]; - - [self endBackgroundTask]; } @end @@ -456,17 +409,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; success:successHandler failure:failureHandler]; - // startBackgroundTask must be called on the main thread. - dispatch_async(dispatch_get_main_queue(), ^{ - // 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]; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message]; - [sendingQueue addOperation:sendMessageOperation]; - }); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message]; + [sendingQueue addOperation:sendMessageOperation]; }); }); } diff --git a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m index 4af0583d2..384a4c11f 100644 --- a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m +++ b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m @@ -73,7 +73,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ @property (nonatomic) NSTimer *backgroundKeepAliveTimer; // This is used to manage the iOS "background task" used to // keep the app alive in the background. -@property (nonatomic) UIBackgroundTaskIdentifier fetchingTaskIdentifier; +@property (nonatomic) OWSBackgroundTask *backgroundTask; // We cache this value instead of consulting [UIApplication sharedApplication].applicationState, // because UIKit only provides a "will resign active" notification, not a "did resign active" @@ -101,7 +101,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ _signalService = [OWSSignalService sharedInstance]; _messageReceiver = [OWSMessageReceiver sharedInstance]; _state = SocketManagerStateClosed; - _fetchingTaskIdentifier = UIBackgroundTaskInvalid; OWSSingletonAssert(); @@ -382,7 +381,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ if ([message.path isEqualToString:@"/api/v1/message"] && [message.verb isEqualToString:@"PUT"]) { - __block OWSBackgroundTask *backgroundTask = [[OWSBackgroundTask alloc] initWithLabelStr:__PRETTY_FUNCTION__]; + __block OWSBackgroundTask *backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -518,7 +517,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ return YES; } else if (self.backgroundKeepAliveUntilDate && [self.backgroundKeepAliveUntilDate timeIntervalSinceNow] > 0.f) { OWSAssert(self.backgroundKeepAliveTimer); - OWSAssert(self.fetchingTaskIdentifier != UIBackgroundTaskInvalid); // If app is doing any work in the background, keep web socket alive. return YES; } else { @@ -537,7 +535,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ } else if (!self.backgroundKeepAliveUntilDate) { OWSAssert(!self.backgroundKeepAliveUntilDate); OWSAssert(!self.backgroundKeepAliveTimer); - OWSAssert(self.fetchingTaskIdentifier == UIBackgroundTaskInvalid); DDLogInfo(@"%s activating socket in the background", __PRETTY_FUNCTION__); @@ -556,19 +553,25 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ // Additionally, we want the reconnect timer to work in the background too. [[NSRunLoop mainRunLoop] addTimer:self.backgroundKeepAliveTimer forMode:NSDefaultRunLoopMode]; - self.fetchingTaskIdentifier = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ - OWSAssert([NSThread isMainThread]); - - DDLogInfo(@"%s background task expired", __PRETTY_FUNCTION__); - - [self clearBackgroundState]; - [self applyDesiredSocketState]; - }]; + __weak typeof(self) weakSelf = self; + self.backgroundTask = + [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__ + completionBlock:^(BackgroundTaskState backgroundTaskState) { + OWSAssert([NSThread isMainThread]); + __strong typeof(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + if (backgroundTaskState == BackgroundTaskState_Expired) { + [strongSelf clearBackgroundState]; + } + [strongSelf applyDesiredSocketState]; + }]; } else { OWSAssert(self.backgroundKeepAliveUntilDate); OWSAssert(self.backgroundKeepAliveTimer); OWSAssert([self.backgroundKeepAliveTimer isValid]); - OWSAssert(self.fetchingTaskIdentifier != UIBackgroundTaskInvalid); if ([self.backgroundKeepAliveUntilDate timeIntervalSinceNow] < durationSeconds) { // Update state used to keep socket alive in background. @@ -628,11 +631,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ self.backgroundKeepAliveUntilDate = nil; [self.backgroundKeepAliveTimer invalidate]; self.backgroundKeepAliveTimer = nil; - - if (self.fetchingTaskIdentifier != UIBackgroundTaskInvalid) { - [CurrentAppContext() endBackgroundTask:self.fetchingTaskIdentifier]; - self.fetchingTaskIdentifier = UIBackgroundTaskInvalid; - } + self.backgroundTask = nil; } #pragma mark - Reconnect diff --git a/SignalServiceKit/src/Util/OWSAnalytics.m b/SignalServiceKit/src/Util/OWSAnalytics.m index 28ed30e25..454ad5bad 100755 --- a/SignalServiceKit/src/Util/OWSAnalytics.m +++ b/SignalServiceKit/src/Util/OWSAnalytics.m @@ -4,6 +4,7 @@ #import "OWSAnalytics.h" #import "AppContext.h" +#import "OWSBackgroundTask.h" #import "OWSQueues.h" #import "TSStorageManager.h" #import @@ -231,22 +232,19 @@ NSString *NSStringForOWSAnalyticsSeverity(OWSAnalyticsSeverity severity) DDLogDebug(@"%@ submitting: %@", self.logTag, eventKey); - __block UIBackgroundTaskIdentifier task; - task = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ - failureBlock(); - - [CurrentAppContext() endBackgroundTask:task]; - }]; + __block OWSBackgroundTask *backgroundTask = + [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__ + completionBlock:^(BackgroundTaskState backgroundTaskState) { + if (backgroundTaskState == BackgroundTaskState_Success) { + successBlock(); + } else { + failureBlock(); + } + }]; // Until we integrate with an analytics platform, behave as though all event delivery succeeds. dispatch_async(self.serialQueue, ^{ - BOOL success = YES; - if (success) { - successBlock(); - } else { - failureBlock(); - } - [CurrentAppContext() endBackgroundTask:task]; + backgroundTask = nil; }); } diff --git a/SignalServiceKit/src/Util/OWSBackgroundTask.h b/SignalServiceKit/src/Util/OWSBackgroundTask.h index 69c278cb8..dfb8b6dee 100644 --- a/SignalServiceKit/src/Util/OWSBackgroundTask.h +++ b/SignalServiceKit/src/Util/OWSBackgroundTask.h @@ -2,9 +2,28 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +typedef NS_ENUM(NSUInteger, BackgroundTaskState) { + BackgroundTaskState_Success, + BackgroundTaskState_CouldNotStart, + BackgroundTaskState_Expired, +}; + +typedef void (^BackgroundTaskCompletionBlock)(BackgroundTaskState backgroundTaskState); + @interface OWSBackgroundTask : NSObject - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithLabelStr:(const char *)labelStr; + ++ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr; + +// completionBlock will be called exactly once on the main thread. ++ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr + completionBlock:(BackgroundTaskCompletionBlock)completionBlock; + ++ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label; + +// completionBlock will be called exactly once on the main thread. ++ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label + completionBlock:(BackgroundTaskCompletionBlock)completionBlock; @end diff --git a/SignalServiceKit/src/Util/OWSBackgroundTask.m b/SignalServiceKit/src/Util/OWSBackgroundTask.m index c6a08ed4d..a310910a4 100644 --- a/SignalServiceKit/src/Util/OWSBackgroundTask.m +++ b/SignalServiceKit/src/Util/OWSBackgroundTask.m @@ -7,16 +7,49 @@ @interface OWSBackgroundTask () -@property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; @property (nonatomic, readonly) NSString *label; +// This property should only be accessed while synchronized on this instance. +@property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; + +@property (nonatomic, nullable) BackgroundTaskCompletionBlock completionBlock; + @end #pragma mark - @implementation OWSBackgroundTask -- (instancetype)initWithLabelStr:(const char *)labelStr ++ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr +{ + OWSAssert(labelStr); + + NSString *label = [NSString stringWithFormat:@"%s", labelStr]; + return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:nil]; +} + ++ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr + completionBlock:(BackgroundTaskCompletionBlock)completionBlock +{ + + OWSAssert(labelStr); + + NSString *label = [NSString stringWithFormat:@"%s", labelStr]; + return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:completionBlock]; +} + ++ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label +{ + return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:nil]; +} + ++ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label + completionBlock:(BackgroundTaskCompletionBlock)completionBlock +{ + return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:completionBlock]; +} + +- (instancetype)initWithLabel:(NSString *)label completionBlock:(BackgroundTaskCompletionBlock _Nullable)completionBlock { self = [super init]; @@ -24,9 +57,10 @@ return self; } - OWSAssert(labelStr); + OWSAssert(label.length > 0); - _label = [NSString stringWithFormat:@"%s", labelStr]; + _label = label; + self.completionBlock = completionBlock; [self startBackgroundTask]; @@ -40,10 +74,9 @@ - (void)startBackgroundTask { - @synchronized(self) - { - __weak typeof(self) weakSelf = self; - + __weak typeof(self) weakSelf = self; + // beginBackgroundTaskWithExpirationHandler must be called on the main thread. + dispatch_async(dispatch_get_main_queue(), ^{ self.backgroundTaskId = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ OWSAssert([NSThread isMainThread]); __strong typeof(self) strongSelf = weakSelf; @@ -57,27 +90,55 @@ } DDLogInfo(@"%@ %@ background task expired", strongSelf.logTag, strongSelf.label); strongSelf.backgroundTaskId = UIBackgroundTaskInvalid; + + if (strongSelf.completionBlock) { + strongSelf.completionBlock(BackgroundTaskState_Expired); + strongSelf.completionBlock = nil; + } } }]; - } + + // If a background task could not be begun, call the completion block. + if (self.backgroundTaskId == UIBackgroundTaskInvalid) { + BackgroundTaskCompletionBlock _Nullable completionBlock; + @synchronized(self) + { + completionBlock = self.completionBlock; + self.completionBlock = nil; + } + if (completionBlock) { + dispatch_async(dispatch_get_main_queue(), ^{ + completionBlock(BackgroundTaskState_CouldNotStart); + }); + } + } + }); } - (void)endBackgroundTask { - __weak typeof(self) weakSelf = self; + // Make a local copy of this state, since this method is called by `dealloc`. + UIBackgroundTaskIdentifier backgroundTaskId; + NSString *logTag = self.logTag; + NSString *label = self.label; + BackgroundTaskCompletionBlock _Nullable completionBlock = self.completionBlock; + + @synchronized(self) + { + backgroundTaskId = self.backgroundTaskId; + } + + if (backgroundTaskId == UIBackgroundTaskInvalid) { + return; + } + + // endBackgroundTask must be called on the main thread. dispatch_async(dispatch_get_main_queue(), ^{ - __strong typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - @synchronized(strongSelf) - { - if (strongSelf.backgroundTaskId == UIBackgroundTaskInvalid) { - return; - } - DDLogInfo(@"%@ %@ background task completed", strongSelf.logTag, strongSelf.label); - [CurrentAppContext() endBackgroundTask:strongSelf.backgroundTaskId]; - strongSelf.backgroundTaskId = UIBackgroundTaskInvalid; + DDLogInfo(@"%@ %@ background task completed", logTag, label); + [CurrentAppContext() endBackgroundTask:backgroundTaskId]; + + if (completionBlock) { + completionBlock(BackgroundTaskState_Success); } }); }