Added support for connection viability updates (#207)

# Enhancement: Integration of Viability Handler in `Network.framework`

We have successfully integrated the `viabilityUpdateHandler` from
`Network.framework` into `NIOTransportServices`, enabling developers to
leverage the full potential of the `Network.framework` API.

This enhancement provides adopters with critical insights into the
viability of connections to remote endpoints. By being informed about
the current network status, developers can implement a more responsive
and adaptive approach to network behavior and conditions.

## Modifications

- Introduced two new methods along with corresponding calls to these
methods within the `Network.framework` handler.
- These handlers trigger the `NIO` *InboundEventsTriggered* method,
allowing consumers to respond to network changes effectively.
- Developed two structs within *NIOTSNetworkEvents* to accompany the
*InboundEventsTriggered* methods, ensuring seamless data transfer.
- Conformed these structs to Sendability for compatibility with the
required operating systems.

## Result

Clients can now receive viability events from `Network.framework`,
enhancing their ability to manage network connections dynamically

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
Co-authored-by: Cory Benfield <lukasa@apple.com>
This commit is contained in:
Cartisim Development 2024-09-16 21:11:19 +08:00 committed by GitHub
parent 32aa0a71b3
commit 99d28e05f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 77 additions and 0 deletions

View File

@ -409,6 +409,13 @@ extension NIOTSConnectionChannel {
self.pipeline.fireUserInboundEventTriggered(NIOTSNetworkEvents.BetterPathUnavailable())
}
}
/// Called by the underlying `NWConnection` when a path becomes viable or non-viable
///
/// Notifies the channel pipeline of the new viability.
private func viabilityUpdateHandler(_ isViable: Bool) {
self.pipeline.fireUserInboundEventTriggered(NIOTSNetworkEvents.ViabilityUpdate(isViable: isViable))
}
/// Called by the underlying `NWConnection` when this connection changes its network path.
///

View File

@ -43,6 +43,19 @@ public enum NIOTSNetworkEvents {
/// Create a new ``NIOTSNetworkEvents/BetterPathUnavailable`` event.
public init(){ }
}
/// ``ViabilityUpdate`` is triggered when the OS informs NIO that communication
/// with the remote endpoint is possible, indicating that the connection is viable.
public struct ViabilityUpdate: NIOTSNetworkEvent {
/// The current viability for the connection
public var isViable: Bool
/// Create a new ``NIOTSNetworkEvents/ViabilityUpdate`` event.
public init(isViable: Bool) {
self.isViable = isViable
}
}
/// ``PathChanged`` is fired whenever the OS has informed NIO that a new path is in use
/// for this `Channel`.
@ -106,6 +119,8 @@ extension NIOTSNetworkEvents.BetterPathAvailable: Sendable {}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSNetworkEvents.BetterPathUnavailable: Sendable {}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSNetworkEvents.ViabilityUpdate: Sendable {}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSNetworkEvents.PathChanged: Sendable {}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSNetworkEvents.ConnectToNWEndpoint: Sendable {}

View File

@ -140,6 +140,7 @@ extension StateManagedNWConnectionChannel {
let connection = NWConnection(to: target, using: parameters)
connection.stateUpdateHandler = self.stateUpdateHandler(newState:)
connection.betterPathUpdateHandler = self.betterPathHandler
connection.viabilityUpdateHandler = self.viabilityUpdateHandler
connection.pathUpdateHandler = self.pathChangedHandler(newPath:)
// Ok, state is ready. Let's go!
@ -230,6 +231,7 @@ extension StateManagedNWConnectionChannel {
self.connectPromise = promise
connection.stateUpdateHandler = self.stateUpdateHandler(newState:)
connection.betterPathUpdateHandler = self.betterPathHandler
connection.viabilityUpdateHandler = self.viabilityUpdateHandler
connection.pathUpdateHandler = self.pathChangedHandler(newPath:)
connection.start(queue: self.connectionQueue)
}
@ -433,6 +435,13 @@ extension StateManagedNWConnectionChannel {
self.pipeline.fireUserInboundEventTriggered(NIOTSNetworkEvents.BetterPathUnavailable())
}
}
/// Called by the underlying `NWConnection` when a path becomes viable or non-viable
///
/// Notifies the channel pipeline of the new viability.
private func viabilityUpdateHandler(_ isViable: Bool) {
self.pipeline.fireUserInboundEventTriggered(NIOTSNetworkEvents.ViabilityUpdate(isViable: isViable))
}
/// Called by the underlying `NWConnection` when this connection changes its network path.
///

View File

@ -557,5 +557,51 @@ class NIOTSEndToEndTests: XCTestCase {
print(error)
}
}
func testViabilityUpdate() throws {
final class ViabilityHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
private let testCompletePromise: EventLoopPromise<Bool>
init(testCompletePromise: EventLoopPromise<Bool>) {
self.testCompletePromise = testCompletePromise
}
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
if let update = event as? NIOTSNetworkEvents.ViabilityUpdate {
testCompletePromise.succeed(update.isViable)
}
}
}
let listener = try NIOTSListenerBootstrap(group: self.group)
.childChannelInitializer { channel in
return channel.eventLoop.makeSucceededVoidFuture()
}
.bind(host: "localhost", port: 0)
.wait()
let testCompletePromise = self.group.next().makePromise(of: Bool.self)
let connection = try NIOTSConnectionBootstrap(group: self.group)
.channelInitializer { channel in
channel.pipeline.addHandler(
ViabilityHandler(
testCompletePromise: testCompletePromise
)
)
}
.connect(to: listener.localAddress!)
.wait()
do {
let result = try testCompletePromise.futureResult.wait()
XCTAssertEqual(result, true)
} catch {
XCTFail("Threw unexpected error \(error)")
}
XCTAssertNoThrow(try connection.close().wait())
XCTAssertNoThrow(try listener.close().wait())
}
}
#endif