Coverage Report

Created: 2026-10-28 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOPosix/PendingWritesManager.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2024 Apple Inc. and the SwiftNIO project authors
6
// Licensed under Apache License v2.0
7
//
8
// See LICENSE.txt for license information
9
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10
//
11
// SPDX-License-Identifier: Apache-2.0
12
//
13
//===----------------------------------------------------------------------===//
14
15
#if !os(WASI)
16
17
import Atomics
18
import CNIOLinux
19
import CNIOOpenBSD
20
import NIOCore
21
22
private struct PendingStreamWrite {
23
    var data: IOData
24
    var promise: Optional<EventLoopPromise<Void>>
25
}
26
27
/// Write result is `.couldNotWriteEverything` but we have no more writes to perform.
28
public struct NIOReportedIncompleteWritesWhenNoMoreToPerform: Error {}
29
30
/// Close result is `.open`.
31
public struct NIOReportedOpenAfterClose: Error {}
32
33
/// There are buffered writes after it should have been cleared, `.readyForClose` or `.closed` state
34
public struct NIOReportedPendingWritesInInvalidState: Error {}
35
36
/// Does the setup required to issue a writev.
37
///
38
/// - Parameters:
39
///    - pending: The currently pending writes.
40
///    - bufferPool: Pool of buffers to use for iovecs and storageRefs
41
///    - body: The function that actually does the vector write (usually `writev`).
42
/// - Returns: A tuple of the number of items attempted to write and the result of the write operation.
43
private func doPendingWriteVectorOperation(
44
    pending: PendingStreamWritesState,
45
    bufferPool: Pool<PooledBuffer>,
46
    _ body: (UnsafeBufferPointer<IOVector>) throws -> IOResult<Int>
47
0
) throws -> (itemCount: Int, writeResult: IOResult<Int>) {
48
0
    let buffer = bufferPool.get()
49
0
    defer { bufferPool.put(buffer) }
50
0
51
0
    return try buffer.withUnsafePointers { iovecs, storageRefs in
52
0
        // Clamp the number of writes we're willing to issue to the limit for writev.
53
0
        var count = min(iovecs.count, storageRefs.count)
54
0
        count = min(pending.flushedChunks, count)
55
0
56
0
        // the numbers of storage refs that we need to decrease later.
57
0
        var numberOfUsedStorageSlots = 0
58
0
        var toWrite: Int = 0
59
0
60
0
        loop: for i in 0..<count {
61
0
            let p = pending[i]
62
0
            switch p.data {
63
0
            case .byteBuffer(let buffer):
64
0
                // Must not write more than Int32.max in one go.
65
0
                guard (numberOfUsedStorageSlots == 0) || (Socket.writevLimitBytes - toWrite >= buffer.readableBytes)
66
0
                else {
67
0
                    break loop
68
0
                }
69
0
                let toWriteForThisBuffer = min(Socket.writevLimitBytes, buffer.readableBytes)
70
0
                toWrite += numericCast(toWriteForThisBuffer)
71
0
72
0
                buffer.withUnsafeReadableBytesWithStorageManagement { ptr, storageRef in
73
0
                    storageRefs[i] = storageRef.retain()
74
0
                    iovecs[i] = IOVector(
75
0
                        iov_base: UnsafeMutableRawPointer(mutating: ptr.baseAddress!),
76
0
                        iov_len: numericCast(toWriteForThisBuffer)
77
0
                    )
78
0
                }
79
0
                numberOfUsedStorageSlots += 1
80
0
            case .fileRegion:
81
0
                assert(numberOfUsedStorageSlots != 0, "first item in doPendingWriteVectorOperation was a FileRegion")
82
0
                // We found a FileRegion so stop collecting
83
0
                break loop
84
0
            }
85
0
        }
86
0
        defer {
87
0
            for i in 0..<numberOfUsedStorageSlots {
88
0
                storageRefs[i].release()
89
0
            }
90
0
        }
91
0
        let result = try body(UnsafeBufferPointer(start: iovecs.baseAddress!, count: numberOfUsedStorageSlots))
92
0
        // if we hit a limit, we really wanted to write more than we have so the caller should retry us
93
0
        return (numberOfUsedStorageSlots, result)
94
0
    }
95
0
}
96
97
/// The result of a single write operation, usually `write`, `sendfile` or `writev`.
98
internal enum OneWriteOperationResult {
99
    /// Wrote everything asked.
100
    case writtenCompletely
101
102
    /// Wrote some portion of what was asked.
103
    case writtenPartially
104
105
    /// Could not write as doing that would have blocked.
106
    case wouldBlock
107
}
108
109
/// The result of trying to write all the outstanding flushed data. That naturally includes all `ByteBuffer`s and
110
/// `FileRegions` and the individual writes have potentially been retried (see `WriteSpinOption`).
111
internal struct OverallWriteResult {
112
    enum WriteOutcome: Equatable {
113
        /// Wrote all the data that was flushed. When receiving this result, we can unsubscribe from 'writable' notification.
114
        case writtenCompletely(WrittenCompletelyResult)
115
116
        /// Could not write everything. Before attempting further writes the eventing system should send a 'writable' notification.
117
        case couldNotWriteEverything
118
119
        /// The resulting status of a `PendingWritesManager` after a completely-written write
120
        ///
121
        /// This type is subtly different to `CloseState` so that it only surfaces the close promise when the caller
122
        /// is expected to fulfill it
123
        internal enum WrittenCompletelyResult: Equatable {
124
            case open
125
            case readyForClose(EventLoopPromise<Void>?)
126
            case closed(EventLoopPromise<Void>?)
127
128
0
            init(_ closeState: CloseState) {
129
0
                switch closeState {
130
0
                case .open:
131
0
                    self = .open
132
0
                case .pending(let closePromise), .readyForClose(let closePromise):
133
0
                    self = .readyForClose(closePromise)
134
0
                case .closed:
135
0
                    self = .closed(nil)
136
0
                }
137
0
            }
138
        }
139
    }
140
141
    internal var writeResult: WriteOutcome
142
    internal var writabilityChange: Bool
143
}
144
145
/// This holds the states of the currently pending stream writes. The core is a `MarkedCircularBuffer` which holds all the
146
/// writes and a mark up until the point the data is flushed.
147
///
148
/// The most important operations on this object are:
149
///  - `append` to add an `IOData` to the list of pending writes.
150
///  - `markFlushCheckpoint` which sets a flush mark on the current position of the `MarkedCircularBuffer`. All the items before the checkpoint will be written eventually.
151
///  - `didWrite` when a number of bytes have been written.
152
///  - `failAll` if for some reason all outstanding writes need to be discarded and the corresponding `EventLoopPromise` needs to be failed.
153
private struct PendingStreamWritesState {
154
0
    private var pendingWrites = MarkedCircularBuffer<PendingStreamWrite>(initialCapacity: 16)
155
0
    public private(set) var bytes: Int64 = 0
156
157
0
    public var flushedChunks: Int {
158
0
        self.pendingWrites.markedElementIndex.map {
159
0
            self.pendingWrites.distance(from: self.pendingWrites.startIndex, to: $0) + 1
160
0
        } ?? 0
161
0
    }
162
163
    /// Subtract `bytes` from the number of outstanding bytes to write.
164
0
    private mutating func subtractOutstanding(bytes: Int) {
165
0
        assert(self.bytes >= bytes, "allegedly written more bytes (\(bytes)) than outstanding (\(self.bytes))")
166
0
        self.bytes -= numericCast(bytes)
167
0
    }
168
169
    /// Indicates that the first outstanding write was written in its entirety.
170
    ///
171
    /// - Returns: The `EventLoopPromise` of the write or `nil` if none was provided. The promise needs to be fulfilled by the caller.
172
    ///
173
0
    private mutating func fullyWrittenFirst() -> EventLoopPromise<Void>? {
174
0
        let first = self.pendingWrites.removeFirst()
175
0
        self.subtractOutstanding(bytes: first.data.readableBytes)
176
0
        return first.promise
177
0
    }
178
179
    /// Indicates that the first outstanding object has been partially written.
180
    ///
181
    /// - Parameters:
182
    ///   - bytes: How many bytes of the item were written.
183
0
    private mutating func partiallyWrittenFirst(bytes: Int) {
184
0
        self.pendingWrites[self.pendingWrites.startIndex].data.moveReaderIndex(forwardBy: bytes)
185
0
        self.subtractOutstanding(bytes: bytes)
186
0
    }
187
188
    /// Initialize a new, empty `PendingWritesState`.
189
0
    public init() {}
190
191
    /// Check if there are no outstanding writes.
192
0
    public var isEmpty: Bool {
193
0
        if self.pendingWrites.isEmpty {
194
0
            assert(self.bytes == 0)
195
0
            assert(!self.pendingWrites.hasMark)
196
0
            return true
197
0
        } else {
198
0
            assert(self.bytes >= 0)
199
0
            return false
200
0
        }
201
0
    }
202
203
    /// Add a new write and optionally the corresponding promise to the list of outstanding writes.
204
0
    public mutating func append(_ chunk: PendingStreamWrite) {
205
0
        self.pendingWrites.append(chunk)
206
0
        switch chunk.data {
207
0
        case .byteBuffer(let buffer):
208
0
            self.bytes += numericCast(buffer.readableBytes)
209
0
        case .fileRegion(let fileRegion):
210
0
            self.bytes += numericCast(fileRegion.readableBytes)
211
0
        }
212
0
    }
213
214
    /// Get the outstanding write at `index`.
215
0
    public subscript(index: Int) -> PendingStreamWrite {
216
0
        self.pendingWrites[self.pendingWrites.index(self.pendingWrites.startIndex, offsetBy: index)]
217
0
    }
218
219
    /// Mark the flush checkpoint.
220
    ///
221
    /// All writes before this checkpoint will eventually be written to the socket.
222
0
    public mutating func markFlushCheckpoint() {
223
0
        self.pendingWrites.mark()
224
0
    }
225
226
    /// Indicate that a write has happened, this may be a write of multiple outstanding writes (using for example `writev`).
227
    ///
228
    /// - warning: The promises will be returned in order. If one of those promises does for example close the `Channel` we might see subsequent writes fail out of order. Example: Imagine the user issues three writes: `A`, `B` and `C`. Imagine that `A` and `B` both get successfully written in one write operation but the user closes the `Channel` in `A`'s callback. Then overall the promises will be fulfilled in this order: 1) `A`: success 2) `C`: error 3) `B`: success. Note how `B` and `C` get fulfilled out of order.
229
    ///
230
    /// - Parameters:
231
    ///   - writeResult: The result of the write operation.
232
    /// - Returns: A tuple of a promise and a `OneWriteResult`. The promise is the first promise that needs to be notified of the write result.
233
    ///            This promise will cascade the result to all other promises that need notifying. If no promises need to be notified, will be `nil`.
234
    ///            The write result will indicate whether we were able to write everything or not.
235
    public mutating func didWrite(
236
        itemCount: Int,
237
        result writeResult: IOResult<Int>
238
0
    ) -> (EventLoopPromise<Void>?, OneWriteOperationResult) {
239
0
        switch writeResult {
240
0
        case .wouldBlock(0):
241
0
            return (nil, .wouldBlock)
242
0
        case .processed(let written), .wouldBlock(let written):
243
0
            var promise0: EventLoopPromise<Void>?
244
0
            assert(written >= 0, "allegedly written a negative amount of bytes: \(written)")
245
0
            var unaccountedWrites = written
246
0
            for _ in 0..<itemCount {
247
0
                let headItemReadableBytes = self.pendingWrites.first!.data.readableBytes
248
0
                if unaccountedWrites >= headItemReadableBytes {
249
0
                    unaccountedWrites -= headItemReadableBytes
250
0
                    // we wrote at least the whole head item, so drop it and succeed the promise
251
0
                    if let promise = self.fullyWrittenFirst() {
252
0
                        if let p = promise0 {
253
0
                            p.futureResult.cascade(to: promise)
254
0
                        } else {
255
0
                            promise0 = promise
256
0
                        }
257
0
                    }
258
0
                } else {
259
0
                    // we could only write a part of the head item, so don't drop it but remember what we wrote
260
0
                    self.partiallyWrittenFirst(bytes: unaccountedWrites)
261
0
262
0
                    // may try again depending on the writeSpinCount
263
0
                    return (promise0, .writtenPartially)
264
0
                }
265
0
            }
266
0
            assert(
267
0
                unaccountedWrites == 0,
268
0
                "after doing all the accounting for the byte written, \(unaccountedWrites) bytes of unaccounted writes remain."
269
0
            )
270
0
            return (promise0, .writtenCompletely)
271
0
        }
272
0
    }
273
274
    /// Is there a pending flush?
275
0
    public var isFlushPending: Bool {
276
0
        self.pendingWrites.hasMark
277
0
    }
278
279
    /// Remove all pending writes and return a `EventLoopPromise` which will cascade notifications to all.
280
    ///
281
    /// - warning: See the warning for `didWrite`.
282
    ///
283
    /// - Returns: promise that needs to be failed, or `nil` if there were no pending writes.
284
0
    public mutating func removeAll() -> EventLoopPromise<Void>? {
285
0
        var promise0: EventLoopPromise<Void>?
286
0
287
0
        while !self.pendingWrites.isEmpty {
288
0
            if let p = self.fullyWrittenFirst() {
289
0
                if let promise = promise0 {
290
0
                    promise.futureResult.cascade(to: p)
291
0
                } else {
292
0
                    promise0 = p
293
0
                }
294
0
            }
295
0
        }
296
0
        return promise0
297
0
    }
298
299
    /// Returns the best mechanism to write pending data at the current point in time.
300
0
    var currentBestWriteMechanism: WriteMechanism {
301
0
        switch self.flushedChunks {
302
0
        case 0:
303
0
            return .nothingToBeWritten
304
0
        case 1:
305
0
            switch self.pendingWrites.first!.data {
306
0
            case .byteBuffer:
307
0
                return .scalarBufferWrite
308
0
            case .fileRegion:
309
0
                return .scalarFileWrite
310
0
            }
311
0
        default:
312
0
            let startIndex = self.pendingWrites.startIndex
313
0
            switch (
314
0
                self.pendingWrites[startIndex].data,
315
0
                self.pendingWrites[self.pendingWrites.index(after: startIndex)].data
316
0
            ) {
317
0
            case (.byteBuffer, .byteBuffer):
318
0
                return .vectorBufferWrite
319
0
            case (.byteBuffer, .fileRegion):
320
0
                return .scalarBufferWrite
321
0
            case (.fileRegion, _):
322
0
                return .scalarFileWrite
323
0
            }
324
0
        }
325
0
    }
326
}
327
328
/// This class manages the writing of pending writes to stream sockets. The state is held in a `PendingWritesState`
329
/// value. The most important purpose of this object is to call `write`, `writev` or `sendfile` depending on the
330
/// currently pending writes.
331
final class PendingStreamWritesManager: PendingWritesManager {
332
0
    private var state = PendingStreamWritesState()
333
    private let bufferPool: Pool<PooledBuffer>
334
335
0
    internal var waterMark: ChannelOptions.Types.WriteBufferWaterMark = ChannelOptions.Types.WriteBufferWaterMark(
336
0
        low: 32 * 1024,
337
0
        high: 64 * 1024
338
0
    )
339
0
    internal let channelWritabilityFlag = ManagedAtomic(true)
340
0
    internal var publishedWritability = true
341
342
0
    internal var writeSpinCount: UInt = 16
343
344
0
    private(set) var isOpen = true
345
346
0
    private(set) var outboundCloseState: CloseState = .open
347
348
    /// Mark the flush checkpoint.
349
0
    func markFlushCheckpoint() {
350
0
        self.state.markFlushCheckpoint()
351
0
    }
352
353
    /// Is there a flush pending?
354
0
    var isFlushPending: Bool {
355
0
        self.state.isFlushPending
356
0
    }
357
358
    /// Are there any outstanding writes currently?
359
0
    var isEmpty: Bool {
360
0
        self.state.isEmpty
361
0
    }
362
363
0
    var bufferedBytes: Int64 {
364
0
        self.state.bytes
365
0
    }
366
367
    /// Add a pending write alongside its promise.
368
    ///
369
    /// - Parameters:
370
    ///   - data: The `IOData` to write.
371
    ///   - promise: Optionally an `EventLoopPromise` that will get the write operation's result
372
    /// - result: If the `Channel` is still writable after adding the write of `data`.
373
0
    func add(data: IOData, promise: EventLoopPromise<Void>?) -> Bool {
374
0
        assert(self.isOpen)
375
0
        self.state.append(PendingStreamWrite(data: data, promise: promise))
376
0
377
0
        if self.state.bytes > waterMark.high
378
0
            && channelWritabilityFlag.compareExchange(expected: true, desired: false, ordering: .relaxed).exchanged
379
0
        {
380
0
            // Returns false to signal the Channel became non-writable and we need to notify the user.
381
0
            self.publishedWritability = false
382
0
            return false
383
0
        }
384
0
        return true
385
0
    }
386
387
    /// Returns the best mechanism to write pending data at the current point in time.
388
0
    var currentBestWriteMechanism: WriteMechanism {
389
0
        self.state.currentBestWriteMechanism
390
0
    }
391
392
    /// Triggers the appropriate write operation. This is a fancy way of saying trigger either `write`, `writev` or
393
    /// `sendfile`.
394
    ///
395
    /// - Parameters:
396
    ///   - scalarBufferWriteOperation: An operation that writes a single, contiguous array of bytes (usually `write`).
397
    ///   - vectorBufferWriteOperation: An operation that writes multiple contiguous arrays of bytes (usually `writev`).
398
    ///   - scalarFileWriteOperation: An operation that writes a region of a file descriptor (usually `sendfile`).
399
    /// - Returns: The `OneWriteOperationResult` and whether the `Channel` is now writable.
400
    func triggerAppropriateWriteOperations(
401
        scalarBufferWriteOperation: (UnsafeRawBufferPointer) throws -> IOResult<Int>,
402
        vectorBufferWriteOperation: (UnsafeBufferPointer<IOVector>) throws -> IOResult<Int>,
403
        scalarFileWriteOperation: (CInt, Int, Int) throws -> IOResult<Int>
404
0
    ) throws -> OverallWriteResult {
405
0
        try self.triggerWriteOperations { writeMechanism in
406
0
            switch writeMechanism {
407
0
            case .scalarBufferWrite:
408
0
                return try triggerScalarBufferWrite({ try scalarBufferWriteOperation($0) })
409
0
            case .vectorBufferWrite:
410
0
                return try triggerVectorBufferWrite({ try vectorBufferWriteOperation($0) })
411
0
            case .scalarFileWrite:
412
0
                return try triggerScalarFileWrite({ try scalarFileWriteOperation($0, $1, $2) })
413
0
            case .nothingToBeWritten:
414
0
                assertionFailure("called \(#function) with nothing available to be written")
415
0
                return .writtenCompletely
416
0
            }
417
0
        }
418
0
    }
419
420
    /// To be called after a write operation (usually selected and run by `triggerAppropriateWriteOperation`) has
421
    /// completed.
422
    ///
423
    /// - Parameters:
424
    ///   - itemCount: The number of items we tried to write.
425
    ///   - result: The result of the write operation.
426
0
    private func didWrite(itemCount: Int, result: IOResult<Int>) -> OneWriteOperationResult {
427
0
        let (promise, result) = self.state.didWrite(itemCount: itemCount, result: result)
428
0
429
0
        if self.state.bytes < waterMark.low {
430
0
            channelWritabilityFlag.store(true, ordering: .relaxed)
431
0
        }
432
0
433
0
        promise?.succeed(())
434
0
        return result
435
0
    }
436
437
    /// Trigger a write of a single `ByteBuffer` (usually using `write(2)`).
438
    ///
439
    /// - Parameters:
440
    ///   - operation: An operation that writes a single, contiguous array of bytes (usually `write`).
441
    private func triggerScalarBufferWrite(
442
        _ operation: (UnsafeRawBufferPointer) throws -> IOResult<Int>
443
0
    ) throws -> OneWriteOperationResult {
444
0
        assert(
445
0
            self.state.isFlushPending && !self.state.isEmpty && self.isOpen,
446
0
            "single write called in illegal state: flush pending: \(self.state.isFlushPending), empty: \(self.state.isEmpty), isOpen: \(self.isOpen)"
447
0
        )
448
0
449
0
        switch self.state[0].data {
450
0
        case .byteBuffer(let buffer):
451
0
            return self.didWrite(itemCount: 1, result: try buffer.withUnsafeReadableBytes({ try operation($0) }))
452
0
        case .fileRegion:
453
0
            preconditionFailure("called \(#function) but first item to write was a FileRegion")
454
0
        }
455
0
    }
456
457
    /// Trigger a write of a single `FileRegion` (usually using `sendfile(2)`).
458
    ///
459
    /// - Parameters:
460
    ///   - operation: An operation that writes a region of a file descriptor.
461
    private func triggerScalarFileWrite(
462
        _ operation: (CInt, Int, Int) throws -> IOResult<Int>
463
0
    ) throws -> OneWriteOperationResult {
464
0
        assert(
465
0
            self.state.isFlushPending && !self.state.isEmpty && self.isOpen,
466
0
            "single write called in illegal state: flush pending: \(self.state.isFlushPending), empty: \(self.state.isEmpty), isOpen: \(self.isOpen)"
467
0
        )
468
0
469
0
        switch self.state[0].data {
470
0
        case .fileRegion(let file):
471
0
            let readerIndex = file.readerIndex
472
0
            let endIndex = file.endIndex
473
0
            let writeResult = try file.fileHandle.withUnsafeFileDescriptor { fd in
474
0
                try operation(fd, readerIndex, endIndex)
475
0
            }
476
0
            return self.didWrite(itemCount: 1, result: writeResult)
477
0
        case .byteBuffer:
478
0
            preconditionFailure("called \(#function) but first item to write was a ByteBuffer")
479
0
        }
480
0
    }
481
482
    /// Trigger a vector write operation. In other words: Write multiple contiguous arrays of bytes.
483
    ///
484
    /// - Parameters:
485
    ///   - operation: The vector write operation to use. Usually `writev`.
486
    private func triggerVectorBufferWrite(
487
        _ operation: (UnsafeBufferPointer<IOVector>) throws -> IOResult<Int>
488
0
    ) throws -> OneWriteOperationResult {
489
0
        assert(
490
0
            self.state.isFlushPending && !self.state.isEmpty && self.isOpen,
491
0
            "vector write called in illegal state: flush pending: \(self.state.isFlushPending), empty: \(self.state.isEmpty), isOpen: \(self.isOpen)"
492
0
        )
493
0
        let result = try doPendingWriteVectorOperation(
494
0
            pending: self.state,
495
0
            bufferPool: bufferPool,
496
0
            { try operation($0) }
497
0
        )
498
0
        return self.didWrite(itemCount: result.itemCount, result: result.writeResult)
499
0
    }
500
501
    /// Fail all the outstanding writes.
502
0
    func failAll(error: Error) -> EventLoopPromise<Void>? {
503
0
        let promise: EventLoopPromise<Void>?
504
0
        self.isOpen = false
505
0
        switch self.outboundCloseState {
506
0
        case .open, .closed:
507
0
            self.outboundCloseState = .closed
508
0
            promise = nil
509
0
        case .pending(let closePromise), .readyForClose(let closePromise):
510
0
            self.outboundCloseState = .closed
511
0
            promise = closePromise
512
0
        }
513
0
514
0
        self.state.removeAll()?.fail(error)
515
0
516
0
        assert(self.state.isEmpty)
517
0
        return promise
518
0
    }
519
520
    // The result of calling `closeOutbound`
521
    enum CloseOutboundResult {
522
        case pending
523
        case readyForClose(EventLoopPromise<Void>?)
524
        case closed(EventLoopPromise<Void>?)
525
        case errored(Error, EventLoopPromise<Void>?)
526
527
0
        init(_ closeState: CloseState, _ isEmpty: Bool, _ promise: EventLoopPromise<Void>?) {
528
0
            switch closeState {
529
0
            case .open:
530
0
                assertionFailure(
531
0
                    "We are in .open state after being asked to close. This should never happen."
532
0
                )
533
0
                self = .errored(NIOReportedOpenAfterClose(), promise)
534
0
            case .pending:
535
0
                // `promise` has already been taken care of in the pending state for later completion
536
0
                self = .pending
537
0
            case .readyForClose(let closePromise):
538
0
                if isEmpty {
539
0
                    self = .readyForClose(closePromise)
540
0
                } else {
541
0
                    assertionFailure(
542
0
                        "We are in .readyForClose state but we still have pending writes. This should never happen."
543
0
                    )
544
0
                    // `promise` has already been cascaded off `closePromise`
545
0
                    self = .errored(NIOReportedPendingWritesInInvalidState(), closePromise)
546
0
                }
547
0
            case .closed:
548
0
                if isEmpty {
549
0
                    self = .closed(promise)
550
0
                } else {
551
0
                    assertionFailure(
552
0
                        "We are in .closed state but we still have pending writes. This should never happen."
553
0
                    )
554
0
                    self = .errored(NIOReportedPendingWritesInInvalidState(), promise)
555
0
                }
556
0
            }
557
0
        }
558
    }
559
560
    /// Signal the intention to close. Takes a promise which will be returned for completing when pending writes are dealt with
561
    ///
562
    /// - Parameters:
563
    ///   - promise: Optionally an `EventLoopPromise` which is stored and is returned to be completed by the caller once
564
    ///              all outstanding writes have been dealt with or an error condition is encountered.
565
0
    func closeOutbound(_ promise: EventLoopPromise<Void>?) -> CloseOutboundResult {
566
0
        // Update our internal state
567
0
        switch self.outboundCloseState {
568
0
        case .open:
569
0
            if self.isEmpty {
570
0
                self.outboundCloseState = .readyForClose(promise)
571
0
            } else {
572
0
                self.outboundCloseState = .pending(promise)
573
0
            }
574
0
        case .readyForClose(var closePromise):
575
0
            closePromise.setOrCascade(to: promise)
576
0
            self.outboundCloseState = .readyForClose(closePromise)
577
0
        case .pending(var closePromise):
578
0
            closePromise.setOrCascade(to: promise)
579
0
            if self.isEmpty {
580
0
                self.outboundCloseState = .readyForClose(closePromise)
581
0
            } else {
582
0
                self.outboundCloseState = .pending(closePromise)
583
0
            }
584
0
        case .closed:
585
0
            ()
586
0
        }
587
0
588
0
        // Decide on the result
589
0
        let result = CloseOutboundResult(self.outboundCloseState, self.isEmpty, promise)
590
0
        switch result {
591
0
        case .pending, .readyForClose:
592
0
            ()
593
0
        case .closed, .errored:
594
0
            self.isOpen = false
595
0
        }
596
0
597
0
        return result
598
0
    }
599
600
    /// Initialize with a pre-allocated array of IO vectors and storage references. We pass in these pre-allocated
601
    /// objects to save allocations. They can be safely be re-used for all `Channel`s on a given `EventLoop` as an
602
    /// `EventLoop` always runs on one and the same thread. That means that there can't be any writes of more than
603
    /// one `Channel` on the same `EventLoop` at the same time.
604
    ///
605
    /// - Parameters:
606
    ///   - bufferPool: Pool of buffers to be used for iovecs and storage references
607
0
    init(bufferPool: Pool<PooledBuffer>) {
608
0
        self.bufferPool = bufferPool
609
0
    }
610
}
611
612
internal enum WriteMechanism {
613
    case scalarBufferWrite
614
    case vectorBufferWrite
615
    case scalarFileWrite
616
    case nothingToBeWritten
617
}
618
619
internal protocol PendingWritesManager: AnyObject {
620
    var isOpen: Bool { get }
621
    var isEmpty: Bool { get }
622
    var outboundCloseState: CloseState { get }
623
    var isFlushPending: Bool { get }
624
    var writeSpinCount: UInt { get }
625
    var currentBestWriteMechanism: WriteMechanism { get }
626
    var channelWritabilityFlag: ManagedAtomic<Bool> { get }
627
628
    /// Represents the writability state the last time we published a writability change to the `Channel`.
629
    /// This is used in `triggerWriteOperations` to determine whether we need to trigger a writability
630
    /// change.
631
    var publishedWritability: Bool { get set }
632
}
633
634
/// Describes the state that a `PendingWritesManager` closure state machine will step through when instructed to close
635
internal enum CloseState {
636
    /// The manager will accept new writes
637
    case open
638
    /// The manager has been asked to close but cannot because its write buffer is not empty
639
    case pending(EventLoopPromise<Void>?)
640
    /// The manager has been asked to close and is ready to be closed because its write buffer is empty
641
    case readyForClose(EventLoopPromise<Void>?)
642
    /// The manager is closed
643
    case closed
644
}
645
646
extension PendingWritesManager {
647
    // This is called from `Channel` API so must be thread-safe.
648
0
    var isWritable: Bool {
649
0
        self.channelWritabilityFlag.load(ordering: .relaxed)
650
0
    }
651
652
    internal func triggerWriteOperations(
653
        triggerOneWriteOperation: (WriteMechanism) throws -> OneWriteOperationResult
654
0
    ) throws -> OverallWriteResult {
655
0
        var result = OverallWriteResult(writeResult: .couldNotWriteEverything, writabilityChange: false)
656
0
657
0
        writeSpinLoop: for _ in 0...self.writeSpinCount {
658
0
            var oneResult: OneWriteOperationResult
659
0
            repeat {
660
0
                guard self.isOpen && self.isFlushPending else {
661
0
                    result.writeResult = .writtenCompletely(.init(self.outboundCloseState))
662
0
                    break writeSpinLoop
663
0
                }
664
0
665
0
                oneResult = try triggerOneWriteOperation(self.currentBestWriteMechanism)
666
0
                if oneResult == .wouldBlock {
667
0
                    break writeSpinLoop
668
0
                }
669
0
            } while oneResult == .writtenCompletely
670
0
        }
671
0
672
0
        // Please note that the re-entrancy protection in `flushNow` expects this code to try to write _all_ the data
673
0
        // that is flushed. If we receive a `flush` whilst processing a previous `flush`, we won't do anything because
674
0
        // we expect this loop to attempt to attempt all writes, even ones that arrive after this method begins to run.
675
0
        //
676
0
        // In other words, don't return `.writtenCompletely` unless you've written everything the PendingWritesManager
677
0
        // knows to be flushed.
678
0
        //
679
0
        // Also, it is very important to not do any outcalls to user code outside of the loop until the `flushNow`
680
0
        // re-entrancy protection is off again.
681
0
682
0
        if !self.publishedWritability {
683
0
            // When we last published a writability change the `Channel` wasn't writable, signal back to the caller
684
0
            // whether we should emit a writability change.
685
0
            result.writabilityChange = self.isWritable
686
0
            self.publishedWritability = result.writabilityChange
687
0
        }
688
0
        return result
689
0
    }
690
}
691
692
extension PendingStreamWritesManager: CustomStringConvertible {
693
0
    var description: String {
694
0
        "PendingStreamWritesManager { isFlushPending: \(self.isFlushPending), "
695
0
            + "writabilityFlag: \(self.channelWritabilityFlag.load(ordering: .relaxed))), state: \(self.state) }"
696
0
    }
697
}
698
#endif  // !os(WASI)