diff --git a/SignalServiceKit/src/Util/NSTimer+OWS.h b/SignalServiceKit/src/Util/NSTimer+OWS.h index e301b39b0..c530f209e 100644 --- a/SignalServiceKit/src/Util/NSTimer+OWS.h +++ b/SignalServiceKit/src/Util/NSTimer+OWS.h @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // NS_ASSUME_NONNULL_BEGIN diff --git a/SignalServiceKit/src/Util/NSTimer+OWS.m b/SignalServiceKit/src/Util/NSTimer+OWS.m index a2f02f2a4..12eb39175 100644 --- a/SignalServiceKit/src/Util/NSTimer+OWS.m +++ b/SignalServiceKit/src/Util/NSTimer+OWS.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import "NSTimer+OWS.h" diff --git a/SignalServiceKit/src/Util/OWSOperation.m b/SignalServiceKit/src/Util/OWSOperation.m index e3455a5f7..444d9c886 100644 --- a/SignalServiceKit/src/Util/OWSOperation.m +++ b/SignalServiceKit/src/Util/OWSOperation.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import "OWSOperation.h" @@ -18,8 +18,9 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; @property (nullable) NSError *failingError; @property (atomic) OWSOperationState operationState; @property (nonatomic) OWSBackgroundTask *backgroundTask; + +// This property should only be accessed on the main queue. @property (nonatomic) NSTimer *_Nullable retryTimer; -@property (nonatomic, readonly) dispatch_queue_t retryTimerSerialQueue; @end @@ -34,7 +35,6 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; _operationState = OWSOperationStateNew; _backgroundTask = [OWSBackgroundTask backgroundTaskWithLabel:self.logTag]; - _retryTimerSerialQueue = dispatch_queue_create("SignalServiceKit.OWSOperation.retryTimer", DISPATCH_QUEUE_SERIAL); // Operations are not retryable by default. _remainingRetries = 0; @@ -131,18 +131,19 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; - (void)runAnyQueuedRetry { - __block NSTimer *_Nullable retryTimer; - dispatch_sync(self.retryTimerSerialQueue, ^{ - retryTimer = self.retryTimer; + dispatch_async(dispatch_get_main_queue(), ^{ + NSTimer *_Nullable retryTimer = self.retryTimer; self.retryTimer = nil; [retryTimer invalidate]; - }); - if (retryTimer != nil) { - [self run]; - } else { - OWSLogVerbose(@"not re-running since operation is already running."); - } + if (retryTimer != nil) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self run]; + }); + } else { + OWSLogVerbose(@"not re-running since operation is already running."); + } + }); } #pragma mark - Public Methods @@ -190,9 +191,17 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; self.remainingRetries--; - dispatch_sync(self.retryTimerSerialQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ OWSAssertDebug(self.retryTimer == nil); [self.retryTimer invalidate]; + + // The `scheduledTimerWith*` methods add the timer to the current thread's RunLoop. + // Since Operations typically run on a background thread, that would mean the background + // thread's RunLoop. However, the OS can spin down background threads if there's no work + // being done, so we run the risk of the timer's RunLoop being deallocated before it's + // fired. + // + // To ensure the timer's thread sticks around, we schedule it while on the main RunLoop. self.retryTimer = [NSTimer weakScheduledTimerWithTimeInterval:self.retryInterval target:self selector:@selector(runAnyQueuedRetry)