|
|
@ -37,7 +37,7 @@ public protocol DurableOperation: class {
|
|
|
|
|
|
|
|
|
|
|
|
var jobRecord: JobRecordType { get }
|
|
|
|
var jobRecord: JobRecordType { get }
|
|
|
|
var durableOperationDelegate: DurableOperationDelegateType? { get set }
|
|
|
|
var durableOperationDelegate: DurableOperationDelegateType? { get set }
|
|
|
|
var operation: Operation { get }
|
|
|
|
var operation: OWSOperation { get }
|
|
|
|
var remainingRetries: UInt { get set }
|
|
|
|
var remainingRetries: UInt { get set }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -67,15 +67,22 @@ public protocol JobQueue: DurableOperationDelegate {
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: Required
|
|
|
|
// MARK: Required
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var runningOperations: [DurableOperationType] { get set }
|
|
|
|
var jobRecordLabel: String { get }
|
|
|
|
var jobRecordLabel: String { get }
|
|
|
|
|
|
|
|
|
|
|
|
var isReady: Bool { get set }
|
|
|
|
var isSetup: Bool { get set }
|
|
|
|
func setup()
|
|
|
|
func setup()
|
|
|
|
func didMarkAsReady(oldJobRecord: JobRecordType, transaction: YapDatabaseReadWriteTransaction)
|
|
|
|
func didMarkAsReady(oldJobRecord: JobRecordType, transaction: YapDatabaseReadWriteTransaction)
|
|
|
|
|
|
|
|
|
|
|
|
func operationQueue(jobRecord: JobRecordType) -> OperationQueue
|
|
|
|
func operationQueue(jobRecord: JobRecordType) -> OperationQueue
|
|
|
|
func buildOperation(jobRecord: JobRecordType, transaction: YapDatabaseReadTransaction) throws -> DurableOperationType
|
|
|
|
func buildOperation(jobRecord: JobRecordType, transaction: YapDatabaseReadTransaction) throws -> DurableOperationType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// When `requiresInternet` is true, we immediately run any jobs which are waiting for retry upon detecting Reachability.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// Because `Reachability` isn't 100% reliable, the jobs will be attempted regardless of what we think our current Reachability is.
|
|
|
|
|
|
|
|
/// However, because these jobs will likely fail many times in succession, their `retryInterval` could be quite long by the time we
|
|
|
|
|
|
|
|
/// are back online.
|
|
|
|
|
|
|
|
var requiresInternet: Bool { get }
|
|
|
|
static var maxRetries: UInt { get }
|
|
|
|
static var maxRetries: UInt { get }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -91,6 +98,10 @@ public extension JobQueue {
|
|
|
|
return JobRecordFinder()
|
|
|
|
return JobRecordFinder()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var reachabilityManager: SSKReachabilityManager {
|
|
|
|
|
|
|
|
return SSKEnvironment.shared.reachabilityManager
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MARK:
|
|
|
|
// MARK:
|
|
|
|
|
|
|
|
|
|
|
|
func add(jobRecord: JobRecordType, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
func add(jobRecord: JobRecordType, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
@ -105,12 +116,11 @@ public extension JobQueue {
|
|
|
|
func workStep() {
|
|
|
|
func workStep() {
|
|
|
|
Logger.debug("")
|
|
|
|
Logger.debug("")
|
|
|
|
|
|
|
|
|
|
|
|
guard isReady else {
|
|
|
|
guard isSetup else {
|
|
|
|
if !CurrentAppContext().isRunningTests {
|
|
|
|
if !CurrentAppContext().isRunningTests {
|
|
|
|
owsFailDebug("not ready")
|
|
|
|
owsFailDebug("not setup")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Logger.error("not ready")
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -132,6 +142,8 @@ public extension JobQueue {
|
|
|
|
let remainingRetries = self.remainingRetries(durableOperation: durableOperation)
|
|
|
|
let remainingRetries = self.remainingRetries(durableOperation: durableOperation)
|
|
|
|
durableOperation.remainingRetries = remainingRetries
|
|
|
|
durableOperation.remainingRetries = remainingRetries
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.runningOperations.append(durableOperation)
|
|
|
|
|
|
|
|
|
|
|
|
Logger.debug("adding operation: \(durableOperation) with remainingRetries: \(remainingRetries)")
|
|
|
|
Logger.debug("adding operation: \(durableOperation) with remainingRetries: \(remainingRetries)")
|
|
|
|
operationQueue.addOperation(durableOperation.operation)
|
|
|
|
operationQueue.addOperation(durableOperation.operation)
|
|
|
|
} catch JobError.assertionFailure(let description) {
|
|
|
|
} catch JobError.assertionFailure(let description) {
|
|
|
@ -181,13 +193,31 @@ public extension JobQueue {
|
|
|
|
/// `setup` is called from objc, and default implementations from a protocol
|
|
|
|
/// `setup` is called from objc, and default implementations from a protocol
|
|
|
|
/// cannot be marked as @objc.
|
|
|
|
/// cannot be marked as @objc.
|
|
|
|
func defaultSetup() {
|
|
|
|
func defaultSetup() {
|
|
|
|
guard !isReady else {
|
|
|
|
guard !isSetup else {
|
|
|
|
owsFailDebug("already ready already")
|
|
|
|
owsFailDebug("already ready already")
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.restartOldJobs()
|
|
|
|
self.restartOldJobs()
|
|
|
|
|
|
|
|
|
|
|
|
self.isReady = true
|
|
|
|
if self.requiresInternet {
|
|
|
|
|
|
|
|
NotificationCenter.default.addObserver(forName: .reachabilityChanged,
|
|
|
|
|
|
|
|
object: self.reachabilityManager.observationContext,
|
|
|
|
|
|
|
|
queue: nil) { _ in
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.reachabilityManager.isReachable {
|
|
|
|
|
|
|
|
Logger.verbose("isReachable: true")
|
|
|
|
|
|
|
|
self.becameReachable()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
Logger.verbose("isReachable: false")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.isSetup = true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DispatchQueue.global().async {
|
|
|
|
|
|
|
|
self.workStep()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func remainingRetries(durableOperation: DurableOperationType) -> UInt {
|
|
|
|
func remainingRetries(durableOperation: DurableOperationType) -> UInt {
|
|
|
@ -201,9 +231,18 @@ public extension JobQueue {
|
|
|
|
return maxRetries - failureCount
|
|
|
|
return maxRetries - failureCount
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func becameReachable() {
|
|
|
|
|
|
|
|
guard requiresInternet else {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.runningOperations.first?.operation.runAnyQueuedRetry()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: DurableOperationDelegate
|
|
|
|
// MARK: DurableOperationDelegate
|
|
|
|
|
|
|
|
|
|
|
|
func durableOperationDidSucceed(_ operation: DurableOperationType, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
func durableOperationDidSucceed(_ operation: DurableOperationType, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
|
|
|
self.runningOperations = self.runningOperations.filter { $0 !== operation }
|
|
|
|
operation.jobRecord.remove(with: transaction)
|
|
|
|
operation.jobRecord.remove(with: transaction)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -217,6 +256,7 @@ public extension JobQueue {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func durableOperation(_ operation: DurableOperationType, didFailWithError error: Error, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
func durableOperation(_ operation: DurableOperationType, didFailWithError error: Error, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
|
|
|
self.runningOperations = self.runningOperations.filter { $0 !== operation }
|
|
|
|
operation.jobRecord.saveAsPermanentlyFailed(transaction: transaction)
|
|
|
|
operation.jobRecord.saveAsPermanentlyFailed(transaction: transaction)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|