From b73fc4e90db2596c01f32028b2289bdde58d50d3 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Mon, 20 Jun 2022 14:23:14 +0200 Subject: [PATCH] Adopt `Sendable` in `NIOTestUtils` (#2199) * Adopting `Sendable` in `NIOTestUtils` The parameter `decoderFactory` of the static methods `ByteToMessageDecoderVerifier.verifyDecoder` do not need to be `@escaping`. I have made them non-escaping as part of `Sendable` adoption because we would otherwise need to think about if they should be `@Sendable` too. `VerificationError` is interesting, see the code comment for more information. * Adopting `Sendable` in `NIOTestUtils` The parameter `decoderFactory` of the static methods `ByteToMessageDecoderVerifier.verifyDecoder` do not need to be `@escaping`. I have made them non-escaping as part of `Sendable` adoption because we would otherwise need to think about if they should be `@Sendable` too. `VerificationError` is interesting, see the code comment for more information. * Clarify the reason `VerificationError` already conforms to `Sendable` --- .../ByteToMessageDecoderVerifier.swift | 20 ++++++++++++++++--- .../NIOTestUtils/EventCounterHandler.swift | 2 +- Sources/NIOTestUtils/NIOHTTP1TestServer.swift | 10 ++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Sources/NIOTestUtils/ByteToMessageDecoderVerifier.swift b/Sources/NIOTestUtils/ByteToMessageDecoderVerifier.swift index 2e8e9162..1d074aae 100644 --- a/Sources/NIOTestUtils/ByteToMessageDecoderVerifier.swift +++ b/Sources/NIOTestUtils/ByteToMessageDecoderVerifier.swift @@ -19,12 +19,13 @@ public enum ByteToMessageDecoderVerifier { /// /// Verify `ByteToMessageDecoder`s with `String` inputs public static func verifyDecoder(stringInputOutputPairs: [(String, [Decoder.InboundOut])], - decoderFactory: @escaping () -> Decoder) throws where Decoder.InboundOut: Equatable { + decoderFactory: () -> Decoder) throws where Decoder.InboundOut: Equatable { let alloc = ByteBufferAllocator() let ioPairs = stringInputOutputPairs.map { (ioPair: (String, [Decoder.InboundOut])) -> (ByteBuffer, [Decoder.InboundOut]) in return (alloc.buffer(string: ioPair.0), ioPair.1) } - return try ByteToMessageDecoderVerifier.verifyDecoder(inputOutputPairs: ioPairs, decoderFactory: decoderFactory) + + try ByteToMessageDecoderVerifier.verifyDecoder(inputOutputPairs: ioPairs, decoderFactory: decoderFactory) } /// Verifies a `ByteToMessageDecoder` by performing a number of tests. @@ -51,7 +52,7 @@ public enum ByteToMessageDecoderVerifier { /// XCTAssertNoThrow(try ByteToMessageDecoderVerifier.verifyDecoder(inputOutputPairs: expectedInOuts, /// decoderFactory: { ExampleDecoder() })) public static func verifyDecoder(inputOutputPairs: [(ByteBuffer, [Decoder.InboundOut])], - decoderFactory: @escaping () -> Decoder) throws where Decoder.InboundOut: Equatable { + decoderFactory: () -> Decoder) throws where Decoder.InboundOut: Equatable { typealias Out = Decoder.InboundOut func verifySimple(channel: RecordingChannel) throws { @@ -207,3 +208,16 @@ extension ByteToMessageDecoderVerifier { } } } + +#if swift(>=5.5) && canImport(_Concurrency) +/// `VerificationError` conforms to `Error` and therefore needs to conform to `Sendable` too. +/// `VerificationError` has a stored property `errorCode` of type `ErrorCode` which can store `NIOAny` which is not and can not be `Sendable`. +/// In addtion, `ErrorCode` can also store a user defined `OutputType` which is not required to be `Sendable` but we could require it to be `Sendable`. +/// We have two choices: +/// - we could lie and conform `ErrorCode` to `Sendable` with `@unchecked` +/// - do the same but for `VerificationError` +/// As `VerificationError` already conforms to `Sendable` (because it conforms to `Error` and `Error` inherits from `Sendable`) +/// it sound like the best option to just stick to the conformances we already have and **not** lie twice by making `VerificationError` conform to `Sendable` too. +/// Note that this still allows us to adopt `Sendable` for `ErrorCode` later if we change our opinion. +extension ByteToMessageDecoderVerifier.VerificationError: @unchecked Sendable {} +#endif diff --git a/Sources/NIOTestUtils/EventCounterHandler.swift b/Sources/NIOTestUtils/EventCounterHandler.swift index adec4ac7..a37a8b50 100644 --- a/Sources/NIOTestUtils/EventCounterHandler.swift +++ b/Sources/NIOTestUtils/EventCounterHandler.swift @@ -23,7 +23,7 @@ import NIOConcurrencyHelpers /// /// - note: Contrary to most `ChannelHandler`s, all of `EventCounterHandler`'s API is thread-safe meaning that you can /// query the events received from any thread. -public final class EventCounterHandler { +public final class EventCounterHandler: NIOSendable { private let _channelRegisteredCalls = NIOAtomic.makeAtomic(value: 0) private let _channelUnregisteredCalls = NIOAtomic.makeAtomic(value: 0) private let _channelActiveCalls = NIOAtomic.makeAtomic(value: 0) diff --git a/Sources/NIOTestUtils/NIOHTTP1TestServer.swift b/Sources/NIOTestUtils/NIOHTTP1TestServer.swift index 198080b4..1b449c61 100644 --- a/Sources/NIOTestUtils/NIOHTTP1TestServer.swift +++ b/Sources/NIOTestUtils/NIOHTTP1TestServer.swift @@ -46,6 +46,11 @@ private final class BlockingQueue { } } +#if swift(>=5.5) && canImport(_Concurrency) +extension BlockingQueue: @unchecked Sendable where Element: Sendable {} +#endif + + private final class WebServerHandler: ChannelDuplexHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundIn = HTTPServerResponsePart @@ -303,6 +308,11 @@ extension NIOHTTP1TestServer { } } +#if swift(>=5.6) +@available(*, unavailable) +extension NIOHTTP1TestServer: Sendable {} +#endif + // MARK: - API for HTTP server extension NIOHTTP1TestServer { fileprivate func pushChannelRead(_ state: HTTPServerRequestPart) {