Browse Source

Backport pull request #13459 from jellyfin/release-10.10.z

Fixed Websocket not locking state correctly

Original-merge: 49bb5a6442ac8b0ddaff7958acedd43e1a72137c

Merged-by: Bond-009 <bond.009@outlook.com>

Backported-by: Bond_009 <bond.009@outlook.com>
JPVenson 3 months ago
parent
commit
db2167178a
1 changed files with 81 additions and 17 deletions
  1. 81 17
      Emby.Server.Implementations/Session/WebSocketController.cs

+ 81 - 17
Emby.Server.Implementations/Session/WebSocketController.cs

@@ -21,6 +21,7 @@ namespace Emby.Server.Implementations.Session
         private readonly SessionInfo _session;
 
         private readonly List<IWebSocketConnection> _sockets;
+        private readonly ReaderWriterLockSlim _socketsLock;
         private bool _disposed = false;
 
         public WebSocketController(
@@ -31,10 +32,26 @@ namespace Emby.Server.Implementations.Session
             _logger = logger;
             _session = session;
             _sessionManager = sessionManager;
-            _sockets = new List<IWebSocketConnection>();
+            _sockets = new();
+            _socketsLock = new();
         }
 
-        private bool HasOpenSockets => GetActiveSockets().Any();
+        private bool HasOpenSockets
+        {
+            get
+            {
+                ObjectDisposedException.ThrowIf(_disposed, this);
+                try
+                {
+                    _socketsLock.EnterReadLock();
+                    return _sockets.Any(i => i.State == WebSocketState.Open);
+                }
+                finally
+                {
+                    _socketsLock.ExitReadLock();
+                }
+            }
+        }
 
         /// <inheritdoc />
         public bool SupportsMediaControl => HasOpenSockets;
@@ -42,23 +59,38 @@ namespace Emby.Server.Implementations.Session
         /// <inheritdoc />
         public bool IsSessionActive => HasOpenSockets;
 
-        private IEnumerable<IWebSocketConnection> GetActiveSockets()
-            => _sockets.Where(i => i.State == WebSocketState.Open);
-
         public void AddWebSocket(IWebSocketConnection connection)
         {
             _logger.LogDebug("Adding websocket to session {Session}", _session.Id);
-            _sockets.Add(connection);
-
-            connection.Closed += OnConnectionClosed;
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            try
+            {
+                _socketsLock.EnterWriteLock();
+                _sockets.Add(connection);
+                connection.Closed += OnConnectionClosed;
+            }
+            finally
+            {
+                _socketsLock.ExitWriteLock();
+            }
         }
 
         private async void OnConnectionClosed(object? sender, EventArgs e)
         {
             var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
             _logger.LogDebug("Removing websocket from session {Session}", _session.Id);
-            _sockets.Remove(connection);
-            connection.Closed -= OnConnectionClosed;
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            try
+            {
+                _socketsLock.EnterWriteLock();
+                _sockets.Remove(connection);
+                connection.Closed -= OnConnectionClosed;
+            }
+            finally
+            {
+                _socketsLock.ExitWriteLock();
+            }
+
             await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false);
         }
 
@@ -69,7 +101,17 @@ namespace Emby.Server.Implementations.Session
             T data,
             CancellationToken cancellationToken)
         {
-            var socket = GetActiveSockets().MaxBy(i => i.LastActivityDate);
+            ObjectDisposedException.ThrowIf(_disposed, this);
+            IWebSocketConnection? socket;
+            try
+            {
+                _socketsLock.EnterReadLock();
+                socket = _sockets.Where(i => i.State == WebSocketState.Open).MaxBy(i => i.LastActivityDate);
+            }
+            finally
+            {
+                _socketsLock.ExitReadLock();
+            }
 
             if (socket is null)
             {
@@ -94,12 +136,23 @@ namespace Emby.Server.Implementations.Session
                 return;
             }
 
-            foreach (var socket in _sockets)
+            try
+            {
+                _socketsLock.EnterWriteLock();
+                foreach (var socket in _sockets)
+                {
+                    socket.Closed -= OnConnectionClosed;
+                    socket.Dispose();
+                }
+
+                _sockets.Clear();
+            }
+            finally
             {
-                socket.Closed -= OnConnectionClosed;
-                socket.Dispose();
+                _socketsLock.ExitWriteLock();
             }
 
+            _socketsLock.Dispose();
             _disposed = true;
         }
 
@@ -110,12 +163,23 @@ namespace Emby.Server.Implementations.Session
                 return;
             }
 
-            foreach (var socket in _sockets)
+            try
+            {
+                _socketsLock.EnterWriteLock();
+                foreach (var socket in _sockets)
+                {
+                    socket.Closed -= OnConnectionClosed;
+                    await socket.DisposeAsync().ConfigureAwait(false);
+                }
+
+                _sockets.Clear();
+            }
+            finally
             {
-                socket.Closed -= OnConnectionClosed;
-                await socket.DisposeAsync().ConfigureAwait(false);
+                _socketsLock.ExitWriteLock();
             }
 
+            _socketsLock.Dispose();
             _disposed = true;
         }
     }