1+ using System ;
2+ using StreamChat . Core . LowLevelClient . Models ;
3+ using StreamChat . Libs . NetworkMonitors ;
4+ using StreamChat . Libs . Time ;
5+
6+ namespace StreamChat . Core . LowLevelClient
7+ {
8+ /// <summary>
9+ /// Schedules next reconnection time based on the past attempts and network availability
10+ /// </summary>
11+ internal class ReconnectScheduler : IDisposable
12+ {
13+ public event Action ReconnectionScheduled ;
14+ public ReconnectStrategy ReconnectStrategy { get ; private set ; } = ReconnectStrategy . Exponential ;
15+ public float ReconnectConstantInterval { get ; private set ; } = 1 ;
16+ public float ReconnectExponentialMinInterval { get ; private set ; } = 0.01f ;
17+ public float ReconnectExponentialMaxInterval { get ; private set ; } = 64 ;
18+ public int ReconnectMaxInstantTrials { get ; private set ; } = 5 ; //StreamTodo: allow to control this by user
19+
20+ public double ? NextReconnectTime
21+ {
22+ get => _nextReconnectTime ;
23+ private set
24+ {
25+ var isEqual = _nextReconnectTime . HasValue && value . HasValue &&
26+ Math . Abs ( _nextReconnectTime . Value - value . Value ) < float . Epsilon ;
27+ if ( isEqual )
28+ {
29+ return ;
30+ }
31+
32+ _nextReconnectTime = value ;
33+
34+ if ( _nextReconnectTime . HasValue )
35+ {
36+ ReconnectionScheduled ? . Invoke ( ) ;
37+ }
38+ }
39+ }
40+
41+ public ReconnectScheduler ( ITimeService timeService , IStreamChatLowLevelClient lowLevelClient ,
42+ INetworkMonitor networkMonitor )
43+ {
44+ _client = lowLevelClient ?? throw new ArgumentNullException ( nameof ( lowLevelClient ) ) ;
45+ _timeService = timeService ?? throw new ArgumentNullException ( nameof ( timeService ) ) ;
46+ _networkMonitor = networkMonitor ?? throw new ArgumentNullException ( nameof ( networkMonitor ) ) ;
47+
48+ _networkMonitor . NetworkAvailabilityChanged += OnNetworkAvailabilityChanged ;
49+
50+ _client . Connected += OnConnected ;
51+ _client . Reconnecting += OnReconnecting ;
52+ _client . ConnectionStateChanged += OnConnectionStateChanged ;
53+ }
54+
55+ public void Dispose ( )
56+ {
57+ if ( _client != null )
58+ {
59+ _client . Connected -= OnConnected ;
60+ _client . Reconnecting -= OnReconnecting ;
61+ _client . ConnectionStateChanged -= OnConnectionStateChanged ;
62+ }
63+ }
64+
65+ public void SetReconnectStrategySettings ( ReconnectStrategy reconnectStrategy , float ? exponentialMinInterval ,
66+ float ? exponentialMaxInterval , float ? constantInterval )
67+ {
68+ ReconnectStrategy = reconnectStrategy ;
69+
70+ void ThrowIfLessOrEqualToZero ( float value , string name )
71+ {
72+ if ( value <= 0 )
73+ {
74+ throw new ArgumentException ( $ "{ name } needs to be greater than zero, given: " + value ) ;
75+ }
76+ }
77+
78+ if ( exponentialMinInterval . HasValue )
79+ {
80+ ThrowIfLessOrEqualToZero ( exponentialMinInterval . Value , nameof ( exponentialMinInterval ) ) ;
81+ ReconnectExponentialMinInterval = exponentialMinInterval . Value ;
82+ }
83+
84+ if ( exponentialMaxInterval . HasValue )
85+ {
86+ ThrowIfLessOrEqualToZero ( exponentialMaxInterval . Value , nameof ( exponentialMaxInterval ) ) ;
87+ ReconnectExponentialMaxInterval = exponentialMaxInterval . Value ;
88+ }
89+
90+ if ( constantInterval . HasValue )
91+ {
92+ ThrowIfLessOrEqualToZero ( constantInterval . Value , nameof ( constantInterval ) ) ;
93+ ReconnectConstantInterval = constantInterval . Value ;
94+ }
95+ }
96+
97+ public void Stop ( )
98+ {
99+ NextReconnectTime = float . MaxValue ;
100+ _isStopped = true ;
101+ }
102+
103+ //StreamTodo: connection info could be split to separate interface
104+ private readonly IStreamChatLowLevelClient _client ;
105+ private readonly ITimeService _timeService ;
106+ private readonly INetworkMonitor _networkMonitor ;
107+
108+ private int _reconnectAttempts ;
109+ private bool _isStopped ;
110+ private double ? _nextReconnectTime ;
111+
112+ private void TryScheduleNextReconnectTime ( )
113+ {
114+ if ( NextReconnectTime . HasValue && NextReconnectTime . Value > _timeService . Time )
115+ {
116+ return ;
117+ }
118+
119+ if ( _isStopped || ReconnectStrategy == ReconnectStrategy . Never )
120+ {
121+ return ;
122+ }
123+
124+ double ? GetNextReconnectTime ( )
125+ {
126+ if ( _reconnectAttempts <= ReconnectMaxInstantTrials )
127+ {
128+ return _timeService . Time ;
129+ }
130+
131+ switch ( ReconnectStrategy )
132+ {
133+ case ReconnectStrategy . Exponential :
134+
135+ var baseInterval = Math . Pow ( 2 , _reconnectAttempts ) ;
136+ var interval = Math . Min ( Math . Max ( ReconnectExponentialMinInterval , baseInterval ) ,
137+ ReconnectExponentialMaxInterval ) ;
138+ return _timeService . Time + interval ;
139+ case ReconnectStrategy . Constant :
140+ return _timeService . Time + ReconnectConstantInterval ;
141+ case ReconnectStrategy . Never :
142+ return null ;
143+ default :
144+ throw new ArgumentOutOfRangeException (
145+ $ "Unhandled { nameof ( ReconnectStrategy ) } : { ReconnectStrategy } ") ;
146+ }
147+ }
148+
149+ NextReconnectTime = GetNextReconnectTime ( ) ;
150+ }
151+
152+ private void OnConnectionStateChanged ( ConnectionState previous , ConnectionState current )
153+ {
154+ switch ( current )
155+ {
156+ case ConnectionState . Disconnected :
157+
158+ TryScheduleNextReconnectTime ( ) ;
159+
160+ break ;
161+ case ConnectionState . Connecting :
162+
163+ NextReconnectTime = default ;
164+
165+ break ;
166+ case ConnectionState . WaitToReconnect :
167+ break ;
168+ case ConnectionState . Connected :
169+ break ;
170+ case ConnectionState . Closing :
171+ break ;
172+ default :
173+ throw new ArgumentOutOfRangeException ( nameof ( current ) , current , null ) ;
174+ }
175+ }
176+
177+ private void OnNetworkAvailabilityChanged ( bool isNetworkAvailable )
178+ {
179+ if ( ! isNetworkAvailable )
180+ {
181+ return ;
182+ }
183+
184+ if ( _client . ConnectionState == ConnectionState . Connected ||
185+ _client . ConnectionState == ConnectionState . Connecting )
186+ {
187+ return ;
188+ }
189+
190+ if ( _isStopped )
191+ {
192+ return ;
193+ }
194+
195+ NextReconnectTime = _timeService . Time ;
196+ }
197+
198+ private void OnReconnecting ( )
199+ {
200+ _reconnectAttempts ++ ;
201+ }
202+
203+ private void OnConnected ( OwnUser localUser )
204+ {
205+ _reconnectAttempts = 0 ;
206+ NextReconnectTime = default ;
207+ }
208+ }
209+ }
0 commit comments