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:
parent
32aa0a71b3
commit
99d28e05f7
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue