diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 47cc8d3c7d..7a5e18f2a5 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -926,8 +926,6 @@ 6612780D2996BD0300A1D5A1 /* RegistrationCoordinatorTestShims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661278092996BAB400A1D5A1 /* RegistrationCoordinatorTestShims.swift */; }; 66138FB6298326C7002E0CFE /* SecureValueRecovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66138FB5298326C7002E0CFE /* SecureValueRecovery.swift */; }; 661396AB28BD53EF00E0C4DF /* HiddenStoryHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661396AA28BD53EF00E0C4DF /* HiddenStoryHeaderCell.swift */; }; - 661396AD28BE74DC00E0C4DF /* ChainedPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661396AC28BE74DC00E0C4DF /* ChainedPromise.swift */; }; - 661396AF28BE881E00E0C4DF /* ChainedPromiseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661396AE28BE881E00E0C4DF /* ChainedPromiseTest.swift */; }; 661429192D35B9F10043AA22 /* BackupArchive+Timestamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661429182D35B9EA0043AA22 /* BackupArchive+Timestamp.swift */; }; 66144B2F2BF7FB5200E2C9CD /* WallpaperImageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66144B2E2BF7FB5200E2C9CD /* WallpaperImageStore.swift */; }; 66144B312BF7FB7B00E2C9CD /* WallpaperImageStoreImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66144B302BF7FB7B00E2C9CD /* WallpaperImageStoreImpl.swift */; }; @@ -5194,8 +5192,6 @@ 661278092996BAB400A1D5A1 /* RegistrationCoordinatorTestShims.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationCoordinatorTestShims.swift; sourceTree = ""; }; 66138FB5298326C7002E0CFE /* SecureValueRecovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureValueRecovery.swift; sourceTree = ""; }; 661396AA28BD53EF00E0C4DF /* HiddenStoryHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenStoryHeaderCell.swift; sourceTree = ""; }; - 661396AC28BE74DC00E0C4DF /* ChainedPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainedPromise.swift; sourceTree = ""; }; - 661396AE28BE881E00E0C4DF /* ChainedPromiseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainedPromiseTest.swift; sourceTree = ""; }; 661429182D35B9EA0043AA22 /* BackupArchive+Timestamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BackupArchive+Timestamp.swift"; sourceTree = ""; }; 66144B2E2BF7FB5200E2C9CD /* WallpaperImageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperImageStore.swift; sourceTree = ""; }; 66144B302BF7FB7B00E2C9CD /* WallpaperImageStoreImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperImageStoreImpl.swift; sourceTree = ""; }; @@ -10461,7 +10457,6 @@ isa = PBXGroup; children = ( 668A01172C2B6088007B8808 /* Catchable.swift */, - 661396AC28BE74DC00E0C4DF /* ChainedPromise.swift */, 668A01182C2B6088007B8808 /* DispatchQueue+Promise.swift */, 668A01192C2B6088007B8808 /* firstly.swift */, 668A011A2C2B6088007B8808 /* Future.swift */, @@ -14323,7 +14318,6 @@ F908C67A29F08E4E00C3EFC4 /* AppExpiryTest.swift */, 66B1E26F2CB48C48005F43AC /* Array+SSKTest.swift */, F9C612B3284E466B00B2199A /* CGPointExtensionsTest.swift */, - 661396AE28BE881E00E0C4DF /* ChainedPromiseTest.swift */, F962B38B293F9F9F00765BD8 /* CRC32Test.swift */, 509BBF7928CA556700F4D8A0 /* Data+SSKTest.swift */, 724E68632C91FA73002199F3 /* DataHexadecimalTest.swift */, @@ -19142,7 +19136,6 @@ F9C5CC0C289453B300548EEE /* CDNDownloadOperation.swift in Sources */, 727328072CA6CF570080E2C7 /* Certificates.swift in Sources */, 508F05A52FA28308004B96E5 /* CGDataProvider+SSK.swift in Sources */, - 661396AD28BE74DC00E0C4DF /* ChainedPromise.swift in Sources */, 50E5E4B32993352C00E15A1C /* ChangePhoneNumberPniManager.swift in Sources */, 6603AC2D29C220F30079BC82 /* ChangePhoneNumberPniManagerMock.swift in Sources */, 66F6D6A32C7D0CCA00EFAF75 /* ChatColors.swift in Sources */, @@ -20130,7 +20123,6 @@ 500AF3AD2C5834A400CB9F4F /* CancellableContinuationTest.swift in Sources */, 508622AD2D026F5200931BF9 /* CanonicalPhoneNumberTest.swift in Sources */, 725464F62B9FAC7600EABFD2 /* CGPointExtensionsTest.swift in Sources */, - 661396AF28BE881E00E0C4DF /* ChainedPromiseTest.swift in Sources */, D985D86829B94EC60087C90C /* ChangePhoneNumberPniManagerTest.swift in Sources */, F9C9610B29A91026001E4A09 /* ChatServiceAuthTest.swift in Sources */, 507529172F6494C2000F72F0 /* CipherContextTest.swift in Sources */, diff --git a/SignalServiceKit/PromiseKit/ChainedPromise.swift b/SignalServiceKit/PromiseKit/ChainedPromise.swift deleted file mode 100644 index 26039f8f4b..0000000000 --- a/SignalServiceKit/PromiseKit/ChainedPromise.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright 2022 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import Foundation - -/// Wraps a promise to execute promises sequentially in a thread-safe way. -/// -/// When a closure which returns a promise is enqueued on the `ChainedPromise`, -/// the closure itself will not be executed until all previously enqueued promises have -/// been resolved. (Note that failures are ignored) -/// In other words, you "enqueue" blocks of work whose results are represented by -/// promises, and guarantee that they are executed serially with FIFO. -/// -/// Enqueue calls are thread-safe. -/// -/// WARNING: for this reason, if an enqueued closure returns a promise that never resolves, -/// the entire chain will be left waiting forever. -/// -/// For example, consider the following sequence of calls: -/// ``` -/// let chainedPromise = ChainedPromise() -/// let createFilePromise = chainedPromise.enqueue { -/// self.createFileOnDisk() -/// } -/// let validateFilePromise = chainedPromise.enqueue { -/// self.validateFileOnDisk() -/// } -/// ``` -/// In this example, the call to `validateFileOnDisk` will not be executed until -/// the promise returned by `createFileOnDisk` is resolved (whether success or failure). -/// Anything enqueued afterwards will wait on the resolution of the promise returned by -/// `validateFileOnDisk`. -/// -public class ChainedPromise { - - private let scheduler: Scheduler - private var currentPromise: Promise - - /// Create a new ChainedPromise. - /// - /// Each ChainedPromise is independent; you typically create a single instance and enqueue multiple - /// blocks of work on it. - /// - /// - Parameter initialValue: the value that will be used as the "previous value" input given to the first enqueued block. - /// - Parameter scheduler: The scheduler to use to serialize all calls. Defaults to a new unique serial background queue. - public init(initialValue: Value, scheduler: Scheduler = DispatchQueue(label: UUID().uuidString)) { - self.scheduler = scheduler - self.currentPromise = .value(initialValue) - } - - // MARK: Primary Enqueuing - - /// Enqueue a block of work to be executed when all previous enqueued work has completed. - /// Future enqueued blocks will not begin until the returned promise is resolved. - /// - /// - Parameter recoverValue: The value to fallback to if the Promise returned by `nextPromise` fails. - /// This the value that will be given as input to the next enqueued block. - /// - Parameter nextPromise: A closure to be executed when previous enqueued work has - /// completed, returning a promise whose resolution blocks future enqueued work. Takes the previous result as input. - /// - Parameter map: Maps from the type of the `nextPromise` return type to the type of the root ChainedPromise. - /// - Returns a promise representing the result of the provided block, when it eventually executes. - public func enqueue( - recoverValue: T, - _ nextPromise: @escaping (Value) -> Promise, - _ map: @escaping (T) -> Value, - ) -> Promise { - _enqueue(nextPromise, recoverValue: map(recoverValue), map: map) - } - - // MARK: Convenience Enqueueing methods - - /// Enqueue a block of work to be executed when all previous enqueued work has completed. - /// Future enqueued blocks will not begin until the returned promise is resolved. - /// - /// - Parameter recoverValue: The value to fallback to if the Promise returned by `nextPromise` fails. - /// This the value that will be given as input to the next enqueued block. - /// - Parameter nextPromise: A closure to be executed when previous enqueued work has - /// completed, returning a promise whose resolution blocks future enqueued work. Takes the previous result as input. - /// - Returns a promise representing the result of the provided block, when it eventually executes. - public func enqueue( - recoverValue: Value, - _ nextPromise: @escaping (Value) -> Promise, - ) -> Promise { - _enqueue(nextPromise, recoverValue: recoverValue) - } -} - -extension ChainedPromise where Value == Void { - - /// Create a new ChainedPromise. - /// - /// Each ChainedPromise is independent; you typically create a single instance and enqueue multiple - /// blocks of work on it. - /// - /// - Parameter scheduler: The scheduler to use to serialize all calls. Defaults to a new unique serial background queue. - convenience init(scheduler: Scheduler = DispatchQueue(label: UUID().uuidString)) { - self.init(initialValue: (), scheduler: scheduler) - } - - /// Enqueue a block of work to be executed when all previous enqueued work has completed. - /// Future enqueued blocks will not begin until the returned promise is resolved. - /// - /// - Parameter nextPromise: A closure to be executed when previous enqueued work has - /// completed, returning a promise whose resolution blocks future enqueued work. - /// - Returns a promise representing the result of the provided block, when it eventually executes. - public func enqueue( - _ nextPromise: @escaping () -> Promise, - ) -> Promise { - _enqueue(nextPromise, recoverValue: ()) - } - - /// Enqueue a block of work to be executed when all previous enqueued work has completed. - /// Future enqueued blocks will not begin until the returned promise is resolved. - /// - /// - Parameter nextPromise: A closure to be executed when previous enqueued work has - /// completed, returning a promise whose resolution blocks future enqueued work. - /// - Returns a promise representing the result of the provided block, when it eventually executes. - public func enqueue( - _ nextPromise: @escaping () -> Promise, - ) -> Promise { - _enqueue(nextPromise, recoverValue: (), map: { _ in () }) - } -} - -extension ChainedPromise { - - // MARK: - Root implementation(s) - - // Note there are independent implementations for mapped and unmapped versions - // so as to avoid excessive queue-hopping when we run maps. - - private func _enqueue( - _ nextPromise: @escaping (Value) -> Promise, - recoverValue: Value, - ) -> Promise { - let (returnPromise, returnFuture) = Promise.pending() - scheduler.asyncIfNecessary { - let newPromise = self.currentPromise.then(on: self.scheduler) { prevValue in - return nextPromise(prevValue) - } - returnFuture.resolve(on: SyncScheduler(), with: newPromise) - self.currentPromise = newPromise - .recover(on: SyncScheduler()) { _ -> Promise in .value(recoverValue) } - } - return returnPromise - } - - private func _enqueue( - _ nextPromise: @escaping (Value) -> Promise, - recoverValue: Value, - map: @escaping (T) -> Value, - ) -> Promise { - let (returnPromise, returnFuture) = Promise.pending() - scheduler.asyncIfNecessary { - let newPromise = self.currentPromise.then(on: self.scheduler) { prevValue in - return nextPromise(prevValue) - } - returnFuture.resolve(on: SyncScheduler(), with: newPromise) - self.currentPromise = newPromise - .map(on: SyncScheduler(), map) - .recover(on: SyncScheduler()) { _ -> Promise in .value(recoverValue) } - } - return returnPromise - } -} diff --git a/SignalServiceKit/tests/Util/ChainedPromiseTest.swift b/SignalServiceKit/tests/Util/ChainedPromiseTest.swift deleted file mode 100644 index a203aa52f9..0000000000 --- a/SignalServiceKit/tests/Util/ChainedPromiseTest.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright 2022 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only -// - -import Foundation -public import XCTest -@testable import SignalServiceKit - -public class ChainedPromiseTest: XCTestCase { - func testChainSingleVoidPromise() throws { - let chainedPromise = ChainedPromise() - - let promise = chainedPromise.enqueue { - return .value(()) - } - expectSuccess(promise, description: "", timeout: 1) - } - - func testChainSingleValuePromise() throws { - let chainedPromise = ChainedPromise(initialValue: "") - let promise = chainedPromise.enqueue(recoverValue: "fail") { _ in - .value("hello") - } - let value = expectSuccess(promise, description: "", timeout: 1) - XCTAssertEqual(value, "hello") - } - - func testChainingPromiseSuccess() throws { - let chainedPromise = ChainedPromise() - - let (firstPromise, firstFuture) = Promise.pending() - _ = chainedPromise.enqueue { - return firstPromise - } - - let failIfExecuteSecondPromise = AtomicBool(true, lock: .sharedGlobal) - let (secondPromise, secondFuture) = Promise.pending() - let secondResultPromise = chainedPromise.enqueue { - XCTAssertFalse(failIfExecuteSecondPromise.get()) - return secondPromise - } - - failIfExecuteSecondPromise.set(false) - firstFuture.resolve(()) - secondFuture.resolve(()) - - expectSuccess(secondResultPromise, description: "", timeout: 1) - } - - func testChainingPromiseFailure() throws { - let chainedPromise = ChainedPromise() - - let (firstPromise, firstFuture) = Promise.pending() - let firstResultPromise = chainedPromise.enqueue { - return firstPromise - } - - let failIfExecuteSecondPromise = AtomicBool(true, lock: .sharedGlobal) - let (secondPromise, secondFuture) = Promise.pending() - let secondResultPromise = chainedPromise.enqueue { - XCTAssertFalse(failIfExecuteSecondPromise.get()) - return secondPromise - } - - failIfExecuteSecondPromise.set(false) - firstFuture.resolve(with: Promise(error: NSError())) - secondFuture.resolve(()) - - expectFailure(firstResultPromise, description: "first result failure", timeout: 0.1) - expectSuccess(secondResultPromise, description: "", timeout: 1) - } - - func testChainingPromiseChainedValue() throws { - let chainedPromise = ChainedPromise(initialValue: 1) - - let (firstPromise, firstFuture) = Promise.pending() - _ = chainedPromise.enqueue(recoverValue: -1) { - XCTAssertEqual($0, 1) - return firstPromise - } - - let (secondPromise, secondFuture) = Promise.pending() - let secondResultPromise = chainedPromise.enqueue(recoverValue: -1) { - XCTAssertEqual($0, 2) - return secondPromise - } - - firstFuture.resolve(2) - secondFuture.resolve(3) - - XCTAssertEqual(expectSuccess(secondResultPromise, description: "", timeout: 1), 3) - } - - func testChainingPromiseLongChain() throws { - let chainedPromise = ChainedPromise() - - // Set an initial promise to gate the others - let (firstPromise, firstFuture) = Promise.pending() - let firstResultPromise = chainedPromise.enqueue { - return firstPromise - } - - let hasResolvedUpTo = AtomicUInt(0, lock: .sharedGlobal) - var pendingFutures = [Future]() - var resultPromises = [Promise]() - var enqueuePromises = [Promise]() - (1...10).forEach { i in - let (promise, future) = Promise.pending() - let (enqueuePromise, enqueueFuture) = Promise.pending() - resultPromises.append(chainedPromise.enqueue { - XCTAssertEqual(hasResolvedUpTo.get(), UInt(i - 1)) - enqueueFuture.resolve(()) - return promise - }) - enqueuePromises.append(enqueuePromise) - pendingFutures.append(future) - } - - firstFuture.resolve(()) - expectSuccess(firstResultPromise, description: "initial", timeout: 1) - - pendingFutures.enumerated().forEach { i, future in - expectSuccess(enqueuePromises[i], description: "\(i)-th enqueue promise", timeout: 1) - hasResolvedUpTo.set(UInt(i + 1)) - future.resolve(()) - expectSuccess(resultPromises[i], description: "\(i)-th promise", timeout: 1) - } - } -}