From 64cdaae02e382de63692fc1b17b823f07174e47c Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 17 Jan 2019 10:40:18 -0700 Subject: [PATCH] schedule retry timer on main run loop --- SignalServiceKit/src/Util/NSTimer+OWS.h | 8 +++- SignalServiceKit/src/Util/NSTimer+OWS.m | 20 +++++++++- SignalServiceKit/src/Util/OWSOperation.m | 47 +++++++++++++++--------- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/SignalServiceKit/src/Util/NSTimer+OWS.h b/SignalServiceKit/src/Util/NSTimer+OWS.h index e301b39b0..9aa7bebed 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 @@ -14,6 +14,12 @@ NS_ASSUME_NONNULL_BEGIN userInfo:(nullable id)userInfo repeats:(BOOL)repeats; ++ (NSTimer *)weakTimerWithTimeInterval:(NSTimeInterval)timeInterval + target:(id)target + selector:(SEL)selector + userInfo:(nullable id)userInfo + repeats:(BOOL)repeats; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSTimer+OWS.m b/SignalServiceKit/src/Util/NSTimer+OWS.m index a2f02f2a4..9b4ad739b 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" @@ -64,6 +64,24 @@ static void *kNSTimer_OWS_Proxy = &kNSTimer_OWS_Proxy; return timer; } ++ (NSTimer *)weakTimerWithTimeInterval:(NSTimeInterval)timeInterval + target:(id)target + selector:(SEL)selector + userInfo:(nullable id)userInfo + repeats:(BOOL)repeats +{ + NSTimerProxy *proxy = [NSTimerProxy new]; + proxy.target = target; + proxy.selector = selector; + NSTimer *timer = [NSTimer timerWithTimeInterval:timeInterval + target:proxy + selector:@selector(timerFired:) + userInfo:userInfo + repeats:repeats]; + [timer ows_setProxy:proxy]; + return timer; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSOperation.m b/SignalServiceKit/src/Util/OWSOperation.m index e3455a5f7..e0c0e65a5 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" @@ -131,18 +131,20 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; - (void)runAnyQueuedRetry { - __block NSTimer *_Nullable retryTimer; - dispatch_sync(self.retryTimerSerialQueue, ^{ - retryTimer = self.retryTimer; - self.retryTimer = nil; - [retryTimer invalidate]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + __block NSTimer *_Nullable retryTimer; + dispatch_sync(self.retryTimerSerialQueue, ^{ + 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) { - [self run]; - } else { - OWSLogVerbose(@"not re-running since operation is already running."); - } } #pragma mark - Public Methods @@ -193,11 +195,22 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; dispatch_sync(self.retryTimerSerialQueue, ^{ OWSAssertDebug(self.retryTimer == nil); [self.retryTimer invalidate]; - self.retryTimer = [NSTimer weakScheduledTimerWithTimeInterval:self.retryInterval - target:self - selector:@selector(runAnyQueuedRetry) - userInfo:nil - repeats:NO]; + NSTimer *retryTimer = [NSTimer weakTimerWithTimeInterval:self.retryInterval + target:self + selector:@selector(runAnyQueuedRetry) + userInfo:nil + repeats:NO]; + + self.retryTimer = retryTimer; + + // 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 on the main RunLoop. + [NSRunLoop.mainRunLoop addTimer:retryTimer forMode:NSDefaultRunLoopMode]; }); }