Skip to content

Commit 04dd4f2

Browse files
Improved migration code, fixed NWConnection leak, and added validation to prevent reconnection loop (#44)
1 parent 19d2395 commit 04dd4f2

File tree

1 file changed

+60
-45
lines changed

1 file changed

+60
-45
lines changed

Sources/NWWebSocket/Model/Client/NWWebSocket.swift

+60-45
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ open class NWWebSocket: WebSocketConnection {
2020
return options
2121
}
2222

23+
private let errorWhileWaitingLimit = 20
24+
2325
// MARK: - Private properties
2426

2527
private var connection: NWConnection?
@@ -28,6 +30,8 @@ open class NWWebSocket: WebSocketConnection {
2830
private let connectionQueue: DispatchQueue
2931
private var pingTimer: Timer?
3032
private var disconnectionWorkItem: DispatchWorkItem?
33+
private var isMigratingConnection = false
34+
private var errorWhileWaitingCount = 0
3135

3236
// MARK: - Initialization
3337

@@ -77,6 +81,11 @@ open class NWWebSocket: WebSocketConnection {
7781
}
7882
}
7983

84+
deinit {
85+
connection?.intentionalDisconnection = true
86+
connection?.cancel()
87+
}
88+
8089
// MARK: - WebSocketConnection conformance
8190

8291
/// Connect to the WebSocket.
@@ -94,6 +103,8 @@ open class NWWebSocket: WebSocketConnection {
94103
}
95104
listen()
96105
connection?.start(queue: connectionQueue)
106+
} else if connection?.state != .ready && !isMigratingConnection {
107+
connection?.start(queue: connectionQueue)
97108
}
98109
}
99110

@@ -189,8 +200,12 @@ open class NWWebSocket: WebSocketConnection {
189200
let context = NWConnection.ContentContext(identifier: "closeContext",
190201
metadata: [metadata])
191202

192-
// See implementation of `send(data:context:)` for `scheduleDisconnection(closeCode:, reason:)`
193-
send(data: nil, context: context)
203+
if connection?.state == .ready {
204+
// See implementation of `send(data:context:)` for `scheduleDisconnection(closeCode:, reason:)`
205+
send(data: nil, context: context)
206+
} else {
207+
scheduleDisconnectionReporting(closeCode: closeCode, reason: nil)
208+
}
194209
}
195210
}
196211

@@ -203,14 +218,26 @@ open class NWWebSocket: WebSocketConnection {
203218
private func stateDidChange(to state: NWConnection.State) {
204219
switch state {
205220
case .ready:
221+
isMigratingConnection = false
206222
delegate?.webSocketDidConnect(connection: self)
207223
case .waiting(let error):
224+
isMigratingConnection = false
208225
reportErrorOrDisconnection(error)
226+
227+
/// Workaround to prevent loop while reconnecting
228+
errorWhileWaitingCount += 1
229+
if errorWhileWaitingCount >= errorWhileWaitingLimit {
230+
tearDownConnection(error: error)
231+
errorWhileWaitingCount = 0
232+
}
209233
case .failed(let error):
234+
errorWhileWaitingCount = 0
235+
isMigratingConnection = false
210236
tearDownConnection(error: error)
211237
case .setup, .preparing:
212238
break
213239
case .cancelled:
240+
errorWhileWaitingCount = 0
214241
tearDownConnection(error: nil)
215242
@unknown default:
216243
fatalError()
@@ -243,35 +270,22 @@ open class NWWebSocket: WebSocketConnection {
243270
/// - Parameter completionHandler: Returns a `Result`with the new connection if the migration was successful
244271
/// or a `NWError` if the migration failed for some reason.
245272
private func migrateConnection(completionHandler: @escaping (Result<WebSocketConnection, NWError>) -> Void) {
246-
247-
let migratedConnection = NWConnection(to: endpoint, using: parameters)
248-
migratedConnection.stateUpdateHandler = { [weak self] state in
249-
guard let self = self else {
250-
return
251-
}
252-
253-
switch state {
254-
case .ready:
255-
self.connection = nil
256-
migratedConnection.stateUpdateHandler = self.stateDidChange(to:)
257-
migratedConnection.betterPathUpdateHandler = self.betterPath(isAvailable:)
258-
migratedConnection.viabilityUpdateHandler = self.viabilityDidChange(isViable:)
259-
self.connection = migratedConnection
260-
self.listen()
261-
completionHandler(.success(self))
262-
case .waiting(let error):
263-
completionHandler(.failure(error))
264-
case .failed(let error):
265-
completionHandler(.failure(error))
266-
case .setup, .preparing:
267-
break
268-
case .cancelled:
269-
completionHandler(.failure(.posix(.ECANCELED)))
270-
@unknown default:
271-
fatalError()
272-
}
273+
guard !isMigratingConnection else { return }
274+
connection?.intentionalDisconnection = true
275+
connection?.cancel()
276+
isMigratingConnection = true
277+
connection = NWConnection(to: endpoint, using: parameters)
278+
connection?.stateUpdateHandler = { [weak self] state in
279+
self?.stateDidChange(to: state)
280+
}
281+
connection?.betterPathUpdateHandler = { [weak self] isAvailable in
282+
self?.betterPath(isAvailable: isAvailable)
273283
}
274-
migratedConnection.start(queue: connectionQueue)
284+
connection?.viabilityUpdateHandler = { [weak self] isViable in
285+
self?.viabilityDidChange(isViable: isViable)
286+
}
287+
listen()
288+
connection?.start(queue: connectionQueue)
275289
}
276290

277291
// MARK: Connection data transfer
@@ -321,21 +335,21 @@ open class NWWebSocket: WebSocketConnection {
321335
contentContext: context,
322336
isComplete: true,
323337
completion: .contentProcessed({ [weak self] error in
324-
guard let self = self else {
325-
return
326-
}
327-
328-
// If a connection closure was sent, inform delegate on completion
329-
if let socketMetadata = context.protocolMetadata.first as? NWProtocolWebSocket.Metadata,
330-
socketMetadata.opcode == .close {
331-
self.scheduleDisconnectionReporting(closeCode: socketMetadata.closeCode,
332-
reason: data)
333-
}
334-
335-
if let error = error {
336-
self.reportErrorOrDisconnection(error)
337-
}
338-
}))
338+
guard let self = self else {
339+
return
340+
}
341+
342+
// If a connection closure was sent, inform delegate on completion
343+
if let socketMetadata = context.protocolMetadata.first as? NWProtocolWebSocket.Metadata,
344+
socketMetadata.opcode == .close {
345+
self.scheduleDisconnectionReporting(closeCode: socketMetadata.closeCode,
346+
reason: data)
347+
}
348+
349+
if let error = error {
350+
self.reportErrorOrDisconnection(error)
351+
}
352+
}))
339353
}
340354

341355
// MARK: Connection cleanup
@@ -369,6 +383,7 @@ open class NWWebSocket: WebSocketConnection {
369383
delegate?.webSocketDidReceiveError(connection: self, error: error)
370384
}
371385
pingTimer?.invalidate()
386+
connection?.cancel()
372387
connection = nil
373388

374389
if let disconnectionWorkItem = disconnectionWorkItem {

0 commit comments

Comments
 (0)