From 164f1976c794440648d205ea008f49df9f95c540 Mon Sep 17 00:00:00 2001 From: Jamil Date: Mon, 17 Nov 2025 08:09:47 -0800 Subject: [PATCH] fix(apple): queue path updates onto workQueue (#10896) Path updates are received on a queue which can be (and is typically) on a different thread from the one the workQueue runs on. Since we are sharing instance properties across these two threads, we need to make sure reads and writes to all properties ideally happen on the same queue. Moving `Adapter` to an actor class could solve these issues, but that is a much larger refactor to be done in #10895 and we'd like to ship this fix in the next release to verify it fixes our issue. --- .../FirezoneNetworkExtension/Adapter.swift | 53 ++++++++++--------- website/src/components/Changelog/Apple.tsx | 3 ++ 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/swift/apple/FirezoneNetworkExtension/Adapter.swift b/swift/apple/FirezoneNetworkExtension/Adapter.swift index 5f9ba57eb..db32114e1 100644 --- a/swift/apple/FirezoneNetworkExtension/Adapter.swift +++ b/swift/apple/FirezoneNetworkExtension/Adapter.swift @@ -127,29 +127,33 @@ class Adapter: @unchecked Sendable { private lazy var pathUpdateHandler: @Sendable (Network.NWPath) -> Void = { [weak self] path in guard let self else { return } - if path.status == .unsatisfied { - // Check if we need to set reasserting, avoids OS log spam and potentially other side effects - if self.packetTunnelProvider?.reasserting == false { - // Tell the UI we're not connected - self.packetTunnelProvider?.reasserting = true - } - } else { - if self.packetTunnelProvider?.reasserting == true { - self.packetTunnelProvider?.reasserting = false - } + workQueue.async { [weak self] in + guard let self else { return } - if path.connectivityDifferentFrom(path: lastPath) { - // Tell connlib to reset network state and DNS resolvers, but only do so if our connectivity has - // meaningfully changed. On darwin, this is needed to send packets - // out of a different interface even when 0.0.0.0 is used as the source. - // If our primary interface changes, we can be certain the old socket shouldn't be - // used anymore. - self.sendCommand(.reset("primary network path changed")) + if path.status == .unsatisfied { + // Check if we need to set reasserting, avoids OS log spam and potentially other side effects + if self.packetTunnelProvider?.reasserting == false { + // Tell the UI we're not connected + self.packetTunnelProvider?.reasserting = true + } + } else { + if self.packetTunnelProvider?.reasserting == true { + self.packetTunnelProvider?.reasserting = false + } + + if path.connectivityDifferentFrom(path: lastPath) { + // Tell connlib to reset network state and DNS resolvers, but only do so if our connectivity has + // meaningfully changed. On darwin, this is needed to send packets + // out of a different interface even when 0.0.0.0 is used as the source. + // If our primary interface changes, we can be certain the old socket shouldn't be + // used anymore. + self.sendCommand(.reset("primary network path changed")) + } + + setSystemDefaultResolvers(path) + + lastPath = path } - - setSystemDefaultResolvers(path) - - lastPath = path } } @@ -189,14 +193,15 @@ class Adapter: @unchecked Sendable { deinit { Log.log("Adapter.deinit") + // Cancel network monitor + networkMonitor?.cancel() + networkMonitor = nil + // Cancel all Tasks - this triggers cooperative cancellation // Event loop checks Task.isCancelled in its polling loop // Event consumer will exit when eventSender.deinit closes the stream eventLoopTask?.cancel() eventConsumerTask?.cancel() - - // Cancel network monitor - networkMonitor?.cancel() } func start() throws { diff --git a/website/src/components/Changelog/Apple.tsx b/website/src/components/Changelog/Apple.tsx index df2580b92..7321036ae 100644 --- a/website/src/components/Changelog/Apple.tsx +++ b/website/src/components/Changelog/Apple.tsx @@ -25,6 +25,9 @@ export default function Apple() { {/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */} + + Fixes a minor race condition that could arise on sign out. + Fixes an issue on macOS where the utun index would auto-increment by itself on configuration updates.