Repository: apple/swift-nio
File: Sources/NIOCore/AsyncChannel/AsyncChannel.swift (Swift)

Code before:
            (NIOAsyncChannelInboundStream<Inbound>, NIOAsyncChannelOutboundWriter<Outbound>) =
                try channel._syncAddAsyncHandlersWithTransformations(
                    backPressureStrategy: backPressureStrategy,
                    isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled,
                    closeOnDeinit: false,
                    channelReadTransformation: channelReadTransformation
                )

        outboundWriter.finish()

        return .init(
            channel: channel,
            inboundStream: inboundStream,
            outboundWriter: outboundWriter
        )
    }

    /// Provides scoped access to the inbound and outbound side of the underlying ``Channel``.
    ///
    /// - Important: After this method returned the underlying ``Channel`` will be closed.
    ///
    /// - Parameter:
    ///     - isolatedTo: actor where this function should be isolated to
    ///     - body: A closure that gets scoped access to the inbound and outbound.
    public func executeThenClose<Result>(
        isolatedTo actor: isolated(any Actor)? = #isolation,
        _ body: (_ inbound: NIOAsyncChannelInboundStream<Inbound>, _ outbound: NIOAsyncChannelOutboundWriter<Outbound>)
            async throws -> Result
    ) async throws -> Result {
        let result: Result
        do {
            result = try await body(self._inbound, self._outbound)
        } catch let bodyError {
            do {
                self._outbound.finish()
                try await self.channel.close().get()
                throw bodyError
            } catch {
                throw bodyError
            }
        }

        self._outbound.finish()
        // We ignore errors from close, since all we care about is that the channel has been closed
        // at this point.
        self.channel.close(promise: nil)
        // `closeFuture` should never be failed, so we could ignore the error. However, do an
        // assertionFailure to guide bad Channel implementations that are incorrectly failing this
        // future to stop failing it.
        do {
            try await self.channel.closeFuture.get()

Review comment (bug):
This change is actually API-breaking: even if the parameter you add is defaulted, the function signature changes.

For instance, imagine you have:
```swift
func someFunc(foo: Int) { ... }

func doSomething() {
  let f: (Int) -> Void  = someFunc(foo:)
  // ...
}
```

Say you add a new defaulted parameter...
```swift
func someFunc(foo: Int, bar: String = "baz") { ... }

func doSomething() {
  let f1: (Int) -> Void  = someFunc(foo:)  //  this will fail to build: "no member named someFunc(foo:)"
  // Should be: 
  let f2: (Int, String?) -> Void  = someFunc(foo:bar:)
}
```


You will have to add a new `executeThenClose` overload that takes a (non-optional) isolated param.

Code after:
            (NIOAsyncChannelInboundStream<Inbound>, NIOAsyncChannelOutboundWriter<Outbound>) =
                try channel._syncAddAsyncHandlersWithTransformations(
                    backPressureStrategy: backPressureStrategy,
                    isOutboundHalfClosureEnabled: isOutboundHalfClosureEnabled,
                    closeOnDeinit: false,
                    channelReadTransformation: channelReadTransformation
                )

        outboundWriter.finish()

        return .init(
            channel: channel,
            inboundStream: inboundStream,
            outboundWriter: outboundWriter
        )
    }

    /// Provides scoped access to the inbound and outbound side of the underlying ``Channel``.
    ///
    /// - Important: After this method returned the underlying ``Channel`` will be closed.
    ///
    /// - Parameter body: A closure that gets scoped access to the inbound and outbound.
    @_disfavoredOverload
    public func executeThenClose<Result>(
        _ body: (_ inbound: NIOAsyncChannelInboundStream<Inbound>, _ outbound: NIOAsyncChannelOutboundWriter<Outbound>)
            async throws -> Result
    ) async throws -> Result {
        let result: Result
        do {
            result = try await body(self._inbound, self._outbound)
        } catch let bodyError {
            do {
                self._outbound.finish()
                try await self.channel.close().get()
                throw bodyError
            } catch {
                throw bodyError
            }
        }

        self._outbound.finish()
        // We ignore errors from close, since all we care about is that the channel has been closed
        // at this point.
        self.channel.close(promise: nil)
        // `closeFuture` should never be failed, so we could ignore the error. However, do an
        // assertionFailure to guide bad Channel implementations that are incorrectly failing this
        // future to stop failing it.
        do {
            try await self.channel.closeFuture.get()
        } catch {
            assertionFailure(