Repository: apple/swift-nio
File: Sources/NIOPosix/PendingWritesManager.swift (Swift)

Code before:

    /// Trigger a vector write operation. In other words: Write multiple contiguous arrays of bytes.
    ///
    /// - Parameters:
    ///   - operation: The vector write operation to use. Usually `writev`.
    private func triggerVectorBufferWrite(
        _ operation: (UnsafeBufferPointer<IOVector>) throws -> IOResult<Int>
    ) throws -> OneWriteOperationResult {
        assert(
            self.state.isFlushPending && !self.state.isEmpty && self.isOpen,
            "vector write called in illegal state: flush pending: \(self.state.isFlushPending), empty: \(self.state.isEmpty), isOpen: \(self.isOpen)"
        )
        let result = try doPendingWriteVectorOperation(
            pending: self.state,
            bufferPool: bufferPool,
            { try operation($0) }
        )
        return self.didWrite(itemCount: result.itemCount, result: result.writeResult)
    }

    /// Fail all the outstanding writes.
    func failAll(error: Error, close: Bool) {
        if close {
            assert(self.isOpen)
            self.isOpen = false
            self.state.removeAll()?.fail(error)
            assert(self.state.isEmpty)
            switch self.outboundCloseState {
            case .open, .closed:
                self.outboundCloseState = .closed
            case .pending(let closePromise), .readyForClose(let closePromise):
                closePromise?.fail(error)
                self.outboundCloseState = .closed
            }
        }
    }

    /// Signal the intention to close. Takes a promise which MUST be completed via a call to `closeComplete`
    ///
    /// - Parameters:
    ///   - promise: Optionally an `EventLoopPromise` that will be succeeded once all outstanding writes have been dealt with
    func close(_ promise: EventLoopPromise<Void>?) -> CloseResult {
        assert(self.isOpen)

        if self.isEmpty {
            switch self.outboundCloseState {
            case .open:
                self.outboundCloseState = .readyForClose(promise)
            case .readyForClose(var closePromise), .pending(var closePromise):
                closePromise.setOrCascade(to: promise)
                self.outboundCloseState = .readyForClose(closePromise)

Review comment (bug):
General rule of thumb: update your state before making outcalls. Failing the promise here might lead to calling back into this type again and we haven't fully reconciled our state yet. We should grab the promise and fail it afterwards.

Code after:
    /// - Parameters:
    ///   - operation: The vector write operation to use. Usually `writev`.
    private func triggerVectorBufferWrite(
        _ operation: (UnsafeBufferPointer<IOVector>) throws -> IOResult<Int>
    ) throws -> OneWriteOperationResult {
        assert(
            self.state.isFlushPending && !self.state.isEmpty && self.isOpen,
            "vector write called in illegal state: flush pending: \(self.state.isFlushPending), empty: \(self.state.isEmpty), isOpen: \(self.isOpen)"
        )
        let result = try doPendingWriteVectorOperation(
            pending: self.state,
            bufferPool: bufferPool,
            { try operation($0) }
        )
        return self.didWrite(itemCount: result.itemCount, result: result.writeResult)
    }

    /// Fail all the outstanding writes.
    func failAll(error: Error) -> EventLoopPromise<Void>? {
        assert(self.isOpen)

        let promise: EventLoopPromise<Void>?
        self.isOpen = false
        switch self.outboundCloseState {
        case .open, .closed:
            self.outboundCloseState = .closed
            promise = nil
        case .pending(let closePromise), .readyForClose(let closePromise):
            self.outboundCloseState = .closed
            promise = closePromise
        }

        self.state.removeAll()?.fail(error)

        assert(self.state.isEmpty)
        return promise
    }

    // The result of calling `closeOutbound`
    enum CloseOutboundResult {
        case pending
        case readyForClose(EventLoopPromise<Void>?)
        case closed(EventLoopPromise<Void>?)
        case errored(Error, EventLoopPromise<Void>?)

        init(_ closeState: CloseState, _ isEmpty: Bool, _ promise: EventLoopPromise<Void>?) {
            switch closeState {
            case .open:
                assertionFailure(
                    "We are in .open state after being asked to close. This should never happen."
                )