Skip to content

Commit

Permalink
Provide configurability for receiving connection data (#212)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
clintonpi authored Oct 17, 2024
1 parent fc398db commit bbd5e63
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 2 deletions.
14 changes: 14 additions & 0 deletions Sources/NIOTransportServices/Datagram/NIOTSDatagramChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
/// after the initial connection attempt has been made.
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.
internal let connectionQueue: DispatchQueue

Expand Down Expand Up @@ -169,11 +175,15 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
internal init(eventLoop: NIOTSEventLoop,
parent: Channel? = nil,
qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
udpOptions: NWProtocolUDP.Options,
tlsOptions: NWProtocolTLS.Options?) {
self.tsEventLoop = eventLoop
self.closePromise = eventLoop.makePromise()
self.parent = parent
self.minimumIncompleteReceiveLength = minimumIncompleteReceiveLength
self.maximumReceiveLength = maximumReceiveLength
self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos)
self.udpOptions = udpOptions
self.tlsOptions = tlsOptions
Expand All @@ -187,11 +197,15 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
on eventLoop: NIOTSEventLoop,
parent: Channel,
qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
udpOptions: NWProtocolUDP.Options,
tlsOptions: NWProtocolTLS.Options?) {
self.init(eventLoop: eventLoop,
parent: parent,
qos: qos,
minimumIncompleteReceiveLength: minimumIncompleteReceiveLength,
maximumReceiveLength: maximumReceiveLength,
udpOptions: udpOptions,
tlsOptions: tlsOptions)
self.connection = connection
Expand Down
26 changes: 26 additions & 0 deletions Sources/NIOTransportServices/NIOTSChannelOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public struct NIOTSChannelOptions {
/// See: ``Types/NIOTSListenerOption``.
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
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()
}


Expand Down Expand Up @@ -179,6 +185,26 @@ extension NIOTSChannelOptions {

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() {}
}
}
}

Expand Down
14 changes: 14 additions & 0 deletions Sources/NIOTransportServices/NIOTSConnectionChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
/// after the initial connection attempt has been made.
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.
internal let connectionQueue: DispatchQueue

Expand Down Expand Up @@ -230,11 +236,15 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
internal init(eventLoop: NIOTSEventLoop,
parent: Channel? = nil,
qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
tcpOptions: NWProtocolTCP.Options,
tlsOptions: NWProtocolTLS.Options?) {
self.tsEventLoop = eventLoop
self.closePromise = eventLoop.makePromise()
self.parent = parent
self.minimumIncompleteReceiveLength = minimumIncompleteReceiveLength
self.maximumReceiveLength = maximumReceiveLength
self.connectionQueue = eventLoop.channelQueue(label: "nio.nioTransportServices.connectionchannel", qos: qos)
self.tcpOptions = tcpOptions
self.tlsOptions = tlsOptions
Expand All @@ -248,11 +258,15 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
on eventLoop: NIOTSEventLoop,
parent: Channel? = nil,
qos: DispatchQoS? = nil,
minimumIncompleteReceiveLength: Int = 1,
maximumReceiveLength: Int = 8192,
tcpOptions: NWProtocolTCP.Options,
tlsOptions: NWProtocolTLS.Options?) {
self.init(eventLoop: eventLoop,
parent: parent,
qos: qos,
minimumIncompleteReceiveLength: minimumIncompleteReceiveLength,
maximumReceiveLength: maximumReceiveLength,
tcpOptions: tcpOptions,
tlsOptions: tlsOptions)
self.connection = connection
Expand Down
20 changes: 18 additions & 2 deletions Sources/NIOTransportServices/StateManagedNWConnectionChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ internal protocol StateManagedNWConnectionChannel: StateManagedChannel where Act

var connection: NWConnection? { get set }

var minimumIncompleteReceiveLength: Int { get set }

var maximumReceiveLength: Int { get set }

var connectionQueue: DispatchQueue { get }

var connectPromise: EventLoopPromise<Void>? { get set }
Expand Down Expand Up @@ -249,9 +253,13 @@ extension StateManagedNWConnectionChannel {
preconditionFailure("Connection should not be nil")
}

// TODO: Can we do something sensible with these numbers?
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) {
Expand Down Expand Up @@ -554,6 +562,10 @@ extension StateManagedNWConnectionChannel {
self.options.supportRemoteHalfClosure = value as! Bool
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
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:
try self.setChannelSpecificOption0(option: option, value: value)
}
Expand Down Expand Up @@ -610,6 +622,10 @@ extension StateManagedNWConnectionChannel {
throw NIOTSErrors.NoCurrentConnection()
}
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:
// watchOS 6.0 availability is covered by the @available on this extension.
if #available(OSX 10.15, iOS 13.0, tvOS 13.0, *) {
Expand Down
40 changes: 40 additions & 0 deletions Tests/NIOTransportServicesTests/NIOTSChannelOptionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,45 @@ class NIOTSChannelOptionsTests: XCTestCase {
XCTAssertEqual(listenerValue, .handover)
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

0 comments on commit bbd5e63

Please sign in to comment.