Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions OpenHABCore/Sources/OpenHABCore/Util/NWPathMonitoring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,9 @@ final class RealPathMonitor: NWPathMonitoring, Sendable {
}

func startMonitoring(handler: @escaping (Bool) async -> Void) async {
if #available(iOS 17, watchOS 10, *) {
for await path in monitor {
Logger.nwPathMonitoring.debug("Path monitor update: \(path.debugDescription)")
await handler(path.status == .satisfied || path.status == .requiresConnection)
}
} else {
for await path in monitor.paths() {
await handler(path.status == .satisfied || path.status == .requiresConnection)
}
for await path in monitor.paths() {
Logger.nwPathMonitoring.debug("Path monitor update: \(path.debugDescription)")
await handler(path.status == .satisfied || path.status == .requiresConnection)
}
}

Expand All @@ -48,21 +42,27 @@ public protocol NWPathMonitoring: AnyObject, Sendable {
func cancel()
}

// MARK: Extension for version iOS <17
// MARK: - AsyncStream wrapper for NWPathMonitor

// this line breaks availability checking, since watchos 10 is minimum for the app
// @available(watchOS, obsoleted: 10.0)
@available(iOS, obsoleted: 17.0)
// Avoids NWPathMonitor's native AsyncSequence conformance (iOS 17+), which has a
// bug where NWPathMonitor.startLocked(lockedState:) holds an internal os_unfair_lock
// and then calls AsyncStream.Continuation.finish() from within that locked context
// via makeAsyncStream(). finish() tries to re-acquire the same lock, causing
// _os_unfair_lock_recursive_abort on com.apple.network.connections.
//
// Our own paths() wrapper avoids makeAsyncStream() entirely. The onTermination
// handler defers cancel() off the AsyncStream._Storage lock context to prevent
// a symmetric re-entrancy risk on the other side.
extension NWPathMonitor {
func paths() -> AsyncStream<NWPath> {
AsyncStream { continuation in
pathUpdateHandler = { path in
continuation.yield(path)
}
continuation.onTermination = { [weak self] _ in
self?.cancel()
DispatchQueue.global(qos: .utility).async { self?.cancel() }
}
start(queue: DispatchQueue(label: "NSPathMonitor.paths"))
start(queue: DispatchQueue(label: "NWPathMonitor.paths"))
}
}
}
Loading