Skip to content

Commit

Permalink
Complete NIOCore strict concurrency (#2959)
Browse files Browse the repository at this point in the history
Motivation:

With our earlier big refactors, NIOCore is now currently strict
concurrency clean. Let's lock in the win by adopting the relevant Swift
settings and fixing up the tests.

Modifications:

- Adopt our strict concurrency settings in NIOCore and NIOCoreTests
- Clean up the tests

Result:

One step closer to a world without warnings.
  • Loading branch information
Lukasa authored Oct 31, 2024
1 parent e25bf7e commit 37b31b9
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 47 deletions.
7 changes: 5 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ let package = Package(
"_NIODataStructures",
swiftCollections,
swiftAtomics,
]
],
swiftSettings: strictConcurrencySettings
),
.target(
name: "_NIODataStructures",
Expand Down Expand Up @@ -414,11 +415,13 @@ let package = Package(
.testTarget(
name: "NIOCoreTests",
dependencies: [
"NIOConcurrencyHelpers",
"NIOCore",
"NIOEmbedded",
"NIOFoundationCompat",
swiftAtomics,
]
],
swiftSettings: strictConcurrencySettings
),
.testTarget(
name: "NIOEmbeddedTests",
Expand Down
2 changes: 1 addition & 1 deletion Sources/NIOCore/BSDSocketAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ let SO_TIMESTAMP = CNIOLinux_SO_TIMESTAMP
let SO_RCVTIMEO = CNIOLinux_SO_RCVTIMEO
#endif

public enum NIOBSDSocket {
public enum NIOBSDSocket: Sendable {
#if os(Windows)
public typealias Handle = SOCKET
#else
Expand Down
6 changes: 3 additions & 3 deletions Tests/NIOCoreTests/AsyncChannel/AsyncChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ final class AsyncChannelTests: XCTestCase {
let strongSentinel: Sentinel? = Sentinel()
sentinel = strongSentinel!
try await XCTAsyncAssertNotNil(
await channel.pipeline.handler(type: NIOAsyncChannelHandler<Sentinel, Sentinel, Never>.self).map { _ in
await channel.pipeline.handler(type: NIOAsyncChannelHandler<Sentinel, Sentinel, Never>.self).map {
_ -> Bool in
true
}.get()
)
Expand Down Expand Up @@ -428,9 +429,8 @@ private final class CloseRecorder: ChannelOutboundHandler, @unchecked Sendable {
}
}

private final class CloseSuppressor: ChannelOutboundHandler, RemovableChannelHandler {
private final class CloseSuppressor: ChannelOutboundHandler, RemovableChannelHandler, Sendable {
typealias OutboundIn = Any
typealias outbound = Any

func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
// We drop the close here.
Expand Down
4 changes: 2 additions & 2 deletions Tests/NIOCoreTests/AsyncSequences/NIOAsyncSequenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ final class NIOAsyncSequenceProducerTests: XCTestCase {
let value = await iterator.next()
resumed.fulfill()

await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
return value
}

Expand Down Expand Up @@ -562,7 +562,7 @@ final class NIOAsyncSequenceProducerTests: XCTestCase {
let cancelled = expectation(description: "task cancelled")

let task: Task<Int?, Never> = Task {
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
let iterator = sequence.makeAsyncIterator()
return await iterator.next()
}
Expand Down
13 changes: 7 additions & 6 deletions Tests/NIOCoreTests/AsyncSequences/NIOAsyncWriterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ final class NIOAsyncWriterTests: XCTestCase {
override func setUp() {
super.setUp()

self.delegate = .init()
let delegate = MockAsyncWriterDelegate()
self.delegate = delegate
let newWriter = NIOAsyncWriter.makeWriter(
elementType: String.self,
isWritable: true,
Expand All @@ -78,7 +79,7 @@ final class NIOAsyncWriterTests: XCTestCase {
)
self.writer = newWriter.writer
self.sink = newWriter.sink
self.sink._storage._setDidSuspend { self.delegate.didSuspend() }
self.sink._storage._setDidSuspend { delegate.didSuspend() }
}

override func tearDown() {
Expand Down Expand Up @@ -411,7 +412,7 @@ final class NIOAsyncWriterTests: XCTestCase {
let cancelled = expectation(description: "task cancelled")

let task = Task { [writer] in
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
try await writer!.yield("message2")
}

Expand Down Expand Up @@ -470,7 +471,7 @@ final class NIOAsyncWriterTests: XCTestCase {
let cancelled = expectation(description: "task cancelled")

let task = Task { [writer] in
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
try await writer!.yield("message1")
}

Expand All @@ -491,7 +492,7 @@ final class NIOAsyncWriterTests: XCTestCase {
let cancelled = expectation(description: "task cancelled")

let task = Task { [writer] in
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
try await writer!.yield("message2")
}

Expand Down Expand Up @@ -545,7 +546,7 @@ final class NIOAsyncWriterTests: XCTestCase {
let cancelled = expectation(description: "task cancelled")

let task = Task { [writer] in
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
try await writer!.yield("message1")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ final class NIOThrowingAsyncSequenceProducerTests: XCTestCase {
let iterator = sequence.makeAsyncIterator()
let element = try await iterator.next()
resumed.fulfill()
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
return element
}

Expand Down Expand Up @@ -655,7 +655,7 @@ final class NIOThrowingAsyncSequenceProducerTests: XCTestCase {
let cancelled = expectation(description: "task cancelled")

let task: Task<Int?, Error> = Task {
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
let iterator = sequence.makeAsyncIterator()
return try await iterator.next()
}
Expand Down Expand Up @@ -686,7 +686,7 @@ final class NIOThrowingAsyncSequenceProducerTests: XCTestCase {
let cancelled = expectation(description: "task cancelled")

let task: Task<Int?, Error> = Task {
await fulfillment(of: [cancelled], timeout: 1)
await XCTWaiter().fulfillment(of: [cancelled], timeout: 1)
let iterator = sequence.makeAsyncIterator()
return try await iterator.next()
}
Expand Down Expand Up @@ -879,12 +879,13 @@ final class NIOThrowingAsyncSequenceProducerTests: XCTestCase {

func testIteratorThrows_whenCancelled() async {
_ = self.source.yield(contentsOf: Array(1...100))
guard let sequence = self.sequence else {
return XCTFail("Expected to have an AsyncSequence")
}

await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
var itemsYieldedCounter = 0
guard let sequence = self.sequence else {
return XCTFail("Expected to have an AsyncSequence")
}

do {
for try await next in sequence {
Expand Down
43 changes: 26 additions & 17 deletions Tests/NIOCoreTests/ByteBufferTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import Atomics
import NIOFoundationCompat
import XCTest
import _NIOBase64
Expand Down Expand Up @@ -2440,8 +2441,8 @@ class ByteBufferTest: XCTestCase {
}

func testReserveCapacityLargerUniquelyReferencedCallsRealloc() throws {
testReserveCapacityLarger_reallocCount = 0
testReserveCapacityLarger_mallocCount = 0
testReserveCapacityLarger_reallocCount.store(0, ordering: .sequentiallyConsistent)
testReserveCapacityLarger_mallocCount.store(0, ordering: .sequentiallyConsistent)

let alloc = ByteBufferAllocator(
hookedMalloc: testReserveCapacityLarger_mallocHook,
Expand All @@ -2453,17 +2454,17 @@ class ByteBufferTest: XCTestCase {

let oldCapacity = buf.capacity

XCTAssertEqual(testReserveCapacityLarger_mallocCount, 1)
XCTAssertEqual(testReserveCapacityLarger_reallocCount, 0)
XCTAssertEqual(testReserveCapacityLarger_mallocCount.load(ordering: .sequentiallyConsistent), 1)
XCTAssertEqual(testReserveCapacityLarger_reallocCount.load(ordering: .sequentiallyConsistent), 0)
buf.reserveCapacity(32)
XCTAssertEqual(testReserveCapacityLarger_mallocCount, 1)
XCTAssertEqual(testReserveCapacityLarger_reallocCount, 1)
XCTAssertEqual(testReserveCapacityLarger_mallocCount.load(ordering: .sequentiallyConsistent), 1)
XCTAssertEqual(testReserveCapacityLarger_reallocCount.load(ordering: .sequentiallyConsistent), 1)
XCTAssertNotEqual(buf.capacity, oldCapacity)
}

func testReserveCapacityLargerMultipleReferenceCallsMalloc() throws {
testReserveCapacityLarger_reallocCount = 0
testReserveCapacityLarger_mallocCount = 0
testReserveCapacityLarger_reallocCount.store(0, ordering: .sequentiallyConsistent)
testReserveCapacityLarger_mallocCount.store(0, ordering: .sequentiallyConsistent)

let alloc = ByteBufferAllocator(
hookedMalloc: testReserveCapacityLarger_mallocHook,
Expand All @@ -2480,11 +2481,11 @@ class ByteBufferTest: XCTestCase {
UInt(bitPattern: $0.baseAddress!)
}

XCTAssertEqual(testReserveCapacityLarger_mallocCount, 1)
XCTAssertEqual(testReserveCapacityLarger_reallocCount, 0)
XCTAssertEqual(testReserveCapacityLarger_mallocCount.load(ordering: .sequentiallyConsistent), 1)
XCTAssertEqual(testReserveCapacityLarger_reallocCount.load(ordering: .sequentiallyConsistent), 0)
buf.reserveCapacity(32)
XCTAssertEqual(testReserveCapacityLarger_mallocCount, 2)
XCTAssertEqual(testReserveCapacityLarger_reallocCount, 0)
XCTAssertEqual(testReserveCapacityLarger_mallocCount.load(ordering: .sequentiallyConsistent), 2)
XCTAssertEqual(testReserveCapacityLarger_reallocCount.load(ordering: .sequentiallyConsistent), 0)

let newPtrVal = buf.withVeryUnsafeBytes {
UInt(bitPattern: $0.baseAddress!)
Expand Down Expand Up @@ -3354,7 +3355,15 @@ private enum AllocationExpectationState: Int {
case freeDone
}

private var testAllocationOfReallyBigByteBuffer_state = AllocationExpectationState.begin
private let _testAllocationOfReallyBigByteBuffer_state = ManagedAtomic<Int>(AllocationExpectationState.begin.rawValue)
private var testAllocationOfReallyBigByteBuffer_state: AllocationExpectationState {
get {
.init(rawValue: _testAllocationOfReallyBigByteBuffer_state.load(ordering: .acquiring))!
}
set {
_testAllocationOfReallyBigByteBuffer_state.store(newValue.rawValue, ordering: .releasing)
}
}
private func testAllocationOfReallyBigByteBuffer_freeHook(_ ptr: UnsafeMutableRawPointer?) {
precondition(AllocationExpectationState.reallocDone == testAllocationOfReallyBigByteBuffer_state)
testAllocationOfReallyBigByteBuffer_state = .freeDone
Expand Down Expand Up @@ -3387,22 +3396,22 @@ private func testAllocationOfReallyBigByteBuffer_memcpyHook(
// not actually doing any copies
}

private var testReserveCapacityLarger_reallocCount = 0
private var testReserveCapacityLarger_mallocCount = 0
private let testReserveCapacityLarger_reallocCount = ManagedAtomic(0)
private let testReserveCapacityLarger_mallocCount = ManagedAtomic(0)
private func testReserveCapacityLarger_freeHook(_ ptr: UnsafeMutableRawPointer) {
free(ptr)
}

private func testReserveCapacityLarger_mallocHook(_ size: Int) -> UnsafeMutableRawPointer? {
testReserveCapacityLarger_mallocCount += 1
testReserveCapacityLarger_mallocCount.wrappingIncrement(ordering: .sequentiallyConsistent)
return malloc(size)
}

private func testReserveCapacityLarger_reallocHook(
_ ptr: UnsafeMutableRawPointer?,
_ count: Int
) -> UnsafeMutableRawPointer? {
testReserveCapacityLarger_reallocCount += 1
testReserveCapacityLarger_reallocCount.wrappingIncrement(ordering: .sequentiallyConsistent)
return realloc(ptr, count)
}

Expand Down
13 changes: 11 additions & 2 deletions Tests/NIOCoreTests/ChannelOptionStorageTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import NIOConcurrencyHelpers
import NIOCore
import NIOEmbedded
import XCTest
Expand Down Expand Up @@ -110,8 +111,16 @@ class ChannelOptionStorageTest: XCTestCase {
}
}

class OptionsCollectingChannel: Channel {
var allOptions: [(Any, Any)] = []
final class OptionsCollectingChannel: Channel {
private let _allOptions = NIOLockedValueBox<[(any Sendable, any Sendable)]>([])
var allOptions: [(any Sendable, any Sendable)] {
get {
self._allOptions.withLockedValue { $0 }
}
set {
self._allOptions.withLockedValue { $0 = newValue }
}
}

var allocator: ByteBufferAllocator { fatalError() }

Expand Down
13 changes: 7 additions & 6 deletions Tests/NIOCoreTests/DispatchQueue+WithFutureTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import Atomics
import Dispatch
import NIOCore
import NIOEmbedded
Expand All @@ -30,20 +31,20 @@ class DispatchQueueWithFutureTest: XCTestCase {
}
let eventLoop = group.next()
let sem = DispatchSemaphore(value: 0)
var nonBlockingRan = false
let nonBlockingRan = ManagedAtomic(false)
let futureResult: EventLoopFuture<String> = DispatchQueue.global().asyncWithFuture(eventLoop: eventLoop) {
() -> String in
sem.wait() // Block in callback
return "hello"
}
futureResult.whenSuccess { value in
XCTAssertEqual(value, "hello")
XCTAssertTrue(nonBlockingRan)
XCTAssertTrue(nonBlockingRan.load(ordering: .sequentiallyConsistent))
}

let p2 = eventLoop.makePromise(of: Bool.self)
p2.futureResult.whenSuccess { _ in
nonBlockingRan = true
nonBlockingRan.store(true, ordering: .sequentiallyConsistent)
}
p2.succeed(true)

Expand All @@ -57,20 +58,20 @@ class DispatchQueueWithFutureTest: XCTestCase {
}
let eventLoop = group.next()
let sem = DispatchSemaphore(value: 0)
var nonBlockingRan = false
let nonBlockingRan = ManagedAtomic(false)
let futureResult: EventLoopFuture<String> = DispatchQueue.global().asyncWithFuture(eventLoop: eventLoop) {
() -> String in
sem.wait() // Block in callback
throw DispatchQueueTestError.example
}
futureResult.whenFailure { err in
XCTAssertEqual(err as! DispatchQueueTestError, DispatchQueueTestError.example)
XCTAssertTrue(nonBlockingRan)
XCTAssertTrue(nonBlockingRan.load(ordering: .sequentiallyConsistent))
}

let p2 = eventLoop.makePromise(of: Bool.self)
p2.futureResult.whenSuccess { _ in
nonBlockingRan = true
nonBlockingRan.store(true, ordering: .sequentiallyConsistent)
}
p2.succeed(true)

Expand Down
2 changes: 1 addition & 1 deletion Tests/NIOCoreTests/NIOCloseOnErrorHandlerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import NIOCore
import NIOEmbedded
import XCTest

final class DummyFailingHandler1: ChannelInboundHandler {
final class DummyFailingHandler1: ChannelInboundHandler, Sendable {
typealias InboundIn = NIOAny

struct DummyError1: Error {}
Expand Down
2 changes: 1 addition & 1 deletion Tests/NIOCoreTests/XCTest+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func assertNoThrowWithValue<T>(

func withTemporaryFile<T>(content: String? = nil, _ body: (NIOCore.NIOFileHandle, String) throws -> T) throws -> T {
let temporaryFilePath = "\(temporaryDirectory)/nio_\(UUID())"
FileManager.default.createFile(atPath: temporaryFilePath, contents: content?.data(using: .utf8))
_ = FileManager.default.createFile(atPath: temporaryFilePath, contents: content?.data(using: .utf8))
defer {
XCTAssertNoThrow(try FileManager.default.removeItem(atPath: temporaryFilePath))
}
Expand Down

0 comments on commit 37b31b9

Please sign in to comment.