From 9839e57df56ab40b26c93bc62c7b9440eab7e507 Mon Sep 17 00:00:00 2001 From: Brandon Sneed Date: Mon, 12 Feb 2024 10:37:05 -0800 Subject: [PATCH 1/2] Fix potential concurrency issue w/ connection check. --- .../Plugins/Platforms/Vendors/AppleUtils.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift b/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift index 4e9b8986..2cdeac3e 100644 --- a/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift +++ b/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift @@ -341,14 +341,24 @@ extension ConnectionStatus { #else self = .online(.wifi) #endif - } else { self = .offline } } } -internal func connectionStatus() -> ConnectionStatus { + +// MARK: -- Connection Status stuff + +/* 10-minute timer to check connection status. Checking this for + every event that comes through seems like overkill. */ + +private var __segment_connectionStatus: ConnectionStatus = .unknown +private let __segment_connectionStatusTimer = QueueTimer(interval: 600) { + __segment_connectionStatus = __segment_connectionStatusCheck() +} + +internal func __segment_connectionStatusCheck() -> ConnectionStatus { var zeroAddress = sockaddr_in() zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) zeroAddress.sin_family = sa_family_t(AF_INET) @@ -369,4 +379,8 @@ internal func connectionStatus() -> ConnectionStatus { return ConnectionStatus(reachabilityFlags: flags) } +internal func connectionStatus() -> ConnectionStatus { + return __segment_connectionStatus +} + #endif From 2094f3d26bab2014718b2de20176e4a43f648b5c Mon Sep 17 00:00:00 2001 From: Brandon Sneed Date: Mon, 12 Feb 2024 12:06:47 -0800 Subject: [PATCH 2/2] Slight rework --- .../Platforms/Vendors/AppleUtils.swift | 64 +++++++++++++++++-- Sources/Segment/Utilities/QueueTimer.swift | 10 ++- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift b/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift index 2cdeac3e..60245d73 100644 --- a/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift +++ b/Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift @@ -350,13 +350,55 @@ extension ConnectionStatus { // MARK: -- Connection Status stuff -/* 10-minute timer to check connection status. Checking this for +internal class ConnectionMonitor { + private var timer: QueueTimer? = nil + + static let shared = ConnectionMonitor() + + @Atomic var connectionStatus: ConnectionStatus = .unknown + + init() { + self.timer = QueueTimer(interval: 300, immediate: true) { [weak self] in + guard let self else { return } + self.check() + } + } + + internal func check() { + var zeroAddress = sockaddr_in() + zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) + zeroAddress.sin_family = sa_family_t(AF_INET) + + guard let defaultRouteReachability = (withUnsafePointer(to: &zeroAddress) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { zeroSockAddress in + SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) + } + }) else { + connectionStatus = .unknown + return + } + + var flags : SCNetworkReachabilityFlags = [] + if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) { + connectionStatus = .unknown + return + } + + connectionStatus = ConnectionStatus(reachabilityFlags: flags) + } +} + +internal func connectionStatus() -> ConnectionStatus { + return ConnectionMonitor.shared.connectionStatus +} + +/* +/* 5-minute timer to check connection status. Checking this for every event that comes through seems like overkill. */ private var __segment_connectionStatus: ConnectionStatus = .unknown -private let __segment_connectionStatusTimer = QueueTimer(interval: 600) { - __segment_connectionStatus = __segment_connectionStatusCheck() -} +private var __segment_connectionStatusTimer: QueueTimer? = nil +private var __segment_connectionStatusLock = NSLock() internal func __segment_connectionStatusCheck() -> ConnectionStatus { var zeroAddress = sockaddr_in() @@ -380,7 +422,19 @@ internal func __segment_connectionStatusCheck() -> ConnectionStatus { } internal func connectionStatus() -> ConnectionStatus { + // the locking may seem like overkill since we're updating it in a queue + // however, it is necessary since we're polling. :( + if __segment_connectionStatusTimer == nil { + __segment_connectionStatusTimer = QueueTimer(interval: 300, immediate: true) { + __segment_connectionStatusLock.lock() + defer { __segment_connectionStatusLock.unlock() } + __segment_connectionStatus = __segment_connectionStatusCheck() + } + } + + __segment_connectionStatusLock.lock() + defer { __segment_connectionStatusLock.unlock() } return __segment_connectionStatus } - +*/ #endif diff --git a/Sources/Segment/Utilities/QueueTimer.swift b/Sources/Segment/Utilities/QueueTimer.swift index 18324c71..da6d97fd 100644 --- a/Sources/Segment/Utilities/QueueTimer.swift +++ b/Sources/Segment/Utilities/QueueTimer.swift @@ -22,18 +22,22 @@ internal class QueueTimer { static var timers = [QueueTimer]() - static func schedule(interval: TimeInterval, queue: DispatchQueue = .main, handler: @escaping () -> Void) { + static func schedule(interval: TimeInterval, immediate: Bool = false, queue: DispatchQueue = .main, handler: @escaping () -> Void) { let timer = QueueTimer(interval: interval, queue: queue, handler: handler) Self.timers.append(timer) } - init(interval: TimeInterval, queue: DispatchQueue = .main, handler: @escaping () -> Void) { + init(interval: TimeInterval, immediate: Bool = false, queue: DispatchQueue = .main, handler: @escaping () -> Void) { self.interval = interval self.queue = queue self.handler = handler timer = DispatchSource.makeTimerSource(flags: [], queue: queue) - timer.schedule(deadline: .now() + self.interval, repeating: self.interval) + if immediate { + timer.schedule(deadline: .now(), repeating: self.interval) + } else { + timer.schedule(deadline: .now() + self.interval, repeating: self.interval) + } timer.setEventHandler { [weak self] in self?.handler() }