From 7969c77b79b7c8edf1bc1262c17de4cd39c66dd5 Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Tue, 28 Nov 2023 11:44:42 +0100 Subject: [PATCH] Throttle network path updates in packet tunnel --- .../PacketTunnelPathObserver.swift | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift index b16c62f705fe..379deb3e3080 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift @@ -6,17 +6,19 @@ // Copyright © 2023 Mullvad VPN AB. All rights reserved. // -import Foundation +import Combine import NetworkExtension import PacketTunnelCore final class PacketTunnelPathObserver: DefaultPathObserverProtocol { private weak var packetTunnelProvider: NEPacketTunnelProvider? private let stateLock = NSLock() - private var observationToken: NSKeyValueObservation? + private var cancellable: AnyCancellable? + private let eventQueue: DispatchQueue - init(packetTunnelProvider: NEPacketTunnelProvider) { + init(packetTunnelProvider: NEPacketTunnelProvider, eventQueue: DispatchQueue) { self.packetTunnelProvider = packetTunnelProvider + self.eventQueue = eventQueue } var defaultPath: NetworkPath? { @@ -25,22 +27,26 @@ final class PacketTunnelPathObserver: DefaultPathObserverProtocol { func start(_ body: @escaping (NetworkPath) -> Void) { stateLock.withLock { - observationToken?.invalidate() + cancellable?.cancel() // Normally packet tunnel provider should exist throughout the network extension lifetime. - observationToken = packetTunnelProvider?.observe(\.defaultPath, options: [.new]) { _, change in - let nwPath = change.newValue.flatMap { $0 } - if let nwPath { - body(nwPath) + cancellable = packetTunnelProvider?.publisher(for: \.defaultPath) + .removeDuplicates(by: { oldPath, newPath in + oldPath?.status == newPath?.status + }) + .throttle(for: .seconds(2), scheduler: eventQueue, latest: true) + .sink { change in + if let change { + body(change) + } } - } } } func stop() { stateLock.withLock { - observationToken?.invalidate() - observationToken = nil + cancellable?.cancel() + cancellable = nil } } }