swift-nio/Sources/NIOCore/ChannelOption.swift

427 lines
19 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// A configuration option that can be set on a `Channel` to configure different behaviour.
public protocol ChannelOption: Equatable, NIOPreconcurrencySendable {
/// The type of the `ChannelOption`'s value.
associatedtype Value
}
public typealias SocketOptionName = Int32
#if os(Linux) || os(Android)
public typealias SocketOptionLevel = Int
public typealias SocketOptionValue = Int
#else
public typealias SocketOptionLevel = CInt
public typealias SocketOptionValue = CInt
#endif
@available(*, deprecated, renamed: "ChannelOptions.Types.SocketOption")
public typealias SocketOption = ChannelOptions.Types.SocketOption
@available(*, deprecated, renamed: "ChannelOptions.Types.AllocatorOption")
public typealias AllocatorOption = ChannelOptions.Types.AllocatorOption
@available(*, deprecated, renamed: "ChannelOptions.Types.RecvAllocatorOption")
public typealias RecvAllocatorOption = ChannelOptions.Types.RecvAllocatorOption
@available(*, deprecated, renamed: "ChannelOptions.Types.AutoReadOption")
public typealias AutoReadOption = ChannelOptions.Types.AutoReadOption
@available(*, deprecated, renamed: "ChannelOptions.Types.WriteSpinOption")
public typealias WriteSpinOption = ChannelOptions.Types.WriteSpinOption
@available(*, deprecated, renamed: "ChannelOptions.Types.MaxMessagesPerReadOption")
public typealias MaxMessagesPerReadOption = ChannelOptions.Types.MaxMessagesPerReadOption
@available(*, deprecated, renamed: "ChannelOptions.Types.BacklogOption")
public typealias BacklogOption = ChannelOptions.Types.BacklogOption
@available(*, deprecated, renamed: "ChannelOptions.Types.DatagramVectorReadMessageCountOption")
public typealias DatagramVectorReadMessageCountOption = ChannelOptions.Types.DatagramVectorReadMessageCountOption
@available(*, deprecated, renamed: "ChannelOptions.Types.WriteBufferWaterMark")
public typealias WriteBufferWaterMark = ChannelOptions.Types.WriteBufferWaterMark
@available(*, deprecated, renamed: "ChannelOptions.Types.WriteBufferWaterMarkOption")
public typealias WriteBufferWaterMarkOption = ChannelOptions.Types.WriteBufferWaterMarkOption
@available(*, deprecated, renamed: "ChannelOptions.Types.ConnectTimeoutOption")
public typealias ConnectTimeoutOption = ChannelOptions.Types.ConnectTimeoutOption
@available(*, deprecated, renamed: "ChannelOptions.Types.AllowRemoteHalfClosureOption")
public typealias AllowRemoteHalfClosureOption = ChannelOptions.Types.AllowRemoteHalfClosureOption
extension ChannelOptions {
public enum Types {
/// `SocketOption` allows users to specify configuration settings that are directly applied to the underlying socket file descriptor.
///
/// Valid options are typically found in the various man pages like `man 4 tcp`.
public struct SocketOption: ChannelOption, Equatable, Sendable {
public typealias Value = (SocketOptionValue)
public var optionLevel: NIOBSDSocket.OptionLevel
public var optionName: NIOBSDSocket.Option
public var level: SocketOptionLevel {
get {
return SocketOptionLevel(optionLevel.rawValue)
}
set {
self.optionLevel = NIOBSDSocket.OptionLevel(rawValue: CInt(newValue))
}
}
public var name: SocketOptionName {
get {
return SocketOptionName(optionName.rawValue)
}
set {
self.optionName = NIOBSDSocket.Option(rawValue: CInt(newValue))
}
}
#if !os(Windows)
/// Create a new `SocketOption`.
///
/// - parameters:
/// - level: The level for the option as defined in `man setsockopt`, e.g. SO_SOCKET.
/// - name: The name of the option as defined in `man setsockopt`, e.g. `SO_REUSEADDR`.
public init(level: SocketOptionLevel, name: SocketOptionName) {
self.optionLevel = NIOBSDSocket.OptionLevel(rawValue: CInt(level))
self.optionName = NIOBSDSocket.Option(rawValue: CInt(name))
}
#endif
/// Create a new `SocketOption`.
///
/// - parameters:
/// - level: The level for the option as defined in `man setsockopt`, e.g. SO_SOCKET.
/// - name: The name of the option as defined in `man setsockopt`, e.g. `SO_REUSEADDR`.
public init(level: NIOBSDSocket.OptionLevel, name: NIOBSDSocket.Option) {
self.optionLevel = level
self.optionName = name
}
}
/// `AllocatorOption` allows to specify the `ByteBufferAllocator` to use.
public struct AllocatorOption: ChannelOption, Sendable {
public typealias Value = ByteBufferAllocator
public init() {}
}
/// `RecvAllocatorOption` allows users to specify the `RecvByteBufferAllocator` to use.
public struct RecvAllocatorOption: ChannelOption, Sendable {
public typealias Value = RecvByteBufferAllocator
public init() {}
}
/// `AutoReadOption` allows users to configure if a `Channel` should automatically call `Channel.read` again once all data was read from the transport or
/// if the user is responsible to call `Channel.read` manually.
public struct AutoReadOption: ChannelOption, Sendable {
public typealias Value = Bool
public init() {}
}
/// `WriteSpinOption` allows users to configure the number of repetitions of a only partially successful write call before considering the `Channel` not writable.
/// Setting this option to `0` means that we only issue one write call and if that call does not write all the bytes,
/// we consider the `Channel` not writable.
public struct WriteSpinOption: ChannelOption, Sendable {
public typealias Value = UInt
public init() {}
}
/// `MaxMessagesPerReadOption` allows users to configure the maximum number of read calls to the underlying transport are performed before wait again until
/// there is more to read and be notified.
public struct MaxMessagesPerReadOption: ChannelOption, Sendable {
public typealias Value = UInt
public init() {}
}
/// `BacklogOption` allows users to configure the `backlog` value as specified in `man 2 listen`. This is only useful for `ServerSocketChannel`s.
public struct BacklogOption: ChannelOption, Sendable {
public typealias Value = Int32
public init() {}
}
/// `DatagramVectorReadMessageCountOption` allows users to configure the number of messages to attempt to read in a single syscall on a
/// datagram `Channel`.
///
/// Some datagram `Channel`s have extremely high datagram throughput. This can occur when the single datagram socket is encapsulating
/// many logical "connections" (e.g. with QUIC) or when the datagram socket is simply serving an enormous number of consumers (e.g.
/// with a public-facing DNS server). In this case the overhead of one syscall per datagram is profoundly limiting. Using this
/// `ChannelOption` allows the `Channel` to read multiple datagrams at once.
///
/// Note that simply increasing this number will not necessarily bring performance gains and may in fact cause data loss. Any increase
/// to this should be matched by increasing the size of the buffers allocated by the `Channel` `RecvByteBufferAllocator` (as set by
/// `ChannelOption.recvAllocator`) proportionally. For example, to receive 10 messages at a time, set the size of the buffers allocated
/// by the `RecvByteBufferAllocator` to at least 10x the size of the maximum datagram size you wish to receive.
///
/// Naturally, this option is only valid on datagram channels.
///
/// This option only works on the following platforms:
///
/// - Linux
/// - FreeBSD
/// - Android
///
/// On all other platforms, setting it has no effect.
///
/// Set this option to 0 to disable vector reads and to use serial reads instead.
public struct DatagramVectorReadMessageCountOption: ChannelOption, Sendable {
public typealias Value = Int
public init() { }
}
/// ``DatagramSegmentSize`` controls the 'UDP_SEGMENT' socket option (sometimes reffered to as 'GSO') which allows for
/// large writes to be sent via `sendmsg` and `sendmmsg` and segmented into separate datagrams by the kernel (or in some cases, the NIC).
/// 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
/// multiple of this option).
///
/// This option is currently only supported on Linux (4.18 and newer). Support can be checked using ``System/supportsUDPSegmentationOffload``.
///
/// Setting this option to zero disables segmentation offload.
public struct DatagramSegmentSize: ChannelOption, Sendable {
public typealias Value = CInt
public init() { }
}
/// When set to true IP level ECN information will be reported through `AddressedEnvelope.Metadata`
public struct ExplicitCongestionNotificationsOption: ChannelOption, Sendable {
public typealias Value = Bool
public init() {}
}
/// The watermark used to detect when `Channel.isWritable` returns `true` or `false`.
public struct WriteBufferWaterMark: Sendable {
/// The low mark setting for a `Channel`.
///
/// When the amount of buffered bytes in the `Channel`s outbound buffer drops below this value the `Channel` will be
/// marked as writable again (after it was non-writable).
public let low: Int
/// The high mark setting for a `Channel`.
///
/// When the amount of buffered bytes in the `Channel`s outbound exceeds this value the `Channel` will be
/// marked as non-writable. It will be marked as writable again once the amount of buffered bytes drops below `low`.
public let high: Int
/// Create a new instance.
///
/// Valid initialization is restricted to `1 <= low <= high`.
///
/// - parameters:
/// - low: The low watermark.
/// - high: The high watermark.
public init(low: Int, high: Int) {
precondition(low >= 1, "low must be >= 1 but was \(low)")
precondition(high >= low, "low must be <= high, but was low: \(low) high: \(high)")
self.low = low
self.high = high
}
}
/// `WriteBufferWaterMarkOption` allows users to configure when a `Channel` should be marked as writable or not. Once the amount of bytes queued in a
/// `Channel`s outbound buffer is larger than `WriteBufferWaterMark.high` the channel will be marked as non-writable and so
/// `Channel.isWritable` will return `false`. Once we were able to write some data out of the outbound buffer and the amount of bytes queued
/// falls below `WriteBufferWaterMark.low` the `Channel` will become writable again. Once this happens `Channel.writable` will return
/// `true` again. These writability changes are also propagated through the `ChannelPipeline` and so can be intercepted via `ChannelInboundHandler.channelWritabilityChanged`.
public struct WriteBufferWaterMarkOption: ChannelOption, Sendable {
public typealias Value = WriteBufferWaterMark
public init() {}
}
/// `ConnectTimeoutOption` allows users to configure the `TimeAmount` after which a connect will fail if it was not established in the meantime. May be
/// `nil`, in which case the connection attempt will never time out.
public struct ConnectTimeoutOption: ChannelOption, Sendable {
public typealias Value = TimeAmount?
public init() {}
}
/// `AllowRemoteHalfClosureOption` allows users to configure whether the `Channel` will close itself when its remote
/// peer shuts down its send stream, or whether it will remain open. If set to `false` (the default), the `Channel`
/// will be closed automatically if the remote peer shuts down its send stream. If set to true, the `Channel` will
/// not be closed: instead, a `ChannelEvent.inboundClosed` user event will be sent on the `ChannelPipeline`,
/// and no more data will be received.
public struct AllowRemoteHalfClosureOption: ChannelOption, Sendable {
public typealias Value = Bool
public init() {}
}
/// When set to true IP level Packet Info information will be reported through `AddressedEnvelope.Metadata` for UDP packets.
public struct ReceivePacketInfo: ChannelOption, Sendable {
public typealias Value = Bool
public init() {}
}
}
}
/// Provides `ChannelOption`s to be used with a `Channel`, `Bootstrap` or `ServerBootstrap`.
public struct ChannelOptions {
#if !os(Windows)
public static let socket = { (level: SocketOptionLevel, name: SocketOptionName) -> Types.SocketOption in
.init(level: NIOBSDSocket.OptionLevel(rawValue: CInt(level)), name: NIOBSDSocket.Option(rawValue: CInt(name)))
}
#endif
/// - seealso: `SocketOption`.
public static let socketOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
.init(level: .socket, name: name)
}
/// - seealso: `SocketOption`.
public static let ipOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
.init(level: .ip, name: name)
}
/// - seealso: `SocketOption`.
public static let tcpOption = { (name: NIOBSDSocket.Option) -> Types.SocketOption in
.init(level: .tcp, name: name)
}
/// - seealso: `AllocatorOption`.
public static let allocator = Types.AllocatorOption()
/// - seealso: `RecvAllocatorOption`.
public static let recvAllocator = Types.RecvAllocatorOption()
/// - seealso: `AutoReadOption`.
public static let autoRead = Types.AutoReadOption()
/// - seealso: `MaxMessagesPerReadOption`.
public static let maxMessagesPerRead = Types.MaxMessagesPerReadOption()
/// - seealso: `BacklogOption`.
public static let backlog = Types.BacklogOption()
/// - seealso: `WriteSpinOption`.
public static let writeSpin = Types.WriteSpinOption()
/// - seealso: `WriteBufferWaterMarkOption`.
public static let writeBufferWaterMark = Types.WriteBufferWaterMarkOption()
/// - seealso: `ConnectTimeoutOption`.
public static let connectTimeout = Types.ConnectTimeoutOption()
/// - seealso: `AllowRemoteHalfClosureOption`.
public static let allowRemoteHalfClosure = Types.AllowRemoteHalfClosureOption()
/// - seealso: `DatagramVectorReadMessageCountOption`
public static let datagramVectorReadMessageCount = Types.DatagramVectorReadMessageCountOption()
/// - seealso: `DatagramSegmentSize`
public static let datagramSegmentSize = Types.DatagramSegmentSize()
/// - seealso: `ExplicitCongestionNotificationsOption`
public static let explicitCongestionNotification = Types.ExplicitCongestionNotificationsOption()
/// - seealso: `ReceivePacketInfo`
public static let receivePacketInfo = Types.ReceivePacketInfo()
}
extension ChannelOptions {
/// A type-safe storage facility for `ChannelOption`s. You will only ever need this if you implement your own
/// `Channel` that needs to store `ChannelOption`s.
public struct Storage {
@usableFromInline
internal var _storage: [(Any, (Any, (Channel) -> (Any, Any) -> EventLoopFuture<Void>))]
public init() {
self._storage = []
self._storage.reserveCapacity(2)
}
/// Add `Options`, a `ChannelOption` to the `ChannelOptions.Storage`.
///
/// - parameters:
/// - key: the key for the option
/// - value: the value for the option
@inlinable
public mutating func append<Option: ChannelOption>(key newKey: Option, value newValue: Option.Value) {
func applier(_ t: Channel) -> (Any, Any) -> EventLoopFuture<Void> {
return { (option, value) in
return t.setOption(option as! Option, value: value as! Option.Value)
}
}
var hasSet = false
self._storage = self._storage.map { currentKeyAndValue in
let (currentKey, _) = currentKeyAndValue
if let currentKey = currentKey as? Option, currentKey == newKey {
hasSet = true
return (currentKey, (newValue, applier))
} else {
return currentKeyAndValue
}
}
if !hasSet {
self._storage.append((newKey, (newValue, applier)))
}
}
/// Apply all stored `ChannelOption`s to `Channel`.
///
/// - parameters:
/// - channel: The `Channel` to apply the `ChannelOption`s to
/// - returns:
/// - An `EventLoopFuture` that is fulfilled when all `ChannelOption`s have been applied to the `Channel`.
public func applyAllChannelOptions(to channel: Channel) -> EventLoopFuture<Void> {
let applyPromise = channel.eventLoop.makePromise(of: Void.self)
var it = self._storage.makeIterator()
func applyNext() {
guard let (key, (value, applier)) = it.next() else {
// If we reached the end, everything is applied.
applyPromise.succeed(())
return
}
applier(channel)(key, value).map {
applyNext()
}.cascadeFailure(to: applyPromise)
}
applyNext()
return applyPromise.futureResult
}
/// Remove all options with the given `key`.
///
/// Calling this function has the effect of removing all instances of a ``ChannelOption``
/// from the ``ChannelOptions/Storage``, as if none had been added. This is useful in rare
/// cases where a bootstrap knows that some configuration must purge options of a certain kind.
///
/// - parameters:
/// - key: The ``ChannelOption`` to remove.
public mutating func remove<Option: ChannelOption>(key: Option) {
self._storage.removeAll(where: { existingKey, _ in
(existingKey as? Option) == key
})
}
}
}
#if swift(>=5.6)
extension ChannelOptions.Storage: @unchecked Sendable {}
#endif