Provide configurability for receiving connection data (#212)

Motivation:

Users are stuck with the hardcoded parameters for receiving data from a
connection.

Modifications:

- Add new options to `NIOTSChannelOptions` for configuring how to
receive data from a connection.

Result:

Users can configure how they receive data from a connection.
This commit is contained in:
Clinton Nkwocha 2024-10-17 13:14:45 +01:00 committed by GitHub
parent fc398db673
commit bbd5e63cf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 112 additions and 2 deletions

View File

@ -68,6 +68,12 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
/// after the initial connection attempt has been made. /// after the initial connection attempt has been made.
internal var connection: NWConnection? internal var connection: NWConnection?
/// The minimum length of data to receive from this connection, until the content is complete.
internal var minimumIncompleteReceiveLength: Int
/// The maximum length of data to receive from this connection in a single completion.
internal var maximumReceiveLength: Int
/// The `DispatchQueue` that socket events for this connection will be dispatched onto. /// The `DispatchQueue` that socket events for this connection will be dispatched onto.
internal let connectionQueue: DispatchQueue internal let connectionQueue: DispatchQueue
@ -169,11 +175,15 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
internal init(eventLoop: NIOTSEventLoop, internal init(eventLoop: NIOTSEventLoop,
parent: Channel? = nil, parent: Channel? = nil,
qos: DispatchQoS? = nil, qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
udpOptions: NWProtocolUDP.Options, udpOptions: NWProtocolUDP.Options,
tlsOptions: NWProtocolTLS.Options?) { tlsOptions: NWProtocolTLS.Options?) {
self.tsEventLoop = eventLoop self.tsEventLoop = eventLoop
self.closePromise = eventLoop.makePromise() self.closePromise = eventLoop.makePromise()
self.parent = parent self.parent = parent
self.minimumIncompleteReceiveLength = minimumIncompleteReceiveLength
self.maximumReceiveLength = maximumReceiveLength
self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos) self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos)
self.udpOptions = udpOptions self.udpOptions = udpOptions
self.tlsOptions = tlsOptions self.tlsOptions = tlsOptions
@ -187,11 +197,15 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
on eventLoop: NIOTSEventLoop, on eventLoop: NIOTSEventLoop,
parent: Channel, parent: Channel,
qos: DispatchQoS? = nil, qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
udpOptions: NWProtocolUDP.Options, udpOptions: NWProtocolUDP.Options,
tlsOptions: NWProtocolTLS.Options?) { tlsOptions: NWProtocolTLS.Options?) {
self.init(eventLoop: eventLoop, self.init(eventLoop: eventLoop,
parent: parent, parent: parent,
qos: qos, qos: qos,
minimumIncompleteReceiveLength: minimumIncompleteReceiveLength,
maximumReceiveLength: maximumReceiveLength,
udpOptions: udpOptions, udpOptions: udpOptions,
tlsOptions: tlsOptions) tlsOptions: tlsOptions)
self.connection = connection self.connection = connection

View File

@ -53,6 +53,12 @@ public struct NIOTSChannelOptions {
/// See: ``Types/NIOTSListenerOption``. /// See: ``Types/NIOTSListenerOption``.
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) @available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
public static let listener = NIOTSChannelOptions.Types.NIOTSListenerOption() public static let listener = NIOTSChannelOptions.Types.NIOTSListenerOption()
/// See: ``Types/NIOTSMinimumIncompleteReceiveLengthOption``.
public static let minimumIncompleteReceiveLength = NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption()
/// See: ``Types/NIOTSMaximumReceiveLengthOption``.
public static let maximumReceiveLength = NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption()
} }
@ -179,6 +185,26 @@ extension NIOTSChannelOptions {
public init() {} public init() {}
} }
/// ``NIOTSMinimumIncompleteReceiveLengthOption`` controls the minimum length to receive from a given
/// `NWConnection`, until the content is complete.
///
/// This option is only valid with a `Channel` backed by an `NWConnection`.
public struct NIOTSMinimumIncompleteReceiveLengthOption: ChannelOption, Equatable {
public typealias Value = Int
public init() {}
}
/// ``NIOTSMaximumReceiveLengthOption`` controls the maximum length to receive from a given
/// `NWConnection` in a single completion.
///
/// This option is only valid with a `Channel` backed by an `NWConnection`.
public struct NIOTSMaximumReceiveLengthOption: ChannelOption, Equatable {
public typealias Value = Int
public init() {}
}
} }
} }

View File

@ -150,6 +150,12 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
/// after the initial connection attempt has been made. /// after the initial connection attempt has been made.
internal var connection: NWConnection? internal var connection: NWConnection?
/// The minimum length of data to receive from this connection, until the content is complete.
internal var minimumIncompleteReceiveLength: Int
/// The maximum length of data to receive from this connection in a single completion.
internal var maximumReceiveLength: Int
/// The `DispatchQueue` that socket events for this connection will be dispatched onto. /// The `DispatchQueue` that socket events for this connection will be dispatched onto.
internal let connectionQueue: DispatchQueue internal let connectionQueue: DispatchQueue
@ -230,11 +236,15 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
internal init(eventLoop: NIOTSEventLoop, internal init(eventLoop: NIOTSEventLoop,
parent: Channel? = nil, parent: Channel? = nil,
qos: DispatchQoS? = nil, qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
tcpOptions: NWProtocolTCP.Options, tcpOptions: NWProtocolTCP.Options,
tlsOptions: NWProtocolTLS.Options?) { tlsOptions: NWProtocolTLS.Options?) {
self.tsEventLoop = eventLoop self.tsEventLoop = eventLoop
self.closePromise = eventLoop.makePromise() self.closePromise = eventLoop.makePromise()
self.parent = parent self.parent = parent
self.minimumIncompleteReceiveLength = minimumIncompleteReceiveLength
self.maximumReceiveLength = maximumReceiveLength
self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos) self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos)
self.tcpOptions = tcpOptions self.tcpOptions = tcpOptions
self.tlsOptions = tlsOptions self.tlsOptions = tlsOptions
@ -248,11 +258,15 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
on eventLoop: NIOTSEventLoop, on eventLoop: NIOTSEventLoop,
parent: Channel? = nil, parent: Channel? = nil,
qos: DispatchQoS? = nil, qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
tcpOptions: NWProtocolTCP.Options, tcpOptions: NWProtocolTCP.Options,
tlsOptions: NWProtocolTLS.Options?) { tlsOptions: NWProtocolTLS.Options?) {
self.init(eventLoop: eventLoop, self.init(eventLoop: eventLoop,
parent: parent, parent: parent,
qos: qos, qos: qos,
minimumIncompleteReceiveLength: minimumIncompleteReceiveLength,
maximumReceiveLength: maximumReceiveLength,
tcpOptions: tcpOptions, tcpOptions: tcpOptions,
tlsOptions: tlsOptions) tlsOptions: tlsOptions)
self.connection = connection self.connection = connection

View File

@ -48,6 +48,10 @@ internal protocol StateManagedNWConnectionChannel: StateManagedChannel where Act
var connection: NWConnection? { get set } var connection: NWConnection? { get set }
var minimumIncompleteReceiveLength: Int { get set }
var maximumReceiveLength: Int { get set }
var connectionQueue: DispatchQueue { get } var connectionQueue: DispatchQueue { get }
var connectPromise: EventLoopPromise<Void>? { get set } var connectPromise: EventLoopPromise<Void>? { get set }
@ -249,9 +253,13 @@ extension StateManagedNWConnectionChannel {
preconditionFailure("Connection should not be nil") preconditionFailure("Connection should not be nil")
} }
// TODO: Can we do something sensible with these numbers?
self.outstandingRead = true self.outstandingRead = true
conn.receive(minimumIncompleteLength: 1, maximumLength: 8192, completion: self.dataReceivedHandler(content:context:isComplete:error:))
conn.receive(
minimumIncompleteLength: self.minimumIncompleteReceiveLength,
maximumLength: self.maximumReceiveLength,
completion: self.dataReceivedHandler(content:context:isComplete:error:)
)
} }
public func doClose0(error: Error) { public func doClose0(error: Error) {
@ -554,6 +562,10 @@ extension StateManagedNWConnectionChannel {
self.options.supportRemoteHalfClosure = value as! Bool self.options.supportRemoteHalfClosure = value as! Bool
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse: case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
self.allowLocalEndpointReuse = value as! NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse.Value self.allowLocalEndpointReuse = value as! NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse.Value
case is NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption:
self.minimumIncompleteReceiveLength = value as! NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption.Value
case is NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption:
self.maximumReceiveLength = value as! NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption.Value
default: default:
try self.setChannelSpecificOption0(option: option, value: value) try self.setChannelSpecificOption0(option: option, value: value)
} }
@ -610,6 +622,10 @@ extension StateManagedNWConnectionChannel {
throw NIOTSErrors.NoCurrentConnection() throw NIOTSErrors.NoCurrentConnection()
} }
return connection.metadata(definition: optionValue.definition) as! Option.Value return connection.metadata(definition: optionValue.definition) as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSMinimumIncompleteReceiveLengthOption:
return self.minimumIncompleteReceiveLength as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSMaximumReceiveLengthOption:
return self.maximumReceiveLength as! Option.Value
default: default:
// watchOS 6.0 availability is covered by the @available on this extension. // watchOS 6.0 availability is covered by the @available on this extension.
if #available(OSX 10.15, iOS 13.0, tvOS 13.0, *) { if #available(OSX 10.15, iOS 13.0, tvOS 13.0, *) {

View File

@ -139,5 +139,45 @@ class NIOTSChannelOptionsTests: XCTestCase {
XCTAssertEqual(listenerValue, .handover) XCTAssertEqual(listenerValue, .handover)
XCTAssertEqual(connectionValue, .interactive) XCTAssertEqual(connectionValue, .interactive)
} }
func testMinimumIncompleteReceiveLength() throws {
let listener = try NIOTSListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try NIOTSConnectionBootstrap(group: self.group)
.channelOption(NIOTSChannelOptions.minimumIncompleteReceiveLength, value: 1)
.connect(to: listener.localAddress!)
.wait()
defer {
XCTAssertNoThrow(try connection.close().wait())
}
let connectionValue = try assertNoThrowWithValue(connection.getOption(NIOTSChannelOptions.minimumIncompleteReceiveLength).wait())
XCTAssertEqual(connectionValue, 1)
}
func testMaximumReceiveLength() throws {
let listener = try NIOTSListenerBootstrap(group: self.group)
.bind(host: "localhost", port: 0).wait()
defer {
XCTAssertNoThrow(try listener.close().wait())
}
let connection = try NIOTSConnectionBootstrap(group: self.group)
.channelOption(NIOTSChannelOptions.maximumReceiveLength, value: 8192)
.connect(to: listener.localAddress!)
.wait()
defer {
XCTAssertNoThrow(try connection.close().wait())
}
let connectionValue = try assertNoThrowWithValue(connection.getOption(NIOTSChannelOptions.maximumReceiveLength).wait())
XCTAssertEqual(connectionValue, 8192)
}
} }
#endif #endif