Creates a new root singleton: OWSMessagePipelineSupervisor As of right now, this singleton has two responsibilities: - Track all message processing pipeline stages that have registered themselves - Post processing suspension updates to interested stages Four classes will now register themselves as pipeline stages: - OWSMessageContentQueue - YAPDBMessageDecryptQueue - IncomingGroupsV2MessageQueue - SSKMessageDecryptJobQueue At initialization, OWSMessagePipelineSupervisor will take out a pipeline suspension while it waits for a UUIDBackfillTask to complete. Other interested objects are also able to suspend the message processing pipeline by invoking suspendMessageProcessing(for:) on the supervisor (though, currently this is not used by anything except tests) This also adds some supporting improvements to UnfairLock. Now, UnfairLock closures will return the value returned from its critical section closure.
231 lines
5.9 KiB
Swift
231 lines
5.9 KiB
Swift
//
|
|
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import XCTest
|
|
|
|
@testable import SignalServiceKit
|
|
|
|
class UnfairLockTest: SSKBaseTestSwift {
|
|
|
|
private var dut: UnfairLock! = nil
|
|
|
|
override func setUp() {
|
|
dut = UnfairLock()
|
|
}
|
|
|
|
// MARK: - Lock + Unlock
|
|
func testSimpleLockAndUnlock() {
|
|
// Setup
|
|
var sharedVal = 0
|
|
|
|
// Test
|
|
fanout(1000) {
|
|
self.dut.lock()
|
|
sharedVal += 1
|
|
self.dut.unlock()
|
|
}
|
|
|
|
// Verify
|
|
XCTAssertEqual(sharedVal, 1000, "Lock failed to prevent data race.")
|
|
}
|
|
|
|
func testLockedClosure() {
|
|
// Setup
|
|
var sharedVal = 0
|
|
|
|
// Test
|
|
fanout(1000) {
|
|
self.dut.withLock {
|
|
sharedVal += 1
|
|
}
|
|
}
|
|
|
|
// Verify
|
|
XCTAssertEqual(sharedVal, 1000, "Lock failed to prevent data race.")
|
|
}
|
|
|
|
// MARK: - Lock attempts
|
|
|
|
func testTryLock_guaranteedFailure() {
|
|
// Setup
|
|
let didLockOuter = dut.tryLock()
|
|
var didLockInner = false
|
|
|
|
// Test
|
|
fanout(1000) {
|
|
if self.dut.tryLock() {
|
|
didLockInner = true
|
|
self.dut.unlock()
|
|
}
|
|
}
|
|
dut.unlock()
|
|
|
|
// Verify
|
|
XCTAssertTrue(didLockOuter, "Failed to acquire the uncontended lock.")
|
|
XCTAssertFalse(didLockInner, "tryLock() acquired an already acquired lock.")
|
|
}
|
|
|
|
func testTryLock_contended() {
|
|
// Setup
|
|
var blockInvocationCount = 0
|
|
|
|
// Test
|
|
fanout(1000) {
|
|
guard self.dut.tryLock() else { return }
|
|
blockInvocationCount += 1
|
|
self.dut.unlock()
|
|
}
|
|
|
|
// Verify
|
|
XCTAssertGreaterThanOrEqual(blockInvocationCount, 1, "Invalid invocation count. Expected: [1, 1000]")
|
|
XCTAssertLessThanOrEqual(blockInvocationCount, 1000, "Invalid invocation count. Expected: [1, 1000]")
|
|
}
|
|
|
|
func testTryLockClosure_guaranteedFailure() {
|
|
// Setup
|
|
let didLockOuter = dut.tryLock()
|
|
var didLockInner = false
|
|
|
|
// Test
|
|
fanout(1000) {
|
|
didLockInner = didLockInner || self.dut.tryWithLock {
|
|
didLockInner = true
|
|
}
|
|
}
|
|
dut.unlock()
|
|
|
|
// Verify
|
|
XCTAssertTrue(didLockOuter, "Failed to acquire the uncontended lock.")
|
|
XCTAssertFalse(didLockInner, "tryLock() acquired an already acquired lock.")
|
|
}
|
|
|
|
func testTryLockClosure_contended() {
|
|
// Setup
|
|
var blockInvocationCount = 0
|
|
|
|
// Test
|
|
fanout(1000) {
|
|
var invokedLocally = false
|
|
let success = self.dut.tryWithLock {
|
|
blockInvocationCount += 1
|
|
|
|
// To catch any repeat invocations of the closure
|
|
XCTAssertFalse(invokedLocally)
|
|
invokedLocally = true
|
|
}
|
|
// If the lock was acquired, the closure should have run
|
|
XCTAssertEqual(invokedLocally, success)
|
|
}
|
|
|
|
// Verify
|
|
XCTAssertGreaterThanOrEqual(blockInvocationCount, 1, "Invalid invocation count. Expected: [1, 1000]")
|
|
XCTAssertLessThanOrEqual(blockInvocationCount, 1000, "Invalid invocation count. Expected: [1, 1000]")
|
|
}
|
|
|
|
func testPropagatedReturnValue() {
|
|
// Setup
|
|
let outerVal: String? = "Hello, this is an optional string"
|
|
|
|
// Test
|
|
let returnedVal = dut.withLock {
|
|
return outerVal?.appending("!")
|
|
}
|
|
|
|
// Expect
|
|
XCTAssertEqual(returnedVal, "Hello, this is an optional string!")
|
|
}
|
|
|
|
func testPropagatedReturnValue_tryLockSuccess() {
|
|
// Setup
|
|
let outerVal: String? = "Hello, this is an optional string"
|
|
|
|
// Test
|
|
let returnedVal: String?? = dut.tryWithLock {
|
|
return outerVal?.appending("!")
|
|
}
|
|
|
|
// Expect
|
|
XCTAssertEqual(returnedVal, "Hello, this is an optional string!")
|
|
}
|
|
|
|
func testPropagatedReturnValue_tryLockFailure() {
|
|
// Setup
|
|
let outerVal: String? = "Hello, this is an optional string"
|
|
|
|
// Test
|
|
let returnedVal: String?? = dut.tryWithLock {
|
|
return dut.tryWithLock {
|
|
return outerVal?.appending("!")
|
|
} ?? "oops nevermind"
|
|
}
|
|
|
|
// Expect
|
|
XCTAssertEqual(returnedVal, "oops nevermind")
|
|
}
|
|
|
|
// MARK: - Throwing Inner Closure
|
|
|
|
func testThrowingLockedClosure() {
|
|
// Setup
|
|
var didAcquireLock = false
|
|
var didCatchError = false
|
|
var didReacquireLock = false
|
|
|
|
// Test
|
|
let toThrow = NSError(domain: "UnfairLockTests", code: 2, userInfo: nil)
|
|
do {
|
|
try dut.withLock {
|
|
didAcquireLock = true
|
|
throw toThrow
|
|
}
|
|
} catch {
|
|
XCTAssertEqual(toThrow, (error as NSError))
|
|
didCatchError = true
|
|
}
|
|
|
|
dut.withLock {
|
|
didReacquireLock = true
|
|
}
|
|
|
|
// Verify
|
|
XCTAssertTrue(didAcquireLock)
|
|
XCTAssertTrue(didCatchError)
|
|
XCTAssertTrue(didReacquireLock)
|
|
}
|
|
|
|
func testThrowingTryLockedClosure() {
|
|
// Setup
|
|
var didAcquireLock = false
|
|
var didCatchError = false
|
|
var didReacquireLock = false
|
|
|
|
// Test
|
|
let toThrow = NSError(domain: "UnfairLockTests", code: 2, userInfo: nil)
|
|
do {
|
|
try dut.tryWithLock {
|
|
didAcquireLock = true
|
|
throw toThrow
|
|
}
|
|
} catch {
|
|
XCTAssertEqual(toThrow, (error as NSError))
|
|
didCatchError = true
|
|
}
|
|
|
|
didReacquireLock = dut.tryWithLock {}
|
|
|
|
// Verify
|
|
XCTAssertTrue(didAcquireLock)
|
|
XCTAssertTrue(didCatchError)
|
|
XCTAssertTrue(didReacquireLock)
|
|
}
|
|
|
|
// MARK: - Test Helpers
|
|
|
|
func fanout(_ iterations: Int, _ block: () -> Void) {
|
|
DispatchQueue.concurrentPerform(iterations: iterations) { (_) in block() }
|
|
}
|
|
|
|
}
|