Improve background task logic.

This commit is contained in:
Matthew Chen 2017-12-15 13:03:03 -05:00
parent 5adf98788d
commit f9ce34f553
11 changed files with 161 additions and 130 deletions

View File

@ -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;
}
});

View File

@ -73,6 +73,7 @@
#import <SignalServiceKit/OWSAnalytics.h>
#import <SignalServiceKit/OWSAnalyticsEvents.h>
#import <SignalServiceKit/OWSAttachmentsProcessor.h>
#import <SignalServiceKit/OWSBackgroundTask.h>
#import <SignalServiceKit/OWSCallAnswerMessage.h>
#import <SignalServiceKit/OWSCallBusyMessage.h>
#import <SignalServiceKit/OWSCallHangupMessage.h>

View File

@ -584,16 +584,19 @@ protocol CallServiceObserver: class {
self.call = newCall
let backgroundTask = UIApplication.shared.beginBackgroundTask {
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)
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.")
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()
}

View File

@ -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];

View File

@ -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];

View File

@ -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) {

View File

@ -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];
});
});
}

View File

@ -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]);
__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;
}
DDLogInfo(@"%s background task expired", __PRETTY_FUNCTION__);
[self clearBackgroundState];
[self applyDesiredSocketState];
}];
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

View File

@ -4,6 +4,7 @@
#import "OWSAnalytics.h"
#import "AppContext.h"
#import "OWSBackgroundTask.h"
#import "OWSQueues.h"
#import "TSStorageManager.h"
#import <CocoaLumberjack/CocoaLumberjack.h>
@ -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;
});
}

View File

@ -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

View File

@ -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);
}
});
}