Skip to content

Commit 70490c9

Browse files
authored
Address thread-safety of SCNetworkReachability (#295)
* Fix potential concurrency issue w/ connection check. * Slight rework
1 parent 3c49330 commit 70490c9

File tree

2 files changed

+76
-4
lines changed

2 files changed

+76
-4
lines changed

Sources/Segment/Plugins/Platforms/Vendors/AppleUtils.swift

+69-1
Original file line numberDiff line numberDiff line change
@@ -341,14 +341,66 @@ extension ConnectionStatus {
341341
#else
342342
self = .online(.wifi)
343343
#endif
344-
345344
} else {
346345
self = .offline
347346
}
348347
}
349348
}
350349

350+
351+
// MARK: -- Connection Status stuff
352+
353+
internal class ConnectionMonitor {
354+
private var timer: QueueTimer? = nil
355+
356+
static let shared = ConnectionMonitor()
357+
358+
@Atomic var connectionStatus: ConnectionStatus = .unknown
359+
360+
init() {
361+
self.timer = QueueTimer(interval: 300, immediate: true) { [weak self] in
362+
guard let self else { return }
363+
self.check()
364+
}
365+
}
366+
367+
internal func check() {
368+
var zeroAddress = sockaddr_in()
369+
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
370+
zeroAddress.sin_family = sa_family_t(AF_INET)
371+
372+
guard let defaultRouteReachability = (withUnsafePointer(to: &zeroAddress) {
373+
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) { zeroSockAddress in
374+
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
375+
}
376+
}) else {
377+
connectionStatus = .unknown
378+
return
379+
}
380+
381+
var flags : SCNetworkReachabilityFlags = []
382+
if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
383+
connectionStatus = .unknown
384+
return
385+
}
386+
387+
connectionStatus = ConnectionStatus(reachabilityFlags: flags)
388+
}
389+
}
390+
351391
internal func connectionStatus() -> ConnectionStatus {
392+
return ConnectionMonitor.shared.connectionStatus
393+
}
394+
395+
/*
396+
/* 5-minute timer to check connection status. Checking this for
397+
every event that comes through seems like overkill. */
398+
399+
private var __segment_connectionStatus: ConnectionStatus = .unknown
400+
private var __segment_connectionStatusTimer: QueueTimer? = nil
401+
private var __segment_connectionStatusLock = NSLock()
402+
403+
internal func __segment_connectionStatusCheck() -> ConnectionStatus {
352404
var zeroAddress = sockaddr_in()
353405
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
354406
zeroAddress.sin_family = sa_family_t(AF_INET)
@@ -369,4 +421,20 @@ internal func connectionStatus() -> ConnectionStatus {
369421
return ConnectionStatus(reachabilityFlags: flags)
370422
}
371423

424+
internal func connectionStatus() -> ConnectionStatus {
425+
// the locking may seem like overkill since we're updating it in a queue
426+
// however, it is necessary since we're polling. :(
427+
if __segment_connectionStatusTimer == nil {
428+
__segment_connectionStatusTimer = QueueTimer(interval: 300, immediate: true) {
429+
__segment_connectionStatusLock.lock()
430+
defer { __segment_connectionStatusLock.unlock() }
431+
__segment_connectionStatus = __segment_connectionStatusCheck()
432+
}
433+
}
434+
435+
__segment_connectionStatusLock.lock()
436+
defer { __segment_connectionStatusLock.unlock() }
437+
return __segment_connectionStatus
438+
}
439+
*/
372440
#endif

Sources/Segment/Utilities/QueueTimer.swift

+7-3
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,22 @@ internal class QueueTimer {
2222

2323
static var timers = [QueueTimer]()
2424

25-
static func schedule(interval: TimeInterval, queue: DispatchQueue = .main, handler: @escaping () -> Void) {
25+
static func schedule(interval: TimeInterval, immediate: Bool = false, queue: DispatchQueue = .main, handler: @escaping () -> Void) {
2626
let timer = QueueTimer(interval: interval, queue: queue, handler: handler)
2727
Self.timers.append(timer)
2828
}
2929

30-
init(interval: TimeInterval, queue: DispatchQueue = .main, handler: @escaping () -> Void) {
30+
init(interval: TimeInterval, immediate: Bool = false, queue: DispatchQueue = .main, handler: @escaping () -> Void) {
3131
self.interval = interval
3232
self.queue = queue
3333
self.handler = handler
3434

3535
timer = DispatchSource.makeTimerSource(flags: [], queue: queue)
36-
timer.schedule(deadline: .now() + self.interval, repeating: self.interval)
36+
if immediate {
37+
timer.schedule(deadline: .now(), repeating: self.interval)
38+
} else {
39+
timer.schedule(deadline: .now() + self.interval, repeating: self.interval)
40+
}
3741
timer.setEventHandler { [weak self] in
3842
self?.handler()
3943
}

0 commit comments

Comments
 (0)