Repository: apple/swift-nio
File: Sources/NIOHTTP1/NIOTypedHTTPServerUpgraderStateMachine.swift (Swift)

Code before:
        }
    }

    @usableFromInline
    enum UnbufferAction {
        case close
        case fireChannelRead(NIOAny)
        case fireChannelReadCompleteAndRemoveHandler
    }

    @inlinable
    mutating func unbuffer() -> UnbufferAction {
        switch self.state {
        case .initial, .awaitingUpgrader, .upgraderReady, .upgrading, .finished:
            preconditionFailure("Invalid state \(self.state)")

        case .unbuffering(var unbuffering):
            self.state = .modifying

            if let element = unbuffering.buffer.popFirst() {
                self.state = .unbuffering(unbuffering)
                switch element {
                case .data(let data):
                    return .fireChannelRead(data)
                case .inputClosed:
                    return .close
                }
            } else {
                self.state = .finished

                return .fireChannelReadCompleteAndRemoveHandler
            }

        case .modifying:
            fatalError("Internal inconsistency in HTTPServerUpgradeStateMachine")

        }
    }

    @usableFromInline
    enum CloseInboundAction {
        case close
        case `continue`
    }

    @inlinable
    mutating func closeInbound() -> CloseInboundAction {
        switch self.state {
        case .initial:
            self.state = .finished
            return .close

Review comment (suggestion):
In this case we shouldn't close the channel. It might be completely valid that the remote send us an upgrade + data on the new protocol format + closed the input all at once. The last input close just indicates a half closure but we **must not** escalate this to a full closure. Instead we should just unbuffer the `inputClosed` here and fire the channel event.

Code after:
        }
    }

    @usableFromInline
    enum UnbufferAction {
        case close
        case fireChannelRead(NIOAny)
        case fireChannelReadCompleteAndRemoveHandler
        case fireInputClosedEvent
    }

    @inlinable
    mutating func unbuffer() -> UnbufferAction {
        switch self.state {
        case .initial, .awaitingUpgrader, .upgraderReady, .upgrading, .finished:
            preconditionFailure("Invalid state \(self.state)")

        case .unbuffering(var unbuffering):
            self.state = .modifying

            if let element = unbuffering.buffer.popFirst() {
                self.state = .unbuffering(unbuffering)
                switch element {
                case .data(let data):
                    return .fireChannelRead(data)
                case .inputClosed:
                    return .fireInputClosedEvent
                }
            } else {
                self.state = .finished

                return .fireChannelReadCompleteAndRemoveHandler
            }

        case .modifying:
            fatalError("Internal inconsistency in HTTPServerUpgradeStateMachine")

        }
    }

    @usableFromInline
    enum InputClosedAction {
        case close
        case `continue`
        case fireInputClosedEvent
    }

    @inlinable
    mutating func inputClosed() -> InputClosedAction {
        switch self.state {
        case .initial: