|  | @@ -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;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 |