diff --git a/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift b/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift index 6a42cc5c..0c5cb296 100644 --- a/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift +++ b/Libraries/ConnectNIO/Internal/ConnectStreamChannelHandler.swift @@ -26,6 +26,7 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck private var context: NIOCore.ChannelHandlerContext? private var isClosed = false + private var hasResponded = false private var pendingClose: NIOHTTP1.HTTPClientRequestPart? private var pendingData = Foundation.Data() private var receivedStatus: NIOHTTP1.HTTPResponseStatus? @@ -81,6 +82,7 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck } self.closeConnection() + self.hasResponded = true self.responseCallbacks.receiveClose(.canceled, [:], ConnectError.canceled()) } } @@ -149,6 +151,7 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck self.responseCallbacks.receiveResponseData(Data(buffer: byteBuffer)) context.fireChannelRead(data) case .end(let trailers): + self.hasResponded = true self.responseCallbacks.receiveClose( self.receivedStatus.map { .fromNIOStatus($0) } ?? .ok, trailers.map { .fromNIOHeaders($0) } ?? [:], @@ -167,7 +170,22 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck } func channelInactive(context: ChannelHandlerContext) { + let shouldNotify = !self.hasResponded self.closeConnection() + if shouldNotify { + self.hasResponded = true + self.responseCallbacks.receiveClose( + .unavailable, + [:], + ConnectError( + code: .unavailable, + message: "Channel became inactive", + exception: nil, + details: [], + metadata: [:] + ) + ) + } context.fireChannelInactive() } @@ -176,6 +194,7 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck return } + self.hasResponded = true self.responseCallbacks.receiveClose( .fromHTTPStatus((error as NSError).code), [:], @@ -190,6 +209,7 @@ final class ConnectStreamChannelHandler: NIOCore.ChannelInboundHandler, @uncheck } self.closeConnection() + self.hasResponded = true self.responseCallbacks.receiveClose(.deadlineExceeded, [:], ConnectError.deadlineExceeded()) } } diff --git a/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift b/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift index 215e958e..6e34ab9a 100644 --- a/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift +++ b/Libraries/ConnectNIO/Internal/ConnectUnaryChannelHandler.swift @@ -27,6 +27,7 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke private var context: NIOCore.ChannelHandlerContext? private var isClosed = false + private var hasResponded = false private var receivedHead: NIOHTTP1.HTTPResponseHead? private var receivedData: Foundation.Data? private var receivedEnd: NIOHTTP1.HTTPHeaders? @@ -51,6 +52,7 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke } self.closeConnection() + self.hasResponded = true self.onResponse(HTTPResponse( code: .canceled, headers: [:], @@ -143,6 +145,7 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke context.fireChannelRead(data) case .end(let trailers): self.receivedEnd = trailers + self.hasResponded = true self.onResponse(self.createResponse(error: nil)) self.onMetrics(.init(taskMetrics: nil)) self.closeConnection() @@ -158,7 +161,25 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke } func channelInactive(context: ChannelHandlerContext) { + let shouldNotify = !self.hasResponded self.closeConnection() + if shouldNotify { + self.hasResponded = true + self.onResponse(.init( + code: .unavailable, + headers: [:], + message: nil, + trailers: [:], + error: ConnectError( + code: .unavailable, + message: "Channel became inactive", + exception: nil, + details: [], + metadata: [:] + ), + tracingInfo: nil + )) + } context.fireChannelInactive() } @@ -167,6 +188,7 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke return } + self.hasResponded = true self.onResponse(self.createResponse(error: error)) self.closeConnection() } @@ -177,6 +199,7 @@ final class ConnectUnaryChannelHandler: NIOCore.ChannelInboundHandler, @unchecke } self.closeConnection() + self.hasResponded = true self.onResponse(.init( code: .deadlineExceeded, headers: [:],