@@ -6,7 +6,7 @@ import Foundation
6
6
public actor HubConnection {
7
7
private static let defaultTimeout : TimeInterval = 30
8
8
private static let defaultPingInterval : TimeInterval = 15
9
- private static let defaultStatefulReconnectBufferSize : Int = 100_000_000 // bytes of messages
9
+ private static let defaultStatefulReconnectBufferSize : Int = 100_000_000 // bytes of messages
10
10
11
11
private var invocationBinder : DefaultInvocationBinder
12
12
private var invocationHandler : InvocationHandler
@@ -16,16 +16,16 @@ public actor HubConnection {
16
16
private let logger : Logger
17
17
private let hubProtocol : HubProtocol
18
18
private let connection : ConnectionProtocol
19
- private let retryPolicy : RetryPolicy
19
+ private let reconnectPolicy : RetryPolicy
20
20
private let keepAliveScheduler : TimeScheduler
21
21
private let serverTimeoutScheduler : TimeScheduler
22
22
private let statefulReconnectBufferSize : Int
23
23
24
24
private var connectionStarted : Bool = false
25
25
private var receivedHandshakeResponse : Bool = false
26
26
private var invocationId : Int = 0
27
+ private var messageBuffer : MessageBuffer ? = nil
27
28
private var connectionStatus : HubConnectionState = . Stopped
28
- private var stopping : Bool = false
29
29
private var stopDuringStartError : Error ?
30
30
private nonisolated ( unsafe) var handshakeResolver: ( ( HandshakeResponseMessage ) -> Void ) ?
31
31
private nonisolated ( unsafe) var handshakeRejector: ( ( Error ) -> Void ) ?
@@ -40,16 +40,17 @@ public actor HubConnection {
40
40
internal init ( connection: ConnectionProtocol ,
41
41
logger: Logger ,
42
42
hubProtocol: HubProtocol ,
43
- retryPolicy : RetryPolicy ,
43
+ reconnectPolicy : RetryPolicy ,
44
44
serverTimeout: TimeInterval ? ,
45
45
keepAliveInterval: TimeInterval ? ,
46
46
statefulReconnectBufferSize: Int ? ) {
47
47
self . serverTimeout = serverTimeout ?? HubConnection . defaultTimeout
48
48
self . keepAliveInterval = keepAliveInterval ?? HubConnection . defaultPingInterval
49
- self . statefulReconnectBufferSize = statefulReconnectBufferSize ?? HubConnection . defaultStatefulReconnectBufferSize
49
+ self . statefulReconnectBufferSize =
50
+ statefulReconnectBufferSize ?? HubConnection . defaultStatefulReconnectBufferSize
50
51
51
52
self . logger = logger
52
- self . retryPolicy = retryPolicy
53
+ self . reconnectPolicy = reconnectPolicy
53
54
54
55
self . connection = connection
55
56
self . hubProtocol = hubProtocol
@@ -58,10 +59,12 @@ public actor HubConnection {
58
59
self . invocationHandler = InvocationHandler ( )
59
60
self . keepAliveScheduler = TimeScheduler ( initialInterval: self . keepAliveInterval)
60
61
self . serverTimeoutScheduler = TimeScheduler ( initialInterval: self . serverTimeout)
62
+ self . reconnectedHandlers = [ ]
63
+ self . reconnectingHandlers = [ ]
61
64
}
62
65
63
66
public func start( ) async throws {
64
- if ( connectionStatus != . Stopped) {
67
+ if connectionStatus != . Stopped {
65
68
throw SignalRError . invalidOperation ( " Start client while not in a stopped state. " )
66
69
}
67
70
@@ -77,7 +80,6 @@ public actor HubConnection {
77
80
startSuccessfully = true
78
81
} catch {
79
82
connectionStatus = . Stopped
80
- stopping = false
81
83
await keepAliveScheduler. stop ( )
82
84
await serverTimeoutScheduler. stop ( )
83
85
logger. log ( level: . debug, message: " HubConnection start failed \( error) " )
@@ -96,13 +98,14 @@ public actor HubConnection {
96
98
}
97
99
98
100
// 2. Another stop is running, just wait for it
99
- if ( stopping ) {
101
+ if connectionStatus == . Stopping {
100
102
logger. log ( level: . debug, message: " Connection is already stopping " )
101
103
await stopTask? . value
102
104
return
103
105
}
104
106
105
- stopping = true
107
+ connectionStatus = . Stopping
108
+ await self . connection. setFeature ( feature: ConnectionFeature . Reconnect, value: false )
106
109
107
110
// In this step, there's no other start running
108
111
stopTask = Task {
@@ -291,6 +294,13 @@ public actor HubConnection {
291
294
292
295
private func stopInternal( ) async {
293
296
if ( connectionStatus == . Stopped) {
297
+ logger. log ( level: . debug, message: " Call to HubConnection.stop ignored because it is already in the disconnected state. " )
298
+ return
299
+ }
300
+
301
+ if connectionStatus == . Stopping {
302
+ logger. log ( level: . debug, message: " Call to HubConnection.stop ignored because it is already in the stopping state. " )
303
+ await stopTask? . value
294
304
return
295
305
}
296
306
@@ -325,7 +335,7 @@ public actor HubConnection {
325
335
handshakeRejector!( SignalRError . connectionAborted)
326
336
}
327
337
328
- if ( stopping ) {
338
+ if connectionStatus == . Connecting {
329
339
await completeClose ( error: error)
330
340
return
331
341
}
@@ -335,7 +345,7 @@ public actor HubConnection {
335
345
// 2. Connected: In this case, we should reconnect
336
346
// 3. Reconnecting: In this case, we're in the control of previous reconnect(), let that function handle the reconnection
337
347
338
- if ( connectionStatus == . Connected) {
348
+ if connectionStatus == . Connected {
339
349
do {
340
350
try await reconnect ( error: error)
341
351
} catch {
@@ -351,13 +361,15 @@ public actor HubConnection {
351
361
var lastError : Error ? = error
352
362
353
363
// reconnect
354
- while let interval = retryPolicy. nextRetryInterval ( retryContext: RetryContext (
355
- retryCount: retryCount,
356
- elapsed: elapsed,
357
- retryReason: lastError
358
- ) ) {
364
+ while let interval = reconnectPolicy. nextRetryInterval (
365
+ retryContext: RetryContext (
366
+ retryCount: retryCount,
367
+ elapsed: elapsed,
368
+ retryReason: lastError
369
+ ) )
370
+ {
359
371
try Task . checkCancellation ( )
360
- if ( stopping ) {
372
+ if connectionStatus == . Stopping {
361
373
break
362
374
}
363
375
@@ -380,7 +392,7 @@ public actor HubConnection {
380
392
logger. log ( level: . warning, message: " Connection reconnect failed: \( error) " )
381
393
}
382
394
383
- if ( stopping ) {
395
+ if connectionStatus == . Stopping {
384
396
break
385
397
}
386
398
@@ -424,6 +436,17 @@ public actor HubConnection {
424
436
do {
425
437
let hubMessage = try hubProtocol. parseMessages ( input: data!, binder: invocationBinder)
426
438
for message in hubMessage {
439
+ do {
440
+ if let messageBuffer = self . messageBuffer {
441
+ let shouldProcess = try await messageBuffer. shouldProcessMessage ( message)
442
+ if !shouldProcess {
443
+ // Don't process the message, we are either waiting for a SequenceMessage or received a duplicate message
444
+ continue
445
+ }
446
+ }
447
+ } catch {
448
+ logger. log ( level: . error, message: " Error parsing messages: \( error) " )
449
+ }
427
450
await dispatchMessage ( message)
428
451
}
429
452
} catch {
@@ -499,7 +522,6 @@ public actor HubConnection {
499
522
500
523
private func completeClose( error: Error ? ) async {
501
524
connectionStatus = . Stopped
502
- stopping = false
503
525
await keepAliveScheduler. stop ( )
504
526
await serverTimeoutScheduler. stop ( )
505
527
@@ -513,7 +535,7 @@ public actor HubConnection {
513
535
private func startInternal( ) async throws {
514
536
try Task . checkCancellation ( )
515
537
516
- guard stopping == false else {
538
+ guard connectionStatus != . Stopping else {
517
539
throw SignalRError . invalidOperation ( " Stopping is called " )
518
540
}
519
541
@@ -532,6 +554,7 @@ public actor HubConnection {
532
554
logger. log ( level: . error, message: " Unsupported handshake version: \( version) " )
533
555
throw SignalRError . unsupportedHandshakeVersion
534
556
}
557
+ // TODO: enable version 2 when stateful reconnect is done
535
558
536
559
receivedHandshakeResponse = false
537
560
let handshakeRequest = HandshakeRequestMessage ( protocol: hubProtocol. name, version: version)
@@ -567,6 +590,23 @@ public actor HubConnection {
567
590
}
568
591
}
569
592
593
+ let useStatefulReconnect = await ( self . connection. features [ ConnectionFeature . Reconnect] as? Bool ) == true
594
+ if useStatefulReconnect {
595
+ self . messageBuffer = MessageBuffer (
596
+ hubProtocol: self . hubProtocol, connection: self . connection,
597
+ bufferSize: self . statefulReconnectBufferSize)
598
+ await self . connection. setFeature (
599
+ feature: ConnectionFeature . Disconnected,
600
+ value: { [ weak self] ( ) async -> Void in
601
+ _ = try ? await self ? . messageBuffer? . disconnected ( )
602
+ } )
603
+ await self . connection. setFeature (
604
+ feature: ConnectionFeature . Resend,
605
+ value: { [ weak self] ( ) async -> Any ? in
606
+ return try ? await self ? . messageBuffer? . resend ( )
607
+ } )
608
+ }
609
+
570
610
let inherentKeepAlive = await connection. inherentKeepAlive
571
611
if ( !inherentKeepAlive) {
572
612
await keepAliveScheduler. start {
@@ -808,6 +848,7 @@ public actor HubConnection {
808
848
public enum HubConnectionState {
809
849
// The connection is stopped. Start can only be called if the connection is in this state.
810
850
case Stopped
851
+ case Stopping
811
852
case Connecting
812
853
case Connected
813
854
case Reconnecting
0 commit comments