Skip to content

Commit

Permalink
Add channel initializer closure to NIOAsyncTestingChannel.init (#3053)
Browse files Browse the repository at this point in the history
Since nio 2.78, adding handlers to a pipeline requires the handlers to
be sendable.

That makes the
[NIOAsyncTestingChannel.init(handlers:loop:)](https://swiftpackageindex.com/apple/swift-nio/2.78.0/documentation/nioembedded/nioasynctestingchannel/init(handlers:loop:))
function cumbersome, because you cannot create handlers and then call
the function (unless your handlers are Sendable) even if you never use
the handlers elsewhere.

This PR adds a new initializer which takes a closure. The closure is run
on-loop before the channel is registered. This means we can do:

```swift
let channel = try await NIOAsyncTestingChannel {
    let handler = MyUnsendableHandler()
    try $0.pipeline.syncOperations.addHandler(handler)
}
```
  • Loading branch information
hamzahrmalik authored Jan 13, 2025
1 parent 23d0b08 commit 329ef54
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 5 deletions.
25 changes: 20 additions & 5 deletions Sources/NIOEmbedded/AsyncTestingChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -307,12 +307,27 @@ public final class NIOAsyncTestingChannel: Channel {
handlers: [ChannelHandler & Sendable],
loop: NIOAsyncTestingEventLoop = NIOAsyncTestingEventLoop()
) async {
self.init(loop: loop)

try! await self._pipeline.addHandlers(handlers)
try! await self.init(loop: loop) { channel in
try channel.pipeline.syncOperations.addHandlers(handlers)
}
}

// This will never throw...
try! await self.register()
/// Create a new instance.
///
/// During creation it will automatically also register itself on the ``NIOAsyncTestingEventLoop``.
///
/// - Parameters:
/// - loop: The ``NIOAsyncTestingEventLoop`` to use.
/// - channelInitializer: The initialization closure which will be run on the `EventLoop` before registration. This could be used to add handlers using `syncOperations`.
public convenience init(
loop: NIOAsyncTestingEventLoop = NIOAsyncTestingEventLoop(),
channelInitializer: @escaping @Sendable (NIOAsyncTestingChannel) throws -> Void
) async throws {
self.init(loop: loop)
try await loop.submit {
try channelInitializer(self)
}.get()
try await self.register()
}

/// Asynchronously closes the ``NIOAsyncTestingChannel``.
Expand Down
11 changes: 11 additions & 0 deletions Tests/NIOEmbeddedTests/AsyncTestingChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ class AsyncTestingChannelTests: XCTestCase {
XCTAssertNoThrow(try channel.pipeline.removeHandler(name: "handler2").wait())
}

func testClosureInit() async throws {
final class Handler: ChannelInboundHandler, Sendable {
typealias InboundIn = Never
}

let channel = try await NIOAsyncTestingChannel {
try $0.pipeline.syncOperations.addHandler(Handler())
}
XCTAssertNoThrow(try channel.pipeline.handler(type: Handler.self).wait())
}

func testWaitForInboundWrite() async throws {
let channel = NIOAsyncTestingChannel()
let task = Task {
Expand Down

0 comments on commit 329ef54

Please sign in to comment.