diff --git a/Sources/NIOCore/AsyncChannel/AsyncChannel.swift b/Sources/NIOCore/AsyncChannel/AsyncChannel.swift index 10354f0760..105862576d 100644 --- a/Sources/NIOCore/AsyncChannel/AsyncChannel.swift +++ b/Sources/NIOCore/AsyncChannel/AsyncChannel.swift @@ -301,15 +301,25 @@ public struct NIOAsyncChannel: Sendable { } } + 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 { - self._outbound.finish() - try await self.channel.close().get() + try await self.channel.closeFuture.get() } catch { - if let error = error as? ChannelError, error == .alreadyClosed { - return result - } - throw error + assertionFailure( + """ + The channel's closeFuture should never be failed, but it was failed with error: \(error). + This is an error in the channel's implementation. + Refer to `Channel/closeFuture`'s documentation for more information. + """ + ) } + return result } } diff --git a/Sources/NIOCore/Channel.swift b/Sources/NIOCore/Channel.swift index 8d0af1fec5..9793102d5e 100644 --- a/Sources/NIOCore/Channel.swift +++ b/Sources/NIOCore/Channel.swift @@ -107,6 +107,12 @@ public protocol Channel: AnyObject, ChannelOutboundInvoker, _NIOPreconcurrencySe var allocator: ByteBufferAllocator { get } /// The `closeFuture` will fire when the `Channel` has been closed. + /// + /// - Important: This future should never be failed: it signals when the channel has been closed, and this action should not fail, + /// regardless of whether the close happenned cleanly or not. + /// If you are interested in any errors thrown during `close` to diagnose any unclean channel closures, you + /// should instead use the future returned from ``ChannelOutboundInvoker/close(mode:file:line:)-7hlgf`` + /// or pass a promise via ``ChannelOutboundInvoker/close(mode:promise:)``. var closeFuture: EventLoopFuture { get } /// The `ChannelPipeline` which handles all I/O events and requests associated with this `Channel`.