|
1 | 1 | import Foundation |
2 | 2 | import Network |
| 3 | +import os |
3 | 4 |
|
4 | 5 | @MainActor |
5 | 6 | class ConnectivityChecker { |
@@ -84,7 +85,7 @@ class ConnectivityChecker { |
84 | 85 | } |
85 | 86 | var storage = sockaddr_storage() |
86 | 87 | data.withUnsafeBytes { bytes in |
87 | | - memcpy(&storage, bytes.baseAddress!, min(bytes.count, MemoryLayout<sockaddr_storage>.size)) |
| 88 | + _ = memcpy(&storage, bytes.baseAddress!, min(bytes.count, MemoryLayout<sockaddr_storage>.size)) |
88 | 89 | } |
89 | 90 |
|
90 | 91 | var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) |
@@ -140,35 +141,34 @@ class ConnectivityChecker { |
140 | 141 | using: .tcp |
141 | 142 | ) |
142 | 143 |
|
143 | | - var completed = false |
144 | | - let timeoutTask = DispatchWorkItem { |
145 | | - if !completed { |
146 | | - completed = true |
147 | | - connection.cancel() |
148 | | - continuation.resume(throwing: NSError( |
149 | | - domain: "ConnectivityChecker", |
150 | | - code: -1, |
151 | | - userInfo: [NSLocalizedDescriptionKey: "Connection timeout"] |
152 | | - )) |
| 144 | + // Returns true only on the first call, so the continuation is resumed exactly once |
| 145 | + let completed = OSAllocatedUnfairLock(initialState: false) |
| 146 | + let markCompleted: @Sendable () -> Bool = { |
| 147 | + completed.withLock { done in |
| 148 | + if done { return false } |
| 149 | + done = true |
| 150 | + return true |
153 | 151 | } |
154 | 152 | } |
| 153 | + let timeoutTask = DispatchWorkItem { |
| 154 | + guard markCompleted() else { return } |
| 155 | + connection.cancel() |
| 156 | + continuation.resume(throwing: NSError( |
| 157 | + domain: "ConnectivityChecker", |
| 158 | + code: -1, |
| 159 | + userInfo: [NSLocalizedDescriptionKey: "Connection timeout"] |
| 160 | + )) |
| 161 | + } |
155 | 162 |
|
156 | 163 | connection.stateUpdateHandler = { state in |
157 | | - guard !completed else { return } |
158 | | - |
159 | 164 | switch state { |
160 | 165 | case .ready: |
161 | | - completed = true |
| 166 | + guard markCompleted() else { return } |
162 | 167 | timeoutTask.cancel() |
163 | 168 | connection.cancel() |
164 | 169 | continuation.resume() |
165 | | - case let .failed(error): |
166 | | - completed = true |
167 | | - timeoutTask.cancel() |
168 | | - connection.cancel() |
169 | | - continuation.resume(throwing: error) |
170 | | - case let .waiting(error): |
171 | | - completed = true |
| 170 | + case let .failed(error), let .waiting(error): |
| 171 | + guard markCompleted() else { return } |
172 | 172 | timeoutTask.cancel() |
173 | 173 | connection.cancel() |
174 | 174 | continuation.resume(throwing: error) |
|
0 commit comments