/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) |