From d80804ca5a3b60d332436312aec35e19c18d428b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Sun, 8 Nov 2020 13:12:38 +1100 Subject: [PATCH] Create JobQueue --- .../Jobs/AttachmentDownloadJob.swift | 12 +++- .../Jobs/AttachmentUploadJob.swift | 12 +++- SessionMessagingKit/Jobs/Job.swift | 9 ++- SessionMessagingKit/Jobs/JobDelegate.swift | 6 ++ SessionMessagingKit/Jobs/JobQueue.swift | 21 ++++++ .../Jobs/MessageReceiveJob.swift | 9 +-- SessionMessagingKit/Jobs/MessageSendJob.swift | 9 +-- .../Jobs/NotifyPNServerJob.swift | 11 ++-- .../Sending & Receiving/MessageSender.swift | 5 +- SessionMessagingKit/Storage.swift | 2 +- SessionUtilities/Meta/SessionUtilities.h | 1 + SessionUtilities/NSTimer+Proxying.h | 16 +++++ SessionUtilities/NSTimer+Proxying.m | 64 +++++++++++++++++++ Signal.xcodeproj/project.pbxproj | 16 +++++ SignalServiceKit/src/Util/NSTimer+OWS.m | 6 +- 15 files changed, 173 insertions(+), 26 deletions(-) create mode 100644 SessionMessagingKit/Jobs/JobDelegate.swift create mode 100644 SessionMessagingKit/Jobs/JobQueue.swift create mode 100644 SessionUtilities/NSTimer+Proxying.h create mode 100644 SessionUtilities/NSTimer+Proxying.m diff --git a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift index 6b0e041a1..f5d1b6f64 100644 --- a/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentDownloadJob.swift @@ -3,9 +3,11 @@ import SessionUtilities // TODO: Implementation public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility + public var delegate: JobDelegate? + public var failureCount: UInt = 0 // MARK: Settings - private static let maxRetryCount: UInt = 20 + public static let maxFailureCount: UInt = 20 // MARK: Coding public init?(coder: NSCoder) { } @@ -15,8 +17,12 @@ public final class AttachmentDownloadJob : NSObject, Job, NSCoding { // NSObjec // MARK: Running public func execute() { } - private func handleSuccess() { } + private func handleSuccess() { + delegate?.handleJobSucceeded(self) + } - private func handleFailure(error: Error) { } + private func handleFailure(error: Error) { + delegate?.handleJobFailed(self, with: error) + } } diff --git a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift index a1f9a2c4d..ae185d8ca 100644 --- a/SessionMessagingKit/Jobs/AttachmentUploadJob.swift +++ b/SessionMessagingKit/Jobs/AttachmentUploadJob.swift @@ -3,9 +3,11 @@ import SessionUtilities // TODO: Implementation public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility + public var delegate: JobDelegate? + public var failureCount: UInt = 0 // MARK: Settings - private static let maxRetryCount: UInt = 20 + public static let maxFailureCount: UInt = 20 // MARK: Coding public init?(coder: NSCoder) { } @@ -15,8 +17,12 @@ public final class AttachmentUploadJob : NSObject, Job, NSCoding { // NSObject/ // MARK: Running public func execute() { } - private func handleSuccess() { } + private func handleSuccess() { + delegate?.handleJobSucceeded(self) + } - private func handleFailure(error: Error) { } + private func handleFailure(error: Error) { + delegate?.handleJobFailed(self, with: error) + } } diff --git a/SessionMessagingKit/Jobs/Job.swift b/SessionMessagingKit/Jobs/Job.swift index ebede2ecc..a6cce33ed 100644 --- a/SessionMessagingKit/Jobs/Job.swift +++ b/SessionMessagingKit/Jobs/Job.swift @@ -1,2 +1,9 @@ -public protocol Job { } +public protocol Job : class { + var delegate: JobDelegate? { get set } + var failureCount: UInt { get set } + + static var maxFailureCount: UInt { get } + + func execute() +} diff --git a/SessionMessagingKit/Jobs/JobDelegate.swift b/SessionMessagingKit/Jobs/JobDelegate.swift new file mode 100644 index 000000000..db6eadfc4 --- /dev/null +++ b/SessionMessagingKit/Jobs/JobDelegate.swift @@ -0,0 +1,6 @@ + +public protocol JobDelegate { + + func handleJobSucceeded(_ job: Job) + func handleJobFailed(_ job: Job, with error: Error) +} diff --git a/SessionMessagingKit/Jobs/JobQueue.swift b/SessionMessagingKit/Jobs/JobQueue.swift new file mode 100644 index 000000000..223b94741 --- /dev/null +++ b/SessionMessagingKit/Jobs/JobQueue.swift @@ -0,0 +1,21 @@ + +public final class JobQueue : JobDelegate { + + public static let shared = JobQueue() + + public func add(_ job: Job, using transaction: Any) { + Configuration.shared.storage.persist(job, using: transaction) + job.delegate = self + job.execute() + } + + public func handleJobSucceeded(_ job: Job) { + // Mark the job as succeeded + } + + public func handleJobFailed(_ job: Job, with error: Error) { + // Persist the job + // Retry it if the max failure count hasn't been reached + // Propagate the error otherwise + } +} diff --git a/SessionMessagingKit/Jobs/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/MessageReceiveJob.swift index 477d29558..981c8d9a4 100644 --- a/SessionMessagingKit/Jobs/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/MessageReceiveJob.swift @@ -4,16 +4,16 @@ import SessionUtilities // TODO: Retrying public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility + public var delegate: JobDelegate? private let data: Data - private var failureCount: UInt + public var failureCount: UInt = 0 // MARK: Settings - private static let maxRetryCount: UInt = 20 + public static let maxFailureCount: UInt = 20 // MARK: Initialization init(data: Data) { self.data = data - self.failureCount = 0 } // MARK: Coding @@ -44,11 +44,12 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NS } private func handleSuccess() { - + delegate?.handleJobSucceeded(self) } private func handleFailure(error: Error) { self.failureCount += 1 + delegate?.handleJobFailed(self, with: error) } } diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 7bf090140..ac2d15743 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -5,18 +5,18 @@ import SessionUtilities // TODO: Retrying public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility + public var delegate: JobDelegate? private let message: Message private let destination: Message.Destination - private var failureCount: UInt + public var failureCount: UInt = 0 // MARK: Settings - private static let maxRetryCount: UInt = 20 + public static let maxFailureCount: UInt = 20 // MARK: Initialization init(message: Message, destination: Message.Destination) { self.message = message self.destination = destination - self.failureCount = 0 } // MARK: Coding @@ -49,11 +49,12 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi } private func handleSuccess() { - + delegate?.handleJobSucceeded(self) } private func handleFailure(error: Error) { self.failureCount += 1 + delegate?.handleJobFailed(self, with: error) } } diff --git a/SessionMessagingKit/Jobs/NotifyPNServerJob.swift b/SessionMessagingKit/Jobs/NotifyPNServerJob.swift index 7924f368a..b03c18a06 100644 --- a/SessionMessagingKit/Jobs/NotifyPNServerJob.swift +++ b/SessionMessagingKit/Jobs/NotifyPNServerJob.swift @@ -7,16 +7,16 @@ import SessionUtilities // TODO: Retrying public final class NotifyPNServerJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility + public var delegate: JobDelegate? private let message: SnodeMessage - private var failureCount: UInt + public var failureCount: UInt = 0 // MARK: Settings - private static let maxRetryCount: UInt = 20 + public static let maxFailureCount: UInt = 20 // MARK: Initialization init(message: SnodeMessage) { self.message = message - self.failureCount = 0 } // MARK: Coding @@ -33,15 +33,16 @@ public final class NotifyPNServerJob : NSObject, Job, NSCoding { // NSObject/NSC // MARK: Running public func execute() { - + } private func handleSuccess() { - + delegate?.handleJobSucceeded(self) } private func handleFailure(error: Error) { self.failureCount += 1 + delegate?.handleJobFailed(self, with: error) } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index d442259f6..3b44a8793 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -98,8 +98,9 @@ internal enum MessageSender { NotificationCenter.default.post(name: .messageSent, object: NSNumber(value: message.sentTimestamp!)) } let notifyPNServerJob = NotifyPNServerJob(message: snodeMessage) - Configuration.shared.storage.persist(notifyPNServerJob) - notifyPNServerJob.execute() + Configuration.shared.storage.with { transaction in + JobQueue.shared.add(notifyPNServerJob, using: transaction) + } } let _ = promise.catch(on: DispatchQueue.main) { _ in if case .contact(_) = destination { diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 8d1f07e70..d10c879c2 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -8,5 +8,5 @@ public protocol SessionMessagingKitStorageProtocol { func getOrGenerateRegistrationID(using transaction: Any) -> UInt32 func isClosedGroup(_ publicKey: String) -> Bool func getClosedGroupPrivateKey(for publicKey: String) -> String? - func persist(_ job: Job) + func persist(_ job: Job, using transaction: Any) } diff --git a/SessionUtilities/Meta/SessionUtilities.h b/SessionUtilities/Meta/SessionUtilities.h index 2c0a4f593..85f6aa875 100644 --- a/SessionUtilities/Meta/SessionUtilities.h +++ b/SessionUtilities/Meta/SessionUtilities.h @@ -4,4 +4,5 @@ FOUNDATION_EXPORT double SessionUtilitiesVersionNumber; FOUNDATION_EXPORT const unsigned char SessionUtilitiesVersionString[]; #import +#import #import diff --git a/SessionUtilities/NSTimer+Proxying.h b/SessionUtilities/NSTimer+Proxying.h new file mode 100644 index 000000000..c322d319f --- /dev/null +++ b/SessionUtilities/NSTimer+Proxying.h @@ -0,0 +1,16 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSTimer (Session) + +// This method avoids the classic NSTimer retain cycle bug by using a weak reference to the target ++ (NSTimer *)weakScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval + target:(id)target + selector:(SEL)selector + userInfo:(nullable id)userInfo + repeats:(BOOL)repeats; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SessionUtilities/NSTimer+Proxying.m b/SessionUtilities/NSTimer+Proxying.m new file mode 100644 index 000000000..d70d86ef8 --- /dev/null +++ b/SessionUtilities/NSTimer+Proxying.m @@ -0,0 +1,64 @@ +#import +#import "NSTimer+Proxying.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSTimerProxy : NSObject + +@property (nonatomic, weak) id target; +@property (nonatomic) SEL selector; + +@end + +@implementation NSTimerProxy + +- (void)timerFired:(NSDictionary *)userInfo +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.target performSelector:self.selector withObject:userInfo]; +#pragma clang diagnostic pop +} + +@end + +static void *kNSTimer_SN_Proxy = &kNSTimer_SN_Proxy; + +@implementation NSTimer (Session) + +- (NSTimerProxy *)sn_proxy +{ + return objc_getAssociatedObject(self, kNSTimer_SN_Proxy); +} + +- (void)sn_setProxy:(NSTimerProxy *)proxy +{ + #if DEBUG + assert(proxy != nil); + #endif + + objc_setAssociatedObject(self, kNSTimer_SN_Proxy, proxy, OBJC_ASSOCIATION_RETAIN); +} + ++ (NSTimer *)weakScheduledTimerWithTimeInterval:(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 scheduledTimerWithTimeInterval:timeInterval + target:proxy + selector:@selector(timerFired:) + userInfo:userInfo + repeats:repeats]; + [timer sn_setProxy:proxy]; + return timer; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 6ad4584a0..404d6a852 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -603,6 +603,10 @@ C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A32E2557549C00338F3E /* NotifyPNServerJob.swift */; }; C352A349255781F400338F3E /* AttachmentDownloadJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A348255781F400338F3E /* AttachmentDownloadJob.swift */; }; C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A35A2557824E00338F3E /* AttachmentUploadJob.swift */; }; + C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */ = {isa = PBXBuildFile; fileRef = C352A36C2557858D00338F3E /* NSTimer+Proxying.m */; }; + C352A3772557864000338F3E /* NSTimer+Proxying.h in Headers */ = {isa = PBXBuildFile; fileRef = C352A3762557859C00338F3E /* NSTimer+Proxying.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C352A3892557876500338F3E /* JobQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A3882557876500338F3E /* JobQueue.swift */; }; + C352A3932557883D00338F3E /* JobDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C352A3922557883D00338F3E /* JobDelegate.swift */; }; C353F8F9244809150011121A /* PNOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F8244809150011121A /* PNOptionView.swift */; }; C3548F0624456447009433A8 /* PNModeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0524456447009433A8 /* PNModeVC.swift */; }; C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */; }; @@ -1609,6 +1613,10 @@ C352A32E2557549C00338F3E /* NotifyPNServerJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPNServerJob.swift; sourceTree = ""; }; C352A348255781F400338F3E /* AttachmentDownloadJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentDownloadJob.swift; sourceTree = ""; }; C352A35A2557824E00338F3E /* AttachmentUploadJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentUploadJob.swift; sourceTree = ""; }; + C352A36C2557858D00338F3E /* NSTimer+Proxying.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+Proxying.m"; sourceTree = ""; }; + C352A3762557859C00338F3E /* NSTimer+Proxying.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSTimer+Proxying.h"; sourceTree = ""; }; + C352A3882557876500338F3E /* JobQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobQueue.swift; sourceTree = ""; }; + C352A3922557883D00338F3E /* JobDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobDelegate.swift; sourceTree = ""; }; C353F8F8244809150011121A /* PNOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNOptionView.swift; sourceTree = ""; }; C3548F0524456447009433A8 /* PNModeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeVC.swift; sourceTree = ""; }; C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Wrapping.swift"; sourceTree = ""; }; @@ -3250,6 +3258,8 @@ isa = PBXGroup; children = ( C352A2F425574B4700338F3E /* Job.swift */, + C352A3922557883D00338F3E /* JobDelegate.swift */, + C352A3882557876500338F3E /* JobQueue.swift */, C352A348255781F400338F3E /* AttachmentDownloadJob.swift */, C352A35A2557824E00338F3E /* AttachmentUploadJob.swift */, C352A31225574F5200338F3E /* MessageReceiveJob.swift */, @@ -3354,6 +3364,8 @@ C3C2A5CE2553860700C340D1 /* Logging.swift */, C300A6302554B68200555489 /* NSDate+Timestamp.h */, C300A6312554B6D100555489 /* NSDate+Timestamp.mm */, + C352A3762557859C00338F3E /* NSTimer+Proxying.h */, + C352A36C2557858D00338F3E /* NSTimer+Proxying.m */, C3C2A5D62553860B00C340D1 /* Promise+Retrying.swift */, C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */, ); @@ -3810,6 +3822,7 @@ buildActionMask = 2147483647; files = ( C300A63B2554B72200555489 /* NSDate+Timestamp.h in Headers */, + C352A3772557864000338F3E /* NSTimer+Proxying.h in Headers */, C3471F5625553E1100297E91 /* ECKeyPair+Utilities.h in Headers */, C3C2A67D255388CC00C340D1 /* SessionUtilities.h in Headers */, ); @@ -4920,6 +4933,7 @@ files = ( C3AABDDF2553ECF00042FF4C /* Array+Description.swift in Sources */, C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */, + C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, C3471F6825553E7600297E91 /* ECKeyPair+Utilities.m in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Description.swift in Sources */, @@ -4942,9 +4956,11 @@ C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, + C352A3932557883D00338F3E /* JobDelegate.swift in Sources */, C352A31325574F5200338F3E /* MessageReceiveJob.swift in Sources */, C3C2A7562553A3AB00C340D1 /* VisibleMessage+Quote.swift in Sources */, C300A5FC2554B0A000555489 /* MessageReceiver.swift in Sources */, + C352A3892557876500338F3E /* JobQueue.swift in Sources */, C3BBE0B52554F0E10050F1E3 /* ProofOfWork.swift in Sources */, C3BBE0802554CDD70050F1E3 /* Storage.swift in Sources */, C3C2A7842553AAF300C340D1 /* SNProto.swift in Sources */, diff --git a/SignalServiceKit/src/Util/NSTimer+OWS.m b/SignalServiceKit/src/Util/NSTimer+OWS.m index 12eb39175..ee295a90a 100644 --- a/SignalServiceKit/src/Util/NSTimer+OWS.m +++ b/SignalServiceKit/src/Util/NSTimer+OWS.m @@ -34,12 +34,12 @@ static void *kNSTimer_OWS_Proxy = &kNSTimer_OWS_Proxy; @implementation NSTimer (OWS) -- (NSTimerProxy *)ows_proxy +- (NSTimerProxy *)sn_proxy { return objc_getAssociatedObject(self, kNSTimer_OWS_Proxy); } -- (void)ows_setProxy:(NSTimerProxy *)proxy +- (void)sn_setProxy:(NSTimerProxy *)proxy { OWSAssertDebug(proxy); @@ -60,7 +60,7 @@ static void *kNSTimer_OWS_Proxy = &kNSTimer_OWS_Proxy; selector:@selector(timerFired:) userInfo:userInfo repeats:repeats]; - [timer ows_setProxy:proxy]; + [timer sn_setProxy:proxy]; return timer; }