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

Code before:
                closePromise.setOrCascade(to: promise)
                self.outboundCloseState = .pending(closePromise)
                closeResult = .pending
            case .readyForClose:
                preconditionFailure(
                    "We are in .readyForClose state but we still have pending writes. This should never happen."
                )
            case .closed:
                preconditionFailure(
                    "We are in .closed state but we still have pending writes. This should never happen."
                )
            }

        }

        return closeResult
    }

    /// Signal that the `Channel` is closed.
    func closeComplete(_ error: Error? = nil) {
        assert(self.isOpen)
        assert(self.isEmpty)

        switch self.outboundCloseState {
        case .readyForClose(let closePromise):
            self.outboundCloseState = .closed
            if let error {
                closePromise?.fail(error)
            } else {
                closePromise?.succeed(())
            }
        case .closed:
            ()  // nothing to do
        case .open, .pending:
            preconditionFailure("close complete called on channel in unexpected state: \(self.outboundCloseState)")
        }
    }

    /// Initialize with a pre-allocated array of IO vectors and storage references. We pass in these pre-allocated
    /// objects to save allocations. They can be safely be re-used for all `Channel`s on a given `EventLoop` as an
    /// `EventLoop` always runs on one and the same thread. That means that there can't be any writes of more than
    /// one `Channel` on the same `EventLoop` at the same time.
    ///
    /// - Parameters:
    ///   - bufferPool: Pool of buffers to be used for iovecs and storage references
    init(bufferPool: Pool<PooledBuffer>) {
        self.bufferPool = bufferPool
    }
}

internal enum CloseState {

Review comment (suggestion):
Ok, let's return the promise here rather than have this type complete it. It's a touch easier for the channel to manage things by completing things itself, rather than having this type do it, because the channel can ensure the state is squared away before making the outcall.

Code after:
                    self = .closed(promise)
                } else {
                    assertionFailure(
                        "We are in .closed state but we still have pending writes. This should never happen."
                    )
                    self = .errored(NIOReportedPendingWritesInInvalidState(), promise)
                }
            }
        }
    }

    /// Signal the intention to close. Takes a promise which will be returned for completing when pending writes are dealt with
    ///
    /// - Parameters:
    ///   - promise: Optionally an `EventLoopPromise` which is stored and is returned to be completed by the caller once
    ///              all outstanding writes have been dealt with or an error condition is encountered.
    func closeOutbound(_ promise: EventLoopPromise<Void>?) -> CloseOutboundResult {
        assert(self.isOpen)

        // Update our internal state
        switch self.outboundCloseState {
        case .open:
            if self.isEmpty {
                self.outboundCloseState = .readyForClose(promise)
            } else {
                self.outboundCloseState = .pending(promise)
            }
        case .readyForClose(var closePromise):
            closePromise.setOrCascade(to: promise)
            self.outboundCloseState = .readyForClose(closePromise)
        case .pending(var closePromise):
            closePromise.setOrCascade(to: promise)
            if self.isEmpty {
                self.outboundCloseState = .readyForClose(closePromise)
            } else {
                self.outboundCloseState = .pending(closePromise)
            }
        case .closed:
            ()
        }

        // Decide on the result
        let result = CloseOutboundResult(self.outboundCloseState, self.isEmpty, promise)

        return result
    }

    /// Initialize with a pre-allocated array of IO vectors and storage references. We pass in these pre-allocated
    /// objects to save allocations. They can be safely be re-used for all `Channel`s on a given `EventLoop` as an
    /// `EventLoop` always runs on one and the same thread. That means that there can't be any writes of more than
    /// one `Channel` on the same `EventLoop` at the same time.