1109 lines
37 KiB
Swift
1109 lines
37 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
|
import Darwin
|
|
#else
|
|
import Glibc
|
|
#endif
|
|
import Dispatch
|
|
import XCTest
|
|
import NIOCore
|
|
@testable import NIOConcurrencyHelpers
|
|
|
|
class NIOConcurrencyHelpersTests: XCTestCase {
|
|
private func sumOfIntegers(until n: UInt64) -> UInt64 {
|
|
return n*(n+1)/2
|
|
}
|
|
|
|
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
|
let noAsyncs: UInt64 = 50
|
|
#else
|
|
/// `swift-corelibs-libdispatch` implementation of concurrent queues only initially spawn up to `System.coreCount` threads.
|
|
/// Afterwards they will create one thread per second.
|
|
/// Therefore this test takes `noAsyncs - System.coreCount` seconds to execute.
|
|
/// For example if `noAsyncs == 50` and `System.coreCount == 8` this test takes ~42 seconds to execute.
|
|
/// On non Darwin system we therefore limit the number of async operations to `System.coreCount`.
|
|
let noAsyncs: UInt64 = UInt64(System.coreCount)
|
|
#endif
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testLargeContendedAtomicSum() {
|
|
|
|
let noCounts: UInt64 = 2_000
|
|
|
|
let q = DispatchQueue(label: "q", attributes: .concurrent)
|
|
let g = DispatchGroup()
|
|
let ai = NIOConcurrencyHelpers.Atomic<UInt64>(value: 0)
|
|
let everybodyHere = DispatchSemaphore(value: 0)
|
|
let go = DispatchSemaphore(value: 0)
|
|
for thread in 1...self.noAsyncs {
|
|
q.async(group: g) {
|
|
everybodyHere.signal()
|
|
go.wait()
|
|
for _ in 0..<noCounts {
|
|
ai.add(thread)
|
|
}
|
|
}
|
|
}
|
|
for _ in 0..<self.noAsyncs {
|
|
everybodyHere.wait()
|
|
}
|
|
for _ in 0..<self.noAsyncs {
|
|
go.signal()
|
|
}
|
|
g.wait()
|
|
XCTAssertEqual(sumOfIntegers(until: self.noAsyncs) * noCounts, ai.load())
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testCompareAndExchangeBool() {
|
|
let ab = Atomic<Bool>(value: true)
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: false, desired: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: true, desired: true))
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: false, desired: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: true, desired: false))
|
|
|
|
XCTAssertTrue(ab.compareAndExchange(expected: false, desired: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: false, desired: true))
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testAllOperationsBool() {
|
|
let ab = Atomic<Bool>(value: false)
|
|
XCTAssertEqual(false, ab.load())
|
|
ab.store(false)
|
|
XCTAssertEqual(false, ab.load())
|
|
ab.store(true)
|
|
XCTAssertEqual(true, ab.load())
|
|
ab.store(true)
|
|
XCTAssertEqual(true, ab.load())
|
|
XCTAssertEqual(true, ab.exchange(with: true))
|
|
XCTAssertEqual(true, ab.exchange(with: false))
|
|
XCTAssertEqual(false, ab.exchange(with: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: false, desired: true))
|
|
XCTAssertFalse(ab.compareAndExchange(expected: false, desired: true))
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testCompareAndExchangeUInts() {
|
|
func testFor<T: AtomicPrimitive & FixedWidthInteger & UnsignedInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
let max = ~zero
|
|
|
|
let ab = Atomic<T>(value: max)
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: max, desired: max))
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: max, desired: zero))
|
|
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: max))
|
|
|
|
var counter = max
|
|
for _ in 0..<255 {
|
|
XCTAssertTrue(ab.compareAndExchange(expected: counter, desired: counter-1))
|
|
counter = counter - 1
|
|
}
|
|
}
|
|
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testCompareAndExchangeInts() {
|
|
func testFor<T: AtomicPrimitive & FixedWidthInteger & SignedInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
let upperBound: T = 127
|
|
|
|
let ab = Atomic<T>(value: upperBound)
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: upperBound, desired: upperBound))
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: upperBound, desired: zero))
|
|
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: upperBound))
|
|
|
|
var counter = upperBound
|
|
for _ in 0..<255 {
|
|
XCTAssertTrue(ab.compareAndExchange(expected: counter, desired: counter-1))
|
|
XCTAssertFalse(ab.compareAndExchange(expected: counter, desired: counter))
|
|
counter = counter - 1
|
|
}
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testAddSub() {
|
|
func testFor<T: AtomicPrimitive & FixedWidthInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
|
|
let ab = Atomic<T>(value: zero)
|
|
|
|
XCTAssertEqual(0, ab.add(1))
|
|
XCTAssertEqual(1, ab.add(41))
|
|
XCTAssertEqual(42, ab.add(23))
|
|
|
|
XCTAssertEqual(65, ab.load())
|
|
|
|
XCTAssertEqual(65, ab.sub(23))
|
|
XCTAssertEqual(42, ab.sub(41))
|
|
XCTAssertEqual(1, ab.sub(1))
|
|
|
|
XCTAssertEqual(0, ab.load())
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testExchange() {
|
|
func testFor<T: AtomicPrimitive & FixedWidthInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
|
|
let ab = Atomic<T>(value: zero)
|
|
|
|
XCTAssertEqual(0, ab.exchange(with: 1))
|
|
XCTAssertEqual(1, ab.exchange(with: 42))
|
|
XCTAssertEqual(42, ab.exchange(with: 65))
|
|
|
|
XCTAssertEqual(65, ab.load())
|
|
|
|
XCTAssertEqual(65, ab.exchange(with: 42))
|
|
XCTAssertEqual(42, ab.exchange(with: 1))
|
|
XCTAssertEqual(1, ab.exchange(with: 0))
|
|
|
|
XCTAssertEqual(0, ab.load())
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testLoadStore() {
|
|
func testFor<T: AtomicPrimitive & FixedWidthInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
|
|
let ab = Atomic<T>(value: zero)
|
|
|
|
XCTAssertEqual(0, ab.load())
|
|
ab.store(42)
|
|
XCTAssertEqual(42, ab.load())
|
|
ab.store(0)
|
|
XCTAssertEqual(0, ab.load())
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testLargeContendedNIOAtomicSum() {
|
|
let noCounts: UInt64 = 2_000
|
|
|
|
let q = DispatchQueue(label: "q", attributes: .concurrent)
|
|
let g = DispatchGroup()
|
|
let ai = NIOConcurrencyHelpers.NIOAtomic<UInt64>.makeAtomic(value: 0)
|
|
let everybodyHere = DispatchSemaphore(value: 0)
|
|
let go = DispatchSemaphore(value: 0)
|
|
for thread in 1...self.noAsyncs {
|
|
q.async(group: g) {
|
|
everybodyHere.signal()
|
|
go.wait()
|
|
for _ in 0..<noCounts {
|
|
ai.add(thread)
|
|
}
|
|
}
|
|
}
|
|
for _ in 0..<self.noAsyncs {
|
|
everybodyHere.wait()
|
|
}
|
|
for _ in 0..<self.noAsyncs {
|
|
go.signal()
|
|
}
|
|
g.wait()
|
|
XCTAssertEqual(sumOfIntegers(until: self.noAsyncs) * noCounts, ai.load())
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testCompareAndExchangeBoolNIOAtomic() {
|
|
let ab = NIOAtomic<Bool>.makeAtomic(value: true)
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: false, desired: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: true, desired: true))
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: false, desired: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: true, desired: false))
|
|
|
|
XCTAssertTrue(ab.compareAndExchange(expected: false, desired: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: false, desired: true))
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testAllOperationsBoolNIOAtomic() {
|
|
let ab = NIOAtomic<Bool>.makeAtomic(value: false)
|
|
XCTAssertEqual(false, ab.load())
|
|
ab.store(false)
|
|
XCTAssertEqual(false, ab.load())
|
|
ab.store(true)
|
|
XCTAssertEqual(true, ab.load())
|
|
ab.store(true)
|
|
XCTAssertEqual(true, ab.load())
|
|
XCTAssertEqual(true, ab.exchange(with: true))
|
|
XCTAssertEqual(true, ab.exchange(with: false))
|
|
XCTAssertEqual(false, ab.exchange(with: false))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: false, desired: true))
|
|
XCTAssertFalse(ab.compareAndExchange(expected: false, desired: true))
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testCompareAndExchangeUIntsNIOAtomic() {
|
|
func testFor<T: NIOAtomicPrimitive & FixedWidthInteger & UnsignedInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
let max = ~zero
|
|
|
|
let ab = NIOAtomic<T>.makeAtomic(value: max)
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: max, desired: max))
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: max, desired: zero))
|
|
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: max))
|
|
|
|
var counter = max
|
|
for _ in 0..<255 {
|
|
XCTAssertTrue(ab.compareAndExchange(expected: counter, desired: counter-1))
|
|
counter = counter - 1
|
|
}
|
|
}
|
|
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testCompareAndExchangeIntsNIOAtomic() {
|
|
func testFor<T: NIOAtomicPrimitive & FixedWidthInteger & SignedInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
let upperBound: T = 127
|
|
|
|
let ab = NIOAtomic<T>.makeAtomic(value: upperBound)
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: upperBound, desired: upperBound))
|
|
|
|
XCTAssertFalse(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: upperBound, desired: zero))
|
|
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: zero))
|
|
XCTAssertTrue(ab.compareAndExchange(expected: zero, desired: upperBound))
|
|
|
|
var counter = upperBound
|
|
for _ in 0..<255 {
|
|
XCTAssertTrue(ab.compareAndExchange(expected: counter, desired: counter-1))
|
|
XCTAssertFalse(ab.compareAndExchange(expected: counter, desired: counter))
|
|
counter = counter - 1
|
|
}
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testAddSubNIOAtomic() {
|
|
func testFor<T: NIOAtomicPrimitive & FixedWidthInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
|
|
let ab = NIOAtomic<T>.makeAtomic(value: zero)
|
|
|
|
XCTAssertEqual(0, ab.add(1))
|
|
XCTAssertEqual(1, ab.add(41))
|
|
XCTAssertEqual(42, ab.add(23))
|
|
|
|
XCTAssertEqual(65, ab.load())
|
|
|
|
XCTAssertEqual(65, ab.sub(23))
|
|
XCTAssertEqual(42, ab.sub(41))
|
|
XCTAssertEqual(1, ab.sub(1))
|
|
|
|
XCTAssertEqual(0, ab.load())
|
|
|
|
let atomic = NIOAtomic<T>.makeAtomic(value: zero)
|
|
atomic.store(100)
|
|
atomic.add(1)
|
|
XCTAssertEqual(101, atomic.load())
|
|
atomic.sub(1)
|
|
XCTAssertEqual(100, atomic.load())
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testExchangeNIOAtomic() {
|
|
func testFor<T: NIOAtomicPrimitive & FixedWidthInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
|
|
let ab = NIOAtomic<T>.makeAtomic(value: zero)
|
|
|
|
XCTAssertEqual(0, ab.exchange(with: 1))
|
|
XCTAssertEqual(1, ab.exchange(with: 42))
|
|
XCTAssertEqual(42, ab.exchange(with: 65))
|
|
|
|
XCTAssertEqual(65, ab.load())
|
|
|
|
XCTAssertEqual(65, ab.exchange(with: 42))
|
|
XCTAssertEqual(42, ab.exchange(with: 1))
|
|
XCTAssertEqual(1, ab.exchange(with: 0))
|
|
|
|
XCTAssertEqual(0, ab.load())
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it tests deprecated functionality")
|
|
func testLoadStoreNIOAtomic() {
|
|
func testFor<T: NIOAtomicPrimitive & FixedWidthInteger>(_ value: T.Type) {
|
|
let zero: T = 0
|
|
|
|
let ab = NIOAtomic<T>.makeAtomic(value: zero)
|
|
|
|
XCTAssertEqual(0, ab.load())
|
|
ab.store(42)
|
|
XCTAssertEqual(42, ab.load())
|
|
ab.store(0)
|
|
XCTAssertEqual(0, ab.load())
|
|
}
|
|
|
|
testFor(Int8.self)
|
|
testFor(Int16.self)
|
|
testFor(Int32.self)
|
|
testFor(Int64.self)
|
|
testFor(Int.self)
|
|
testFor(UInt8.self)
|
|
testFor(UInt16.self)
|
|
testFor(UInt32.self)
|
|
testFor(UInt64.self)
|
|
testFor(UInt.self)
|
|
}
|
|
|
|
|
|
func testLockMutualExclusion() {
|
|
let l = NIOLock()
|
|
|
|
var x = 1
|
|
let q = DispatchQueue(label: "q")
|
|
let g = DispatchGroup()
|
|
let sem1 = DispatchSemaphore(value: 0)
|
|
let sem2 = DispatchSemaphore(value: 0)
|
|
|
|
l.lock()
|
|
|
|
q.async(group: g) {
|
|
sem1.signal()
|
|
l.lock()
|
|
x = 2
|
|
l.unlock()
|
|
sem2.signal()
|
|
}
|
|
|
|
sem1.wait()
|
|
XCTAssertEqual(DispatchTimeoutResult.timedOut,
|
|
g.wait(timeout: .now() + 0.1))
|
|
XCTAssertEqual(1, x)
|
|
|
|
l.unlock()
|
|
sem2.wait()
|
|
|
|
l.lock()
|
|
XCTAssertEqual(2, x)
|
|
l.unlock()
|
|
}
|
|
|
|
func testWithLockMutualExclusion() {
|
|
let l = NIOLock()
|
|
|
|
var x = 1
|
|
let q = DispatchQueue(label: "q")
|
|
let g = DispatchGroup()
|
|
let sem1 = DispatchSemaphore(value: 0)
|
|
let sem2 = DispatchSemaphore(value: 0)
|
|
|
|
l.withLock {
|
|
q.async(group: g) {
|
|
sem1.signal()
|
|
l.withLock {
|
|
x = 2
|
|
}
|
|
sem2.signal()
|
|
}
|
|
|
|
sem1.wait()
|
|
XCTAssertEqual(DispatchTimeoutResult.timedOut,
|
|
g.wait(timeout: .now() + 0.1))
|
|
XCTAssertEqual(1, x)
|
|
}
|
|
sem2.wait()
|
|
|
|
l.withLock {
|
|
XCTAssertEqual(2, x)
|
|
}
|
|
}
|
|
|
|
func testConditionLockMutualExclusion() {
|
|
let l = ConditionLock(value: 0)
|
|
|
|
var x = 1
|
|
let q = DispatchQueue(label: "q")
|
|
let g = DispatchGroup()
|
|
let sem1 = DispatchSemaphore(value: 0)
|
|
let sem2 = DispatchSemaphore(value: 0)
|
|
|
|
l.lock()
|
|
|
|
q.async(group: g) {
|
|
sem1.signal()
|
|
l.lock()
|
|
x = 2
|
|
l.unlock()
|
|
sem2.signal()
|
|
}
|
|
|
|
sem1.wait()
|
|
XCTAssertEqual(DispatchTimeoutResult.timedOut,
|
|
g.wait(timeout: .now() + 0.1))
|
|
XCTAssertEqual(1, x)
|
|
|
|
l.unlock()
|
|
sem2.wait()
|
|
|
|
l.lock()
|
|
XCTAssertEqual(2, x)
|
|
l.unlock()
|
|
}
|
|
|
|
func testConditionLock() {
|
|
let l = ConditionLock(value: 0)
|
|
let q = DispatchQueue(label: "q")
|
|
let sem = DispatchSemaphore(value: 0)
|
|
|
|
XCTAssertEqual(0, l.value)
|
|
|
|
l.lock()
|
|
l.unlock(withValue: 1)
|
|
|
|
XCTAssertEqual(1, l.value)
|
|
|
|
q.async {
|
|
l.lock(whenValue: 2)
|
|
l.unlock(withValue: 3)
|
|
sem.signal()
|
|
}
|
|
|
|
usleep(100_000)
|
|
|
|
l.lock()
|
|
l.unlock(withValue: 2)
|
|
|
|
sem.wait()
|
|
l.lock(whenValue: 3)
|
|
l.unlock()
|
|
|
|
XCTAssertEqual(false, l.lock(whenValue: 4, timeoutSeconds: 0.1))
|
|
|
|
XCTAssertEqual(true, l.lock(whenValue: 3, timeoutSeconds: 0.01))
|
|
l.unlock()
|
|
|
|
q.async {
|
|
usleep(100_000)
|
|
|
|
l.lock()
|
|
l.unlock(withValue: 4)
|
|
sem.signal()
|
|
}
|
|
|
|
XCTAssertEqual(true, l.lock(whenValue: 4, timeoutSeconds: 10))
|
|
l.unlock()
|
|
}
|
|
|
|
func testConditionLockWithDifferentConditions() {
|
|
for _ in 0..<200 {
|
|
let l = ConditionLock(value: 0)
|
|
let q1 = DispatchQueue(label: "q1")
|
|
let q2 = DispatchQueue(label: "q2")
|
|
|
|
let readySem = DispatchSemaphore(value: 0)
|
|
let doneSem = DispatchSemaphore(value: 0)
|
|
|
|
q1.async {
|
|
readySem.signal()
|
|
|
|
l.lock(whenValue: 1)
|
|
l.unlock()
|
|
XCTAssertEqual(1, l.value)
|
|
|
|
doneSem.signal()
|
|
}
|
|
|
|
q2.async {
|
|
readySem.signal()
|
|
|
|
l.lock(whenValue: 2)
|
|
l.unlock()
|
|
XCTAssertEqual(2, l.value)
|
|
|
|
doneSem.signal()
|
|
}
|
|
|
|
readySem.wait()
|
|
readySem.wait()
|
|
l.lock()
|
|
l.unlock(withValue: 1)
|
|
|
|
doneSem.wait() /* job on 'q1' is done */
|
|
|
|
XCTAssertEqual(1, l.value)
|
|
l.lock()
|
|
l.unlock(withValue: 2)
|
|
|
|
doneSem.wait() /* job on 'q2' is done */
|
|
}
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testAtomicBoxDoesNotTriviallyLeak() throws {
|
|
class SomeClass {}
|
|
weak var weakSomeInstance1: SomeClass? = nil
|
|
weak var weakSomeInstance2: SomeClass? = nil
|
|
({
|
|
let someInstance = SomeClass()
|
|
weakSomeInstance1 = someInstance
|
|
let someAtomic = AtomicBox(value: someInstance)
|
|
let loadedFromAtomic = someAtomic.load()
|
|
weakSomeInstance2 = loadedFromAtomic
|
|
XCTAssertNotNil(weakSomeInstance1)
|
|
XCTAssertNotNil(weakSomeInstance2)
|
|
XCTAssert(someInstance === loadedFromAtomic)
|
|
})()
|
|
XCTAssertNil(weakSomeInstance1)
|
|
XCTAssertNil(weakSomeInstance2)
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testAtomicBoxCompareAndExchangeWorksIfEqual() throws {
|
|
class SomeClass {}
|
|
weak var weakSomeInstance1: SomeClass? = nil
|
|
weak var weakSomeInstance2: SomeClass? = nil
|
|
weak var weakSomeInstance3: SomeClass? = nil
|
|
({
|
|
let someInstance1 = SomeClass()
|
|
let someInstance2 = SomeClass()
|
|
weakSomeInstance1 = someInstance1
|
|
|
|
let atomic = AtomicBox(value: someInstance1)
|
|
var loadedFromAtomic = atomic.load()
|
|
XCTAssert(someInstance1 === loadedFromAtomic)
|
|
weakSomeInstance2 = loadedFromAtomic
|
|
|
|
XCTAssertTrue(atomic.compareAndExchange(expected: loadedFromAtomic, desired: someInstance2))
|
|
|
|
loadedFromAtomic = atomic.load()
|
|
weakSomeInstance3 = loadedFromAtomic
|
|
XCTAssert(someInstance1 !== loadedFromAtomic)
|
|
XCTAssert(someInstance2 === loadedFromAtomic)
|
|
|
|
XCTAssertNotNil(weakSomeInstance1)
|
|
XCTAssertNotNil(weakSomeInstance2)
|
|
XCTAssertNotNil(weakSomeInstance3)
|
|
XCTAssert(weakSomeInstance1 === weakSomeInstance2 && weakSomeInstance2 !== weakSomeInstance3)
|
|
})()
|
|
XCTAssertNil(weakSomeInstance1)
|
|
XCTAssertNil(weakSomeInstance2)
|
|
XCTAssertNil(weakSomeInstance3)
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testAtomicBoxCompareAndExchangeWorksIfNotEqual() throws {
|
|
class SomeClass {}
|
|
weak var weakSomeInstance1: SomeClass? = nil
|
|
weak var weakSomeInstance2: SomeClass? = nil
|
|
weak var weakSomeInstance3: SomeClass? = nil
|
|
({
|
|
let someInstance1 = SomeClass()
|
|
let someInstance2 = SomeClass()
|
|
weakSomeInstance1 = someInstance1
|
|
|
|
let atomic = AtomicBox(value: someInstance1)
|
|
var loadedFromAtomic = atomic.load()
|
|
XCTAssert(someInstance1 === loadedFromAtomic)
|
|
weakSomeInstance2 = loadedFromAtomic
|
|
|
|
XCTAssertFalse(atomic.compareAndExchange(expected: someInstance2, desired: someInstance2))
|
|
XCTAssertFalse(atomic.compareAndExchange(expected: SomeClass(), desired: someInstance2))
|
|
XCTAssertTrue(atomic.load() === someInstance1)
|
|
|
|
loadedFromAtomic = atomic.load()
|
|
weakSomeInstance3 = someInstance2
|
|
XCTAssert(someInstance1 === loadedFromAtomic)
|
|
XCTAssert(someInstance2 !== loadedFromAtomic)
|
|
|
|
XCTAssertNotNil(weakSomeInstance1)
|
|
XCTAssertNotNil(weakSomeInstance2)
|
|
XCTAssertNotNil(weakSomeInstance3)
|
|
})()
|
|
XCTAssertNil(weakSomeInstance1)
|
|
XCTAssertNil(weakSomeInstance2)
|
|
XCTAssertNil(weakSomeInstance3)
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testAtomicBoxStoreWorks() throws {
|
|
class SomeClass {}
|
|
weak var weakSomeInstance1: SomeClass? = nil
|
|
weak var weakSomeInstance2: SomeClass? = nil
|
|
weak var weakSomeInstance3: SomeClass? = nil
|
|
({
|
|
let someInstance1 = SomeClass()
|
|
let someInstance2 = SomeClass()
|
|
weakSomeInstance1 = someInstance1
|
|
|
|
let atomic = AtomicBox(value: someInstance1)
|
|
var loadedFromAtomic = atomic.load()
|
|
XCTAssert(someInstance1 === loadedFromAtomic)
|
|
weakSomeInstance2 = loadedFromAtomic
|
|
|
|
atomic.store(someInstance2)
|
|
|
|
loadedFromAtomic = atomic.load()
|
|
weakSomeInstance3 = loadedFromAtomic
|
|
XCTAssert(someInstance1 !== loadedFromAtomic)
|
|
XCTAssert(someInstance2 === loadedFromAtomic)
|
|
|
|
XCTAssertNotNil(weakSomeInstance1)
|
|
XCTAssertNotNil(weakSomeInstance2)
|
|
XCTAssertNotNil(weakSomeInstance3)
|
|
})()
|
|
XCTAssertNil(weakSomeInstance1)
|
|
XCTAssertNil(weakSomeInstance2)
|
|
XCTAssertNil(weakSomeInstance3)
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testAtomicBoxCompareAndExchangeOntoItselfWorks() {
|
|
let q = DispatchQueue(label: "q")
|
|
let g = DispatchGroup()
|
|
let sem1 = DispatchSemaphore(value: 0)
|
|
let sem2 = DispatchSemaphore(value: 0)
|
|
class SomeClass {}
|
|
weak var weakInstance: SomeClass?
|
|
({
|
|
let instance = SomeClass()
|
|
weakInstance = instance
|
|
|
|
let atomic = AtomicBox(value: instance)
|
|
q.async(group: g) {
|
|
sem1.signal()
|
|
sem2.wait()
|
|
for _ in 0..<1000 {
|
|
XCTAssertTrue(atomic.compareAndExchange(expected: instance, desired: instance))
|
|
}
|
|
}
|
|
sem2.signal()
|
|
sem1.wait()
|
|
for _ in 0..<1000 {
|
|
XCTAssertTrue(atomic.compareAndExchange(expected: instance, desired: instance))
|
|
}
|
|
g.wait()
|
|
let v = atomic.load()
|
|
XCTAssert(v === instance)
|
|
})()
|
|
XCTAssertNil(weakInstance)
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testAtomicLoadMassLoadAndStore() {
|
|
let writer = DispatchQueue(label: "\(#file):writer")
|
|
let reader = DispatchQueue(label: "\(#file):reader")
|
|
let g = DispatchGroup()
|
|
let writerArrived = DispatchSemaphore(value: 0)
|
|
let readerArrived = DispatchSemaphore(value: 0)
|
|
let go = DispatchSemaphore(value: 0)
|
|
let iterations = 100_000
|
|
|
|
class Foo {
|
|
var x: Int
|
|
|
|
init(_ x: Int) {
|
|
self.x = x
|
|
}
|
|
|
|
deinit {
|
|
self.x = -1
|
|
}
|
|
}
|
|
|
|
let box: AtomicBox<Foo> = .init(value: Foo(iterations))
|
|
|
|
writer.async(group: g) {
|
|
writerArrived.signal()
|
|
go.wait()
|
|
|
|
for i in 0..<iterations {
|
|
box.store(Foo(i))
|
|
}
|
|
}
|
|
|
|
reader.async(group: g) {
|
|
readerArrived.signal()
|
|
go.wait()
|
|
|
|
for _ in 0..<iterations {
|
|
if box.load().x < 0 {
|
|
XCTFail("bad")
|
|
}
|
|
}
|
|
}
|
|
|
|
writerArrived.wait()
|
|
readerArrived.wait()
|
|
go.signal()
|
|
go.signal()
|
|
|
|
g.wait()
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testAtomicBoxCompareAndExchangeOntoItself() {
|
|
class Foo {}
|
|
weak var weakF: Foo? = nil
|
|
weak var weakG: Foo? = nil
|
|
|
|
@inline(never)
|
|
func doIt() {
|
|
let f = Foo()
|
|
let g = Foo()
|
|
weakF = f
|
|
weakG = g
|
|
let box = AtomicBox<Foo>(value: f)
|
|
XCTAssertFalse(box.compareAndExchange(expected: g, desired: g))
|
|
XCTAssertTrue(box.compareAndExchange(expected: f, desired: f))
|
|
XCTAssertFalse(box.compareAndExchange(expected: g, desired: g))
|
|
XCTAssertTrue(box.compareAndExchange(expected: f, desired: g))
|
|
}
|
|
doIt()
|
|
assert(weakF == nil, within: .seconds(1))
|
|
assert(weakG == nil, within: .seconds(1))
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testLoadAndExchangeHammering() {
|
|
let allDeallocations = NIOAtomic<Int>.makeAtomic(value: 0)
|
|
let iterations = 10_000
|
|
|
|
@inline(never)
|
|
func doIt() {
|
|
let box = AtomicBox(value: IntHolderWithDeallocationTracking(0, allDeallocations: allDeallocations))
|
|
|
|
spawnAndJoinRacingThreads(count: 6) { i in
|
|
switch i {
|
|
case 0: // writer
|
|
for i in 1 ... iterations {
|
|
let nextObject = box.exchange(with: .init(i, allDeallocations: allDeallocations))
|
|
XCTAssertEqual(nextObject.value, i - 1)
|
|
}
|
|
default: // readers
|
|
while true {
|
|
if box.load().value < 0 || box.load().value > iterations {
|
|
XCTFail("bad")
|
|
}
|
|
if box.load().value == iterations {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
doIt()
|
|
assert(allDeallocations.load() == iterations + 1, within: .seconds(1))
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testLoadAndStoreHammering() {
|
|
let allDeallocations = NIOAtomic<Int>.makeAtomic(value: 0)
|
|
let iterations = 10_000
|
|
|
|
@inline(never)
|
|
func doIt() {
|
|
let box = AtomicBox(value: IntHolderWithDeallocationTracking(0, allDeallocations: allDeallocations))
|
|
|
|
spawnAndJoinRacingThreads(count: 6) { i in
|
|
switch i {
|
|
case 0: // writer
|
|
for i in 1 ... iterations {
|
|
box.store(IntHolderWithDeallocationTracking(i, allDeallocations: allDeallocations))
|
|
}
|
|
default: // readers
|
|
while true {
|
|
if box.load().value < 0 || box.load().value > iterations {
|
|
XCTFail("loaded the wrong value")
|
|
}
|
|
if box.load().value == iterations {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
doIt()
|
|
assert(allDeallocations.load() == iterations + 1, within: .seconds(1))
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testLoadAndCASHammering() {
|
|
let allDeallocations = NIOAtomic<Int>.makeAtomic(value: 0)
|
|
let iterations = 1_000
|
|
|
|
@inline(never)
|
|
func doIt() {
|
|
let box = AtomicBox(value: IntHolderWithDeallocationTracking(0, allDeallocations: allDeallocations))
|
|
|
|
spawnAndJoinRacingThreads(count: 6) { i in
|
|
switch i {
|
|
case 0: // writer
|
|
for i in 1 ... iterations {
|
|
let old = box.load()
|
|
XCTAssertEqual(i - 1, old.value)
|
|
if !box.compareAndExchange(expected: old,
|
|
desired: .init(i, allDeallocations: allDeallocations)) {
|
|
XCTFail("compare and exchange didn't work but it should have")
|
|
}
|
|
}
|
|
default: // readers
|
|
while true {
|
|
if box.load().value < 0 || box.load().value > iterations {
|
|
XCTFail("loaded wrong value")
|
|
}
|
|
if box.load().value == iterations {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
doIt()
|
|
assert(allDeallocations.load() == iterations + 1, within: .seconds(1))
|
|
}
|
|
|
|
@available(*, deprecated, message: "AtomicBox is deprecated, this is a test for the deprecated functionality")
|
|
func testMultipleLoadsRacingWhilstStoresAreGoingOn() {
|
|
// regression test for https://github.com/apple/swift-nio/pull/1287#discussion_r353932225
|
|
let allDeallocations = NIOAtomic<Int>.makeAtomic(value: 0)
|
|
let iterations = 10_000
|
|
|
|
@inline(never)
|
|
func doIt() {
|
|
let box = AtomicBox(value: IntHolderWithDeallocationTracking(0, allDeallocations: allDeallocations))
|
|
spawnAndJoinRacingThreads(count: 3) { i in
|
|
switch i {
|
|
case 0:
|
|
var last = Int.min
|
|
while last < iterations {
|
|
let loaded = box.load()
|
|
XCTAssertGreaterThanOrEqual(loaded.value, last)
|
|
last = loaded.value
|
|
}
|
|
case 1:
|
|
for n in 1...iterations {
|
|
box.store(.init(n, allDeallocations: allDeallocations))
|
|
}
|
|
case 2:
|
|
var last = Int.min
|
|
while last < iterations {
|
|
let loaded = box.load()
|
|
XCTAssertGreaterThanOrEqual(loaded.value, last)
|
|
last = loaded.value
|
|
}
|
|
default:
|
|
preconditionFailure("thread \(i)?!")
|
|
}
|
|
}
|
|
}
|
|
|
|
doIt()
|
|
XCTAssertEqual(iterations + 1, allDeallocations.load())
|
|
}
|
|
|
|
func testNIOLockedValueBox() {
|
|
struct State {
|
|
var count: Int = 0
|
|
}
|
|
|
|
let lv = NIOLockedValueBox<State>(State())
|
|
spawnAndJoinRacingThreads(count: 50) { _ in
|
|
for _ in 0..<1000 {
|
|
lv.withLockedValue { state in
|
|
state.count += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
XCTAssertEqual(50_000, lv.withLockedValue { $0.count })
|
|
}
|
|
|
|
func testNIOLockedValueBoxHandlesThingsWithTransitiveClassesProperly() {
|
|
struct State {
|
|
var counts: [Int] = []
|
|
}
|
|
|
|
let lv = NIOLockedValueBox<State>(State())
|
|
spawnAndJoinRacingThreads(count: 50) { _ in
|
|
for i in 0..<1000 {
|
|
lv.withLockedValue { state in
|
|
state.counts.append(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
XCTAssertEqual(50_000, lv.withLockedValue { $0.counts.count })
|
|
}
|
|
}
|
|
|
|
func spawnAndJoinRacingThreads(count: Int, _ body: @escaping (Int) -> Void) {
|
|
let go = DispatchSemaphore(value: 0) // will be incremented when the threads are supposed to run (and race).
|
|
let arrived = Array(repeating: DispatchSemaphore(value: 0), count: count) // waiting for all threads to arrive
|
|
|
|
let group = DispatchGroup()
|
|
for i in 0..<count {
|
|
DispatchQueue(label: "\(#file):\(#line):\(i)").async(group: group) {
|
|
arrived[i].signal()
|
|
go.wait()
|
|
body(i)
|
|
}
|
|
}
|
|
|
|
for sem in arrived {
|
|
sem.wait()
|
|
}
|
|
// all the threads are ready to go
|
|
for _ in 0..<count {
|
|
go.signal()
|
|
}
|
|
|
|
group.wait()
|
|
}
|
|
|
|
func assert(_ condition: @autoclosure () -> Bool, within time: TimeAmount, testInterval: TimeAmount? = nil, _ message: String = "condition not satisfied in time", file: StaticString = #filePath, line: UInt = #line) {
|
|
let testInterval = testInterval ?? TimeAmount.nanoseconds(time.nanoseconds / 5)
|
|
let endTime = NIODeadline.now() + time
|
|
|
|
repeat {
|
|
if condition() { return }
|
|
usleep(UInt32(testInterval.nanoseconds / 1000))
|
|
} while (NIODeadline.now() < endTime)
|
|
|
|
if !condition() {
|
|
XCTFail(message, file: (file), line: line)
|
|
}
|
|
}
|
|
|
|
@available(*, deprecated, message: "deprecated because it is used to test deprecated functionality")
|
|
fileprivate class IntHolderWithDeallocationTracking {
|
|
private(set) var value: Int
|
|
let allDeallocations: NIOAtomic<Int>
|
|
|
|
init(_ x: Int, allDeallocations: NIOAtomic<Int>) {
|
|
self.value = x
|
|
self.allDeallocations = allDeallocations
|
|
}
|
|
|
|
deinit {
|
|
self.value = -1
|
|
self.allDeallocations.add(1)
|
|
}
|
|
}
|