Coverage Report

Created: 2026-10-28 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOCore/ChannelOption.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2018 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
/// A configuration option that can be set on a `Channel` to configure different behaviour.
16
public protocol ChannelOption: Equatable, _NIOPreconcurrencySendable {
17
    /// The type of the `ChannelOption`'s value.
18
    associatedtype Value: Sendable
19
}
20
21
public typealias SocketOptionName = Int32
22
#if (os(Linux) || os(Android)) && !canImport(Musl)
23
public typealias SocketOptionLevel = Int
24
public typealias SocketOptionValue = Int
25
#else
26
public typealias SocketOptionLevel = CInt
27
public typealias SocketOptionValue = CInt
28
#endif
29
30
@available(*, deprecated, renamed: "ChannelOptions.Types.SocketOption")
31
public typealias SocketOption = ChannelOptions.Types.SocketOption
32
33
@available(*, deprecated, renamed: "ChannelOptions.Types.AllocatorOption")
34
public typealias AllocatorOption = ChannelOptions.Types.AllocatorOption
35
36
@available(*, deprecated, renamed: "ChannelOptions.Types.RecvAllocatorOption")
37
public typealias RecvAllocatorOption = ChannelOptions.Types.RecvAllocatorOption
38
39
@available(*, deprecated, renamed: "ChannelOptions.Types.AutoReadOption")
40
public typealias AutoReadOption = ChannelOptions.Types.AutoReadOption
41
42
@available(*, deprecated, renamed: "ChannelOptions.Types.WriteSpinOption")
43
public typealias WriteSpinOption = ChannelOptions.Types.WriteSpinOption
44
45
@available(*, deprecated, renamed: "ChannelOptions.Types.MaxMessagesPerReadOption")
46
public typealias MaxMessagesPerReadOption = ChannelOptions.Types.MaxMessagesPerReadOption
47
48
@available(*, deprecated, renamed: "ChannelOptions.Types.BacklogOption")
49
public typealias BacklogOption = ChannelOptions.Types.BacklogOption
50
51
@available(*, deprecated, renamed: "ChannelOptions.Types.DatagramVectorReadMessageCountOption")
52
public typealias DatagramVectorReadMessageCountOption = ChannelOptions.Types.DatagramVectorReadMessageCountOption
53
54
@available(*, deprecated, renamed: "ChannelOptions.Types.WriteBufferWaterMark")
55
public typealias WriteBufferWaterMark = ChannelOptions.Types.WriteBufferWaterMark
56
57
@available(*, deprecated, renamed: "ChannelOptions.Types.WriteBufferWaterMarkOption")
58
public typealias WriteBufferWaterMarkOption = ChannelOptions.Types.WriteBufferWaterMarkOption
59
60
@available(*, deprecated, renamed: "ChannelOptions.Types.ConnectTimeoutOption")
61
public typealias ConnectTimeoutOption = ChannelOptions.Types.ConnectTimeoutOption
62
63
@available(*, deprecated, renamed: "ChannelOptions.Types.AllowRemoteHalfClosureOption")
64
public typealias AllowRemoteHalfClosureOption = ChannelOptions.Types.AllowRemoteHalfClosureOption
65
66
extension ChannelOptions {
67
    public enum Types: Sendable {
68
69
        /// `SocketOption` allows users to specify configuration settings that are directly applied to the underlying socket file descriptor.
70
        ///
71
        /// Valid options are typically found in the various man pages like `man 4 tcp`.
72
        public struct SocketOption: ChannelOption, Equatable, Sendable {
73
            public typealias Value = (SocketOptionValue)
74
75
            public var optionLevel: NIOBSDSocket.OptionLevel
76
            public var optionName: NIOBSDSocket.Option
77
78
            public var level: SocketOptionLevel {
79
0
                get {
80
0
                    SocketOptionLevel(optionLevel.rawValue)
81
0
                }
82
0
                set {
83
0
                    self.optionLevel = NIOBSDSocket.OptionLevel(rawValue: CInt(newValue))
84
0
                }
85
            }
86
            public var name: SocketOptionName {
87
0
                get {
88
0
                    SocketOptionName(optionName.rawValue)
89
0
                }
90
0
                set {
91
0
                    self.optionName = NIOBSDSocket.Option(rawValue: CInt(newValue))
92
0
                }
93
            }
94
95
            #if !os(Windows)
96
            // Unavailable on Windows: there, WinSDK imports the socket option
97
            // level/name constants (`IPPROTO_IP`, `IP_TTL`, `SO_REUSEADDR`, …)
98
            // as enumeration cases rather than plain integers, so they don't fit
99
            // the integer `SocketOptionLevel`/`SocketOptionName` typealiases.
100
            // Use `init(level:name:)` taking `NIOBSDSocket.OptionLevel`/`Option`
101
            // instead (their `.ip`/`.tcp`/etc. accessors read the enum's raw
102
            // value).
103
104
            /// Create a new `SocketOption`.
105
            ///
106
            /// - Parameters:
107
            ///   - level: The level for the option as defined in `man setsockopt`, e.g. SO_SOCKET.
108
            ///   - name: The name of the option as defined in `man setsockopt`, e.g. `SO_REUSEADDR`.
109
0
            public init(level: SocketOptionLevel, name: SocketOptionName) {
110
0
                self.optionLevel = NIOBSDSocket.OptionLevel(rawValue: CInt(level))
111
0
                self.optionName = NIOBSDSocket.Option(rawValue: CInt(name))
112
0
            }
113
            #endif
114
115
            /// Create a new `SocketOption`.
116
            ///
117
            /// - Parameters:
118
            ///   - level: The level for the option as defined in `man setsockopt`, e.g. SO_SOCKET.
119
            ///   - name: The name of the option as defined in `man setsockopt`, e.g. `SO_REUSEADDR`.
120
0
            public init(level: NIOBSDSocket.OptionLevel, name: NIOBSDSocket.Option) {
121
0
                self.optionLevel = level
122
0
                self.optionName = name
123
0
            }
124
        }
125
126
        /// `AllocatorOption` allows to specify the `ByteBufferAllocator` to use.
127
        public struct AllocatorOption: ChannelOption, Sendable {
128
            public typealias Value = ByteBufferAllocator
129
130
0
            public init() {}
131
        }
132
133
        /// `RecvAllocatorOption` allows users to specify the `RecvByteBufferAllocator` to use.
134
        public struct RecvAllocatorOption: ChannelOption, Sendable {
135
            public typealias Value = RecvByteBufferAllocator
136
137
0
            public init() {}
138
        }
139
140
        /// `AutoReadOption` allows users to configure if a `Channel` should automatically call `Channel.read` again once all data was read from the transport or
141
        /// if the user is responsible to call `Channel.read` manually.
142
        public struct AutoReadOption: ChannelOption, Sendable {
143
            public typealias Value = Bool
144
145
0
            public init() {}
146
        }
147
148
        /// `WriteSpinOption` allows users to configure the number of repetitions of a only partially successful write call before considering the `Channel` not writable.
149
        /// Setting this option to `0` means that we only issue one write call and if that call does not write all the bytes,
150
        /// we consider the `Channel` not writable.
151
        public struct WriteSpinOption: ChannelOption, Sendable {
152
            public typealias Value = UInt
153
154
0
            public init() {}
155
        }
156
157
        /// `MaxMessagesPerReadOption` allows users to configure the maximum number of read calls to the underlying transport are performed before wait again until
158
        /// there is more to read and be notified.
159
        public struct MaxMessagesPerReadOption: ChannelOption, Sendable {
160
            public typealias Value = UInt
161
162
0
            public init() {}
163
        }
164
165
        /// `BacklogOption` allows users to configure the `backlog` value as specified in `man 2 listen`. This is only useful for `ServerSocketChannel`s.
166
        public struct BacklogOption: ChannelOption, Sendable {
167
            public typealias Value = Int32
168
169
0
            public init() {}
170
        }
171
172
        /// `DatagramVectorReadMessageCountOption` allows users to configure the number of messages to attempt to read in a single syscall on a
173
        /// datagram `Channel`.
174
        ///
175
        /// Some datagram `Channel`s have extremely high datagram throughput. This can occur when the single datagram socket is encapsulating
176
        /// many logical "connections" (e.g. with QUIC) or when the datagram socket is simply serving an enormous number of consumers (e.g.
177
        /// with a public-facing DNS server). In this case the overhead of one syscall per datagram is profoundly limiting. Using this
178
        /// `ChannelOption` allows the `Channel` to read multiple datagrams at once.
179
        ///
180
        /// Note that simply increasing this number will not necessarily bring performance gains and may in fact cause data loss. Any increase
181
        /// to this should be matched by increasing the size of the buffers allocated by the `Channel` `RecvByteBufferAllocator` (as set by
182
        /// `ChannelOption.recvAllocator`) proportionally. For example, to receive 10 messages at a time, set the size of the buffers allocated
183
        /// by the `RecvByteBufferAllocator` to at least 10x the size of the maximum datagram size you wish to receive.
184
        ///
185
        /// Naturally, this option is only valid on datagram channels.
186
        ///
187
        /// This option only works on the following platforms:
188
        ///
189
        /// - Linux
190
        /// - FreeBSD
191
        /// - Android
192
        ///
193
        /// On all other platforms, setting it has no effect.
194
        ///
195
        /// Set this option to 0 to disable vector reads and to use serial reads instead.
196
        public struct DatagramVectorReadMessageCountOption: ChannelOption, Sendable {
197
            public typealias Value = Int
198
199
0
            public init() {}
200
        }
201
202
        /// ``DatagramSegmentSize`` controls the `UDP_SEGMENT` socket option (sometimes reffered to as 'GSO') which allows for
203
        /// large writes to be sent via `sendmsg` and `sendmmsg` and segmented into separate datagrams by the kernel (or in some cases, the NIC).
204
        /// The size of segments the large write is split into is controlled by the value of this option (note that writes do not need to be a
205
        /// multiple of this option).
206
        ///
207
        /// This option is currently only supported on Linux (4.18 and newer). Support can be checked using ``System/supportsUDPSegmentationOffload``.
208
        ///
209
        /// Setting this option to zero disables segmentation offload.
210
        public struct DatagramSegmentSize: ChannelOption, Sendable {
211
            public typealias Value = CInt
212
0
            public init() {}
213
        }
214
215
        /// ``DatagramReceiveOffload`` sets the `UDP_GRO` socket option which allows for datagrams to be accumulated
216
        /// by the kernel (or in some cases, the NIC) and reduces traversals in the kernel's networking layer.
217
        ///
218
        /// This option is currently only supported on Linux (5.10 and newer). Support can be checked
219
        /// using ``System/supportsUDPReceiveOffload``.
220
        ///
221
        /// - Note: users should ensure they use an appropriate receive buffer allocator when enabling this option.
222
        ///   The default allocator for datagram channels uses fixed sized buffers of 2048 bytes.
223
        public struct DatagramReceiveOffload: ChannelOption, Sendable {
224
            public typealias Value = Bool
225
0
            public init() {}
226
        }
227
228
        /// ``DatagramReceiveSegmentSize`` enables per-message GRO (Generic Receive Offload) segment size reporting.
229
        /// When enabled, the kernel will provide the original segment size via control messages for aggregated datagrams,
230
        /// which will be reported in `AddressedEnvelope.Metadata.segmentSize`.
231
        ///
232
        /// This option requires ``DatagramReceiveOffload`` to be enabled first and is only supported on Linux.
233
        /// Support can be checked using ``System/supportsUDPReceiveOffload``.
234
        ///
235
        /// - Note: This provides the receive-side complement to per-message GSO (``AddressedEnvelope/Metadata/segmentSize``).
236
        public struct DatagramReceiveSegmentSize: ChannelOption, Sendable {
237
            public typealias Value = Bool
238
0
            public init() {}
239
        }
240
241
        /// When set to true IP level ECN information will be reported through `AddressedEnvelope.Metadata`
242
        public struct ExplicitCongestionNotificationsOption: ChannelOption, Sendable {
243
            public typealias Value = Bool
244
0
            public init() {}
245
        }
246
247
        /// The watermark used to detect when `Channel.isWritable` returns `true` or `false`.
248
        public struct WriteBufferWaterMark: Sendable {
249
            /// The low mark setting for a `Channel`.
250
            ///
251
            /// When the amount of buffered bytes in the `Channel`s outbound buffer drops below this value the `Channel` will be
252
            /// marked as writable again (after it was non-writable).
253
            public let low: Int
254
255
            /// The high mark setting for a `Channel`.
256
            ///
257
            /// When the amount of buffered bytes in the `Channel`s outbound exceeds this value the `Channel` will be
258
            /// marked as non-writable. It will be marked as writable again once the amount of buffered bytes drops below `low`.
259
            public let high: Int
260
261
            /// Create a new instance.
262
            ///
263
            /// Valid initialization is restricted to `1 <= low <= high`.
264
            ///
265
            /// - Parameters:
266
            ///      - low: The low watermark.
267
            ///      - high: The high watermark.
268
0
            public init(low: Int, high: Int) {
269
0
                precondition(low >= 1, "low must be >= 1 but was \(low)")
270
0
                precondition(high >= low, "low must be <= high, but was low: \(low) high: \(high)")
271
0
                self.low = low
272
0
                self.high = high
273
0
            }
274
        }
275
276
        /// `WriteBufferWaterMarkOption` allows users to configure when a `Channel` should be marked as writable or not. Once the amount of bytes queued in a
277
        /// `Channel`s outbound buffer is larger than `WriteBufferWaterMark.high` the channel will be marked as non-writable and so
278
        /// `Channel.isWritable` will return `false`. Once we were able to write some data out of the outbound buffer and the amount of bytes queued
279
        /// falls below `WriteBufferWaterMark.low` the `Channel` will become writable again. Once this happens `Channel.writable` will return
280
        /// `true` again. These writability changes are also propagated through the `ChannelPipeline` and so can be intercepted via `ChannelInboundHandler.channelWritabilityChanged`.
281
        public struct WriteBufferWaterMarkOption: ChannelOption, Sendable {
282
            public typealias Value = WriteBufferWaterMark
283
284
0
            public init() {}
285
        }
286
287
        /// `ConnectTimeoutOption` allows users to configure the `TimeAmount` after which a connect will fail if it was not established in the meantime. May be
288
        /// `nil`, in which case the connection attempt will never time out.
289
        public struct ConnectTimeoutOption: ChannelOption, Sendable {
290
            public typealias Value = TimeAmount?
291
292
0
            public init() {}
293
        }
294
295
        /// `AllowRemoteHalfClosureOption` allows users to configure whether the `Channel` will close itself when its remote
296
        /// peer shuts down its send stream, or whether it will remain open. If set to `false` (the default), the `Channel`
297
        /// will be closed automatically if the remote peer shuts down its send stream. If set to true, the `Channel` will
298
        /// not be closed: instead, a `ChannelEvent.inboundClosed` user event will be sent on the `ChannelPipeline`,
299
        /// and no more data will be received.
300
        public struct AllowRemoteHalfClosureOption: ChannelOption, Sendable {
301
            public typealias Value = Bool
302
303
0
            public init() {}
304
        }
305
306
        /// When set to true IP level Packet Info information will be reported through `AddressedEnvelope.Metadata` for UDP packets.
307
        public struct ReceivePacketInfo: ChannelOption, Sendable {
308
            public typealias Value = Bool
309
0
            public init() {}
310
        }
311
312
        /// `BufferedWritableBytesOption` allows users to know the number of writable bytes currently buffered in the `Channel`.
313
        public struct BufferedWritableBytesOption: ChannelOption, Sendable {
314
            public typealias Value = Int
315
316
0
            public init() {}
317
        }
318
    }
319
}
320
321
/// Provides `ChannelOption`s to be used with a `Channel`, `Bootstrap` or `ServerBootstrap`.
322
public struct ChannelOptions: Sendable {
323
    // Unavailable on Windows; see `SocketOption.init(level:name:)`. Use the
324
    // typed `socketOption(_:)`/`ipOption(_:)`/`tcpOption(_:)` accessors instead.
325
    #if !os(Windows)
326
0
    public static let socket: @Sendable (SocketOptionLevel, SocketOptionName) -> ChannelOptions.Types.SocketOption = {
327
0
        (level: SocketOptionLevel, name: SocketOptionName) -> Types.SocketOption in
328
0
        .init(level: NIOBSDSocket.OptionLevel(rawValue: CInt(level)), name: NIOBSDSocket.Option(rawValue: CInt(name)))
329
0
    }
330
    #endif
331
332
    /// - seealso: `SocketOption`.
333
0
    public static let socketOption: @Sendable (NIOBSDSocket.Option) -> ChannelOptions.Types.SocketOption = {
334
0
        (name: NIOBSDSocket.Option) -> Types.SocketOption in
335
0
        .init(level: .socket, name: name)
336
0
    }
337
338
    /// - seealso: `SocketOption`.
339
0
    public static let ipOption: @Sendable (NIOBSDSocket.Option) -> ChannelOptions.Types.SocketOption = {
340
0
        (name: NIOBSDSocket.Option) -> Types.SocketOption in
341
0
        .init(level: .ip, name: name)
342
0
    }
343
344
    /// - seealso: `SocketOption`.
345
0
    public static let tcpOption: @Sendable (NIOBSDSocket.Option) -> ChannelOptions.Types.SocketOption = {
346
0
        (name: NIOBSDSocket.Option) -> Types.SocketOption in
347
0
        .init(level: .tcp, name: name)
348
0
    }
349
350
    /// - seealso: `AllocatorOption`.
351
    public static let allocator = Types.AllocatorOption()
352
353
    /// - seealso: `RecvAllocatorOption`.
354
    public static let recvAllocator = Types.RecvAllocatorOption()
355
356
    /// - seealso: `AutoReadOption`.
357
    public static let autoRead = Types.AutoReadOption()
358
359
    /// - seealso: `MaxMessagesPerReadOption`.
360
    public static let maxMessagesPerRead = Types.MaxMessagesPerReadOption()
361
362
    /// - seealso: `BacklogOption`.
363
    public static let backlog = Types.BacklogOption()
364
365
    /// - seealso: `WriteSpinOption`.
366
    public static let writeSpin = Types.WriteSpinOption()
367
368
    /// - seealso: `WriteBufferWaterMarkOption`.
369
    public static let writeBufferWaterMark = Types.WriteBufferWaterMarkOption()
370
371
    /// - seealso: `ConnectTimeoutOption`.
372
    public static let connectTimeout = Types.ConnectTimeoutOption()
373
374
    /// - seealso: `AllowRemoteHalfClosureOption`.
375
    public static let allowRemoteHalfClosure = Types.AllowRemoteHalfClosureOption()
376
377
    /// - seealso: `DatagramVectorReadMessageCountOption`
378
    public static let datagramVectorReadMessageCount = Types.DatagramVectorReadMessageCountOption()
379
380
    /// - seealso: `DatagramSegmentSize`
381
    public static let datagramSegmentSize = Types.DatagramSegmentSize()
382
383
    /// - seealso: `DatagramReceiveOffload`
384
    public static let datagramReceiveOffload = Types.DatagramReceiveOffload()
385
386
    /// - seealso: `DatagramReceiveSegmentSize`
387
    public static let datagramReceiveSegmentSize = Types.DatagramReceiveSegmentSize()
388
389
    /// - seealso: `ExplicitCongestionNotificationsOption`
390
    public static let explicitCongestionNotification = Types.ExplicitCongestionNotificationsOption()
391
392
    /// - seealso: `ReceivePacketInfo`
393
    public static let receivePacketInfo = Types.ReceivePacketInfo()
394
395
    /// - seealso: `BufferedWritableBytesOption`
396
    public static let bufferedWritableBytes = Types.BufferedWritableBytesOption()
397
}
398
399
/// - seealso: `SocketOption`.
400
extension ChannelOption where Self == ChannelOptions.Types.SocketOption {
401
    // Unavailable on Windows; see `SocketOption.init(level:name:)`. Use the
402
    // typed `socketOption(_:)`/`ipOption(_:)`/`tcpOption(_:)` accessors instead.
403
    #if !(os(Windows))
404
0
    public static func socket(_ level: SocketOptionLevel, _ name: SocketOptionName) -> Self {
405
0
        .init(level: NIOBSDSocket.OptionLevel(rawValue: CInt(level)), name: NIOBSDSocket.Option(rawValue: CInt(name)))
406
0
    }
407
    #endif
408
409
0
    public static func socketOption(_ name: NIOBSDSocket.Option) -> Self {
410
0
        .init(level: .socket, name: name)
411
0
    }
412
413
0
    public static func ipOption(_ name: NIOBSDSocket.Option) -> Self {
414
0
        .init(level: .ip, name: name)
415
0
    }
416
417
0
    public static func tcpOption(_ name: NIOBSDSocket.Option) -> Self {
418
0
        .init(level: .tcp, name: name)
419
0
    }
420
}
421
422
/// - seealso: `AllocatorOption`.
423
extension ChannelOption where Self == ChannelOptions.Types.AllocatorOption {
424
0
    public static var allocator: Self { .init() }
425
}
426
427
/// - seealso: `RecvAllocatorOption`.
428
extension ChannelOption where Self == ChannelOptions.Types.RecvAllocatorOption {
429
0
    public static var recvAllocator: Self { .init() }
430
}
431
432
/// - seealso: `AutoReadOption`.
433
extension ChannelOption where Self == ChannelOptions.Types.AutoReadOption {
434
0
    public static var autoRead: Self { .init() }
435
}
436
437
/// - seealso: `MaxMessagesPerReadOption`.
438
extension ChannelOption where Self == ChannelOptions.Types.MaxMessagesPerReadOption {
439
0
    public static var maxMessagesPerRead: Self { .init() }
440
}
441
442
/// - seealso: `BacklogOption`.
443
extension ChannelOption where Self == ChannelOptions.Types.BacklogOption {
444
0
    public static var backlog: Self { .init() }
445
}
446
447
/// - seealso: `WriteSpinOption`.
448
extension ChannelOption where Self == ChannelOptions.Types.WriteSpinOption {
449
0
    public static var writeSpin: Self { .init() }
450
}
451
452
/// - seealso: `WriteBufferWaterMarkOption`.
453
extension ChannelOption where Self == ChannelOptions.Types.WriteBufferWaterMarkOption {
454
0
    public static var writeBufferWaterMark: Self { .init() }
455
}
456
457
/// - seealso: `ConnectTimeoutOption`.
458
extension ChannelOption where Self == ChannelOptions.Types.ConnectTimeoutOption {
459
0
    public static var connectTimeout: Self { .init() }
460
}
461
462
/// - seealso: `AllowRemoteHalfClosureOption`.
463
extension ChannelOption where Self == ChannelOptions.Types.AllowRemoteHalfClosureOption {
464
0
    public static var allowRemoteHalfClosure: Self { .init() }
465
}
466
467
/// - seealso: `DatagramVectorReadMessageCountOption`.
468
extension ChannelOption where Self == ChannelOptions.Types.DatagramVectorReadMessageCountOption {
469
0
    public static var datagramVectorReadMessageCount: Self { .init() }
470
}
471
472
/// - seealso: `DatagramSegmentSize`.
473
extension ChannelOption where Self == ChannelOptions.Types.DatagramSegmentSize {
474
0
    public static var datagramSegmentSize: Self { .init() }
475
}
476
477
/// - seealso: `DatagramReceiveOffload`.
478
extension ChannelOption where Self == ChannelOptions.Types.DatagramReceiveOffload {
479
0
    public static var datagramReceiveOffload: Self { .init() }
480
}
481
482
/// - seealso: `DatagramReceiveSegmentSize`.
483
extension ChannelOption where Self == ChannelOptions.Types.DatagramReceiveSegmentSize {
484
0
    public static var datagramReceiveSegmentSize: Self { .init() }
485
}
486
487
/// - seealso: `ExplicitCongestionNotificationsOption`.
488
extension ChannelOption where Self == ChannelOptions.Types.ExplicitCongestionNotificationsOption {
489
0
    public static var explicitCongestionNotification: Self { .init() }
490
}
491
492
/// - seealso: `ReceivePacketInfo`.
493
extension ChannelOption where Self == ChannelOptions.Types.ReceivePacketInfo {
494
0
    public static var receivePacketInfo: Self { .init() }
495
}
496
497
/// - seealso: `BufferedWritableBytesOption`
498
extension ChannelOption where Self == ChannelOptions.Types.BufferedWritableBytesOption {
499
0
    public static var bufferedWritableBytes: Self { .init() }
500
}
501
502
extension ChannelOptions {
503
    /// A type-safe storage facility for `ChannelOption`s. You will only ever need this if you implement your own
504
    /// `Channel` that needs to store `ChannelOption`s.
505
    public struct Storage: Sendable {
506
        @usableFromInline
507
        internal var _storage:
508
            [(
509
                any ChannelOption,
510
                (any Sendable, @Sendable (Channel) -> (any ChannelOption, any Sendable) -> EventLoopFuture<Void>)
511
            )]
512
513
0
        public init() {
514
0
            self._storage = []
515
0
            self._storage.reserveCapacity(2)
516
0
        }
517
518
        /// Add `Options`, a `ChannelOption` to the `ChannelOptions.Storage`.
519
        ///
520
        /// - Parameters:
521
        ///    - newKey: the key for the option
522
        ///    - newValue: the value for the option
523
        @inlinable
524
0
        public mutating func append<Option: ChannelOption>(key newKey: Option, value newValue: Option.Value) {
525
0
            @Sendable
526
0
            func applier(_ t: Channel) -> (any ChannelOption, any Sendable) -> EventLoopFuture<Void> {
527
0
                { (option, value) in
528
0
                    t.setOption(option as! Option, value: value as! Option.Value)
529
0
                }
530
0
            }
531
0
            var hasSet = false
532
0
            self._storage = self._storage.map { currentKeyAndValue in
533
0
                let (currentKey, _) = currentKeyAndValue
534
0
                if let currentKey = currentKey as? Option, currentKey == newKey {
535
0
                    hasSet = true
536
0
                    return (currentKey, (newValue, applier))
537
0
                } else {
538
0
                    return currentKeyAndValue
539
0
                }
540
0
            }
541
0
            if !hasSet {
542
0
                self._storage.append((newKey, (newValue, applier)))
543
0
            }
544
0
        }
545
546
        /// Apply all stored `ChannelOption`s to `Channel`.
547
        ///
548
        /// - Parameters:
549
        ///    - channel: The `Channel` to apply the `ChannelOption`s to
550
        /// - Returns:
551
        ///    - An `EventLoopFuture` that is fulfilled when all `ChannelOption`s have been applied to the `Channel`.
552
0
        public func applyAllChannelOptions(to channel: Channel) -> EventLoopFuture<Void> {
553
0
            let applyPromise = channel.eventLoop.makePromise(of: Void.self)
554
0
            let it = self._storage.makeIterator()
555
0
556
0
            @Sendable
557
0
            func applyNext(
558
0
                iterator: IndexingIterator<
559
0
                    [(
560
0
                        any ChannelOption,
561
0
                        (
562
0
                            any Sendable,
563
0
                            @Sendable (any Channel) -> (any ChannelOption, any Sendable) -> EventLoopFuture<Void>
564
0
                        )
565
0
                    )]
566
0
                >
567
0
            ) {
568
0
                var iterator = iterator
569
0
                guard let (key, (value, applier)) = iterator.next() else {
570
0
                    // If we reached the end, everything is applied.
571
0
                    applyPromise.succeed(())
572
0
                    return
573
0
                }
574
0
                let it = iterator
575
0
576
0
                applier(channel)(key, value).map {
577
0
                    applyNext(
578
0
                        iterator: it
579
0
                    )
580
0
                }.cascadeFailure(to: applyPromise)
581
0
            }
582
0
            applyNext(iterator: it)
583
0
584
0
            return applyPromise.futureResult
585
0
        }
586
587
        /// Remove all options with the given `key`.
588
        ///
589
        /// Calling this function has the effect of removing all instances of a ``ChannelOption``
590
        /// from the ``ChannelOptions/Storage``, as if none had been added. This is useful in rare
591
        /// cases where a bootstrap knows that some configuration must purge options of a certain kind.
592
        ///
593
        /// - Parameters:
594
        ///   - key: The ``ChannelOption`` to remove.
595
0
        public mutating func remove<Option: ChannelOption>(key: Option) {
596
0
            self._storage.removeAll(where: { existingKey, _ in
597
0
                (existingKey as? Option) == key
598
0
            })
599
0
        }
600
    }
601
}