From async back to the synchronous world (Swift NIO)

Hi everyone,

So I've been getting code ready for Swift 6 using Strict Concurrency Checking. I solved all the warnings, except for this one:

final class ParseHandler: ChannelInboundHandler, Sendable {
    
    typealias InboundIn = MudCommand
    typealias InboundOut = [MudResponse]
    
    public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        let promise = context.eventLoop.makePromise(of: Void.self)        
        let mudCommand = self.unwrapInboundIn(data)
        
        // `Context` does not conform to `@Sendable`, but `EventLoop` does,
        // so we pass only the EventLoop and a reference to the `fireChannelRead` function.
        let eventLoop = context.eventLoop
        let fireChannelRead = context.fireChannelRead
        
        promise.completeWithTask {
            let response = await createMudResponse(mudCommand: mudCommand)
            
            eventLoop.execute {
                fireChannelRead(self.wrapInboundOut(response)) // WARNING: Capture of 'fireChannelRead' with non-sendable type '(NIOAny) -> ()' in a `@Sendable` closure
            }
        }
    }
}

What I try to accomplish is this: I have all "business logic" in the Swift concurrency world, hidden behind the createMudResponse function. This async function just returns a result at some time. After this result becomes available, I want to send out the result. I now do this using fireChannelRead. And that leads to this error.

Am I on the right track here? How should I go about this?

KR Maarten

The "proper" refactoring would probably be utilizing the NIOAsyncChannel further down the pipline to get your logic out of the channel handlers.

As a quick-fix, you could wrap your context or fireChannelRead function in a NIOLoopBound box - should do the trick I think.

2 Likes

The NIOLoopBound box is indeed the quick fix. :tada:

Now I'm gonna try using NIOAsyncChannel.

1 Like