Skip to content

Commit 6a51a40

Browse files
authored
Merge pull request #179 from GetStream/feature/uni-133-fix-leaving-a-call-can-take-long-time
Feature/uni 133 fix leaving a call can take long time
2 parents b16d41e + b81898b commit 6a51a40

File tree

13 files changed

+121
-34
lines changed

13 files changed

+121
-34
lines changed

Packages/StreamVideo/Runtime/Core/ConnectionState.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public enum ConnectionState
1313
/// If was Connected before and allowed by <see cref="IStreamVideoLowLevelClient.ReconnectStrategy"/> it will switch to WaitToReconnect and attempt to connect again after a timeout
1414
/// </summary>
1515
Disconnected,
16+
17+
/// <summary>
18+
/// Disconnecting current user in progress. Client can still reconnect after this.
19+
/// </summary>
20+
Disconnecting,
1621

1722
/// <summary>
1823
/// Currently connecting to server, waiting for the connection handshake to complete
@@ -30,7 +35,7 @@ public enum ConnectionState
3035
Connected,
3136

3237
/// <summary>
33-
/// Connection is permanently closing. There will be no reconnects made. StreamChatClient is probably being disposed
38+
/// Connection is permanently closing. There will be no reconnects made. StreamVideoClient is probably being disposed
3439
/// </summary>
3540
Closing,
3641
}
@@ -47,6 +52,7 @@ public static bool IsValidToConnect(this ConnectionState state)
4752
case ConnectionState.Connecting:
4853
case ConnectionState.Connected:
4954
case ConnectionState.Closing:
55+
case ConnectionState.Disconnecting:
5056
return false;
5157
case ConnectionState.Disconnected:
5258
case ConnectionState.WaitToReconnect:

Packages/StreamVideo/Runtime/Core/LowLevelClient/CallingState.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public enum CallingState
5151
/// <summary>
5252
/// The call is in offline mode.
5353
/// </summary>
54-
Offline
54+
Offline,
55+
56+
/// <summary>
57+
/// Leaving operation in progress.
58+
/// </summary>
59+
Leaving,
5560
}
5661
}

Packages/StreamVideo/Runtime/Core/LowLevelClient/ReconnectScheduler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ private void OnConnectionStateChanged(ConnectionState previous, ConnectionState
173173
break;
174174
case ConnectionState.Closing:
175175
break;
176+
case ConnectionState.Disconnecting:
177+
break;
176178
default:
177179
throw new ArgumentOutOfRangeException(nameof(current), current, null);
178180
}

Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,10 @@ public async Task StartAsync(StreamCall call)
369369

370370
//StreamTodo: validate when this state should set
371371
CallState = CallingState.Joined;
372+
373+
#if STREAM_DEBUG_ENABLED
372374
_videoAudioSyncBenchmark?.Init(call);
375+
#endif
373376
}
374377
catch
375378
{
@@ -380,6 +383,14 @@ public async Task StartAsync(StreamCall call)
380383

381384
public async Task StopAsync(string reason = "")
382385
{
386+
if (CallState == CallingState.Leaving || CallState == CallingState.Offline)
387+
{
388+
return;
389+
}
390+
391+
CallState = CallingState.Leaving;
392+
393+
383394
if (UseNativeAudioBindings)
384395
{
385396
#if STREAM_NATIVE_AUDIO
@@ -393,26 +404,20 @@ public async Task StopAsync(string reason = "")
393404
{
394405
// Trace leave call before leaving the call. Otherwise, stats are not send because SFU WS disconnects
395406
_sfuTracer?.Trace(PeerConnectionTraceKey.LeaveCall, new { SessionId = SessionId, Reason = reason });
407+
396408
if (_statsSender != null) // This was null in tests
397409
{
398-
await _statsSender.SendFinalStatsAsync();
410+
using (new TimeLogScope("Sending final stats on leave", _logs.Info))
411+
{
412+
await _statsSender.SendFinalStatsAsync();
413+
}
399414
}
400415
}
401416
catch (Exception e)
402417
{
403418
_logs.Error($"Failed to send final stats on leave: {e.Message}");
404419
}
405420

406-
_sfuWebSocket.SendLeaveCallRequest(reason);
407-
408-
for (int i = 0; i < 60; i++)
409-
{
410-
if (_sfuWebSocket.SendQueueCount > 0)
411-
{
412-
await Task.Delay(5);
413-
}
414-
}
415-
416421
#if STREAM_DEBUG_ENABLED
417422
if (_sfuWebSocket.SendQueueCount > 0)
418423
{
@@ -424,9 +429,17 @@ public async Task StopAsync(string reason = "")
424429

425430
ClearSession();
426431
//StreamTodo: check with js definition of "offline"
432+
433+
using (new TimeLogScope("Sending leave call request & disconnect", _logs.Info))
434+
{
435+
await _sfuWebSocket.DisconnectAsync(WebSocketCloseStatus.NormalClosure, reason);
436+
}
437+
427438
CallState = CallingState.Offline;
428-
await _sfuWebSocket.DisconnectAsync(WebSocketCloseStatus.NormalClosure, "Video session stopped");
439+
440+
#if STREAM_DEBUG_ENABLED
429441
_videoAudioSyncBenchmark?.Finish();
442+
#endif
430443
}
431444

432445
//StreamTodo: call by call.reconnectOrSwitchSfu()
@@ -666,7 +679,8 @@ private IEnumerable<TrackSubscriptionDetails> GetDesiredTracksDetails()
666679
var userId = GetUserId(participant);
667680
if (string.IsNullOrEmpty(userId))
668681
{
669-
_logs.Error($"Cannot subscribe to {trackType} - participant UserId is null or empty. SessionID: {participant.SessionId}");
682+
_logs.Error(
683+
$"Cannot subscribe to {trackType} - participant UserId is null or empty. SessionID: {participant.SessionId}");
670684
continue;
671685
}
672686

Packages/StreamVideo/Runtime/Core/LowLevelClient/WebSockets/BasePersistentWebSocket.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ private void TryToReconnect()
7373
{
7474
return;
7575
}
76-
77-
#if STREAM_DEBUG_ENABLED
76+
77+
#if STREAM_DEBUG_ENABLED
7878
Logs.Info($"{GetType()} TryToReconnect");
79-
#endif
79+
#endif
8080

8181
ConnectAsync().LogIfFailed();
8282
}
@@ -96,17 +96,24 @@ public Task ConnectAsync(CancellationToken cancellationToken = default)
9696
}
9797
}
9898

99-
public Task DisconnectAsync(WebSocketCloseStatus closeStatus, string closeMessage)
99+
public async Task DisconnectAsync(WebSocketCloseStatus closeStatus, string closeMessage)
100100
{
101-
//StreamTodo: ignore if already disconnected or disconnecting
102-
OnDisconnecting();
101+
if (ConnectionState == ConnectionState.Disconnected || ConnectionState == ConnectionState.Closing ||
102+
ConnectionState == ConnectionState.Disconnecting)
103+
{
104+
return;
105+
}
106+
107+
ConnectionState = ConnectionState.Disconnecting;
108+
109+
await OnDisconnectingAsync(closeMessage);
103110

104111
if (WebsocketClient == null)
105112
{
106-
return Task.CompletedTask;
113+
return;
107114
}
108115

109-
return WebsocketClient.DisconnectAsync(closeStatus, closeMessage);
116+
await WebsocketClient.DisconnectAsync(closeStatus, closeMessage);
110117
}
111118

112119
//StreamTodo: either move to coordinator or make generic and pass TMessageType and abstract deserializer
@@ -179,7 +186,7 @@ public void Dispose()
179186
protected IRequestUriFactory UriFactory { get; }
180187
protected IAuthProvider AuthProvider { get; }
181188
protected IReadOnlyDictionary<string, Action<string>> EventHandlers => _eventKeyToHandler;
182-
189+
183190
protected abstract int HealthCheckMaxWaitingTime { get; }
184191
protected abstract int HealthCheckSendInterval { get; }
185192

@@ -198,7 +205,7 @@ protected BasePersistentWebSocket(IWebsocketClient websocketClient, IReconnectSc
198205

199206
WebsocketClient.ConnectionFailed += OnConnectionFailed;
200207
WebsocketClient.Disconnected += OnDisconnected;
201-
208+
202209
_reconnectScheduler.ReconnectionScheduled += OnReconnectionScheduled;
203210
}
204211

@@ -208,16 +215,16 @@ protected BasePersistentWebSocket(IWebsocketClient websocketClient, IReconnectSc
208215

209216
protected void OnHealthCheckReceived()
210217
{
211-
212218
#if STREAM_DEBUG_ENABLED
213219
var timeSinceLast = Mathf.Round(TimeService.Time - _lastHealthCheckReceivedTime);
214220
//Logs.Info($"{LogsPrefix} Health check RECEIVED. Time since last: {timeSinceLast} seconds");
215221
#endif
216222
_lastHealthCheckReceivedTime = TimeService.Time;
217223
}
218224

219-
protected virtual void OnDisconnecting()
225+
protected virtual Task OnDisconnectingAsync(string closeMessage)
220226
{
227+
return Task.CompletedTask;
221228
}
222229

223230
protected virtual void OnDisposing()
@@ -226,8 +233,10 @@ protected virtual void OnDisposing()
226233

227234
private readonly object _websocketConnectionFailedFlagLock = new object();
228235
private readonly IReconnectScheduler _reconnectScheduler;
236+
229237
private readonly Dictionary<string, Action<string>> _eventKeyToHandler =
230238
new Dictionary<string, Action<string>>();
239+
231240
private readonly StringBuilder _logSb = new StringBuilder();
232241

233242
private ConnectionState _connectionState;
@@ -275,7 +284,8 @@ private void MonitorHealthCheck()
275284
var timeSinceLastHealthCheck = TimeService.Time - _lastHealthCheckReceivedTime;
276285
if (timeSinceLastHealthCheck > HealthCheckMaxWaitingTime)
277286
{
278-
Logs.Warning($"{LogsPrefix} Health check was not received since: {timeSinceLastHealthCheck}, reset connection");
287+
Logs.Warning(
288+
$"{LogsPrefix} Health check was not received since: {timeSinceLastHealthCheck}, reset connection");
279289
WebsocketClient
280290
.DisconnectAsync(WebSocketCloseStatus.InternalServerError,
281291
$"{LogsPrefix} Health check was not received since: {timeSinceLastHealthCheck}")

Packages/StreamVideo/Runtime/Core/LowLevelClient/WebSockets/CoordinatorWebSocket.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ protected override async Task ExecuteConnectAsync(CancellationToken cancellation
103103
await _connectUserTaskSource.Task;
104104
}
105105

106-
protected override void OnDisconnecting()
106+
protected override async Task OnDisconnectingAsync(string closeMessage)
107107
{
108108
_connectUserTaskSource?.TrySetCanceled();
109109

110-
base.OnDisconnecting();
110+
await base.OnDisconnectingAsync(closeMessage);
111111
}
112112

113113
protected override void OnDisposing()

Packages/StreamVideo/Runtime/Core/LowLevelClient/WebSockets/SfuWebSocket.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using StreamVideo.Libs.Logs;
1212
using StreamVideo.Libs.Serialization;
1313
using StreamVideo.Libs.Time;
14+
using StreamVideo.Libs.Utils;
1415
using StreamVideo.Libs.Websockets;
1516
using Error = StreamVideo.v1.Sfu.Events.Error;
1617
using ICETrickle = StreamVideo.v1.Sfu.Models.ICETrickle;
@@ -71,7 +72,7 @@ public void SendLeaveCallRequest(string reason = "")
7172
{
7273
if (string.IsNullOrEmpty(_sessionId))
7374
{
74-
throw new ArgumentException($"{nameof(_sessionId)} is null or empty.");
75+
return;
7576
}
7677

7778
if (reason == null)
@@ -103,6 +104,11 @@ public void SendLeaveCallRequest(string reason = "")
103104

104105
protected override void SendHealthCheck()
105106
{
107+
if (ConnectionState != ConnectionState.Connected)
108+
{
109+
return;
110+
}
111+
106112
var sfuRequest = new SfuRequest
107113
{
108114
HealthCheckRequest = new HealthCheckRequest(),
@@ -114,6 +120,11 @@ protected override void SendHealthCheck()
114120

115121
protected override async Task ExecuteConnectAsync(CancellationToken cancellationToken = default)
116122
{
123+
if (ConnectionState == ConnectionState.Disconnecting || ConnectionState == ConnectionState.Closing)
124+
{
125+
Logs.Error($"Tried to connect to the {nameof(SfuWebSocket)} while disconnecting or closing. Aborting.");
126+
return;
127+
}
117128
//StreamTodo: validate session data
118129

119130
if (string.IsNullOrEmpty(_sfuToken))
@@ -279,11 +290,26 @@ protected override void ProcessMessages()
279290
}
280291
}
281292

282-
protected override void OnDisconnecting()
293+
protected override async Task OnDisconnectingAsync(string closeMessage)
283294
{
284295
_connectUserTaskSource?.TrySetCanceled();
296+
297+
WebsocketClient.ClearSendQueue();
298+
299+
using (new TimeLogScope("Sending leave call request", Logs.Info))
300+
{
301+
SendLeaveCallRequest(closeMessage);
302+
303+
for (int i = 0; i < 60; i++)
304+
{
305+
if (SendQueueCount > 0)
306+
{
307+
await Task.Delay(5);
308+
}
309+
}
310+
}
285311

286-
base.OnDisconnecting();
312+
await base.OnDisconnectingAsync(closeMessage);
287313
}
288314

289315
protected override void OnDisposing()

Packages/StreamVideo/Runtime/Core/StatefulModels/IStreamCall.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ public interface IStreamCall : IStreamStatefulModel, IHasCustomData
126126
/// </summary>
127127
StreamCallType Type { get; }
128128

129+
/// <summary>
130+
/// Does the user of this client own the call
131+
/// </summary>
129132
bool IsLocalUserOwner { get; }
130133

131134
/// <summary>

Packages/StreamVideo/Runtime/Core/StreamVideoClient.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,12 @@ private async Task LeaveCallAsync(IStreamCall call)
451451
{
452452
try
453453
{
454+
var callState = InternalLowLevelClient.RtcSession.CallState;
455+
if (callState == CallingState.Leaving || callState == CallingState.Offline)
456+
{
457+
_logs.Warning($"{nameof(LeaveCallAsync)}: Call is already leaving or offline, skipping leave operation.");
458+
return;
459+
}
454460
await InternalLowLevelClient.RtcSession.StopAsync("User is leaving the call");
455461
}
456462
catch (Exception e)

Packages/StreamVideo/Runtime/Libs/Utils/TimeLogScope.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public void Dispose()
2929
_sb.Append(_name);
3030
_sb.Append(" - Executed in: ");
3131
_sb.Append(elapsed);
32+
_sb.Append(" seconds");
3233

3334
_logger(_sb.ToString());
3435
}

0 commit comments

Comments
 (0)