mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	Improve locking logic in SyncPlay manager
This commit is contained in:
		
							parent
							
								
									a3ca36cb54
								
							
						
					
					
						commit
						426e258f1f
					
				@ -54,10 +54,15 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
            new Dictionary<Guid, IGroupController>();
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Lock used for accesing any group.
 | 
			
		||||
        /// Lock used for accesing the list of groups.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private readonly object _groupsLock = new object();
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Lock used for accesing the session-to-group map.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private readonly object _mapsLock = new object();
 | 
			
		||||
 | 
			
		||||
        private bool _disposed = false;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@ -97,7 +102,12 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Locking required to access list of groups.
 | 
			
		||||
            lock (_groupsLock)
 | 
			
		||||
            {
 | 
			
		||||
                // Locking required as session-to-group map will be edited.
 | 
			
		||||
                // Locking the group is not required as it is not visible yet.
 | 
			
		||||
                lock (_mapsLock)
 | 
			
		||||
                {
 | 
			
		||||
                    if (IsSessionInGroup(session))
 | 
			
		||||
                    {
 | 
			
		||||
@ -111,6 +121,7 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                    group.CreateGroup(session, request, cancellationToken);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken)
 | 
			
		||||
@ -123,6 +134,7 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
 | 
			
		||||
            var user = _userManager.GetUserById(session.UserId);
 | 
			
		||||
 | 
			
		||||
            // Locking required to access list of groups.
 | 
			
		||||
            lock (_groupsLock)
 | 
			
		||||
            {
 | 
			
		||||
                _groups.TryGetValue(groupId, out IGroupController group);
 | 
			
		||||
@ -136,6 +148,12 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Locking required as session-to-group map will be edited.
 | 
			
		||||
                lock (_mapsLock)
 | 
			
		||||
                {
 | 
			
		||||
                    // Group lock required to let other requests end first.
 | 
			
		||||
                    lock (group)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!group.HasAccessToPlayQueue(user))
 | 
			
		||||
                        {
 | 
			
		||||
                            _logger.LogWarning("Session {SessionId} tried to join group {GroupId} but does not have access to some content of the playing queue.", session.Id, group.GroupId.ToString());
 | 
			
		||||
@ -147,7 +165,7 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
 | 
			
		||||
                        if (IsSessionInGroup(session))
 | 
			
		||||
                        {
 | 
			
		||||
                    if (GetSessionGroup(session).Equals(groupId))
 | 
			
		||||
                            if (FindJoinedGroupId(session).Equals(groupId))
 | 
			
		||||
                            {
 | 
			
		||||
                                group.SessionRestore(session, request, cancellationToken);
 | 
			
		||||
                                return;
 | 
			
		||||
@ -160,6 +178,8 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                        group.SessionJoin(session, request, cancellationToken);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken)
 | 
			
		||||
@ -170,11 +190,13 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // TODO: determine what happens to users that are in a group and get their permissions revoked.
 | 
			
		||||
            // Locking required to access list of groups.
 | 
			
		||||
            lock (_groupsLock)
 | 
			
		||||
            {
 | 
			
		||||
                _sessionToGroupMap.TryGetValue(session.Id, out var group);
 | 
			
		||||
 | 
			
		||||
                // Locking required as session-to-group map will be edited.
 | 
			
		||||
                lock (_mapsLock)
 | 
			
		||||
                {
 | 
			
		||||
                    var group = FindJoinedGroup(session);
 | 
			
		||||
                    if (group == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id);
 | 
			
		||||
@ -184,6 +206,9 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Group lock required to let other requests end first.
 | 
			
		||||
                    lock (group)
 | 
			
		||||
                    {
 | 
			
		||||
                        RemoveSessionFromGroup(session, group);
 | 
			
		||||
                        group.SessionLeave(session, cancellationToken);
 | 
			
		||||
 | 
			
		||||
@ -194,6 +219,8 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public List<GroupInfoDto> ListGroups(SessionInfo session)
 | 
			
		||||
@ -205,16 +232,26 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var user = _userManager.GetUserById(session.UserId);
 | 
			
		||||
            List<GroupInfoDto> list = new List<GroupInfoDto>();
 | 
			
		||||
 | 
			
		||||
            // Locking required to access list of groups.
 | 
			
		||||
            lock (_groupsLock)
 | 
			
		||||
            {
 | 
			
		||||
                return _groups
 | 
			
		||||
                    .Values
 | 
			
		||||
                    .Where(group => group.HasAccessToPlayQueue(user))
 | 
			
		||||
                    .Select(group => group.GetInfo())
 | 
			
		||||
                    .ToList();
 | 
			
		||||
                foreach (var group in _groups.Values)
 | 
			
		||||
                {
 | 
			
		||||
                    // Locking required as group is not thread-safe.
 | 
			
		||||
                    lock (group)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (group.HasAccessToPlayQueue(user))
 | 
			
		||||
                        {
 | 
			
		||||
                            list.Add(group.GetInfo());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return list;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken)
 | 
			
		||||
@ -225,10 +262,7 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            lock (_groupsLock)
 | 
			
		||||
            {
 | 
			
		||||
                _sessionToGroupMap.TryGetValue(session.Id, out var group);
 | 
			
		||||
 | 
			
		||||
            var group = FindJoinedGroup(session);
 | 
			
		||||
            if (group == null)
 | 
			
		||||
            {
 | 
			
		||||
                _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id);
 | 
			
		||||
@ -238,6 +272,9 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Group lock required as GroupController is not thread-safe.
 | 
			
		||||
            lock (group)
 | 
			
		||||
            {
 | 
			
		||||
                group.HandleRequest(session, request, cancellationToken);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -260,52 +297,57 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
        private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            var session = e.SessionInfo;
 | 
			
		||||
            lock (_groupsLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (!IsSessionInGroup(session))
 | 
			
		||||
 | 
			
		||||
            Guid groupId = FindJoinedGroupId(session);
 | 
			
		||||
            if (groupId.Equals(Guid.Empty))
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                var groupId = GetSessionGroup(session);
 | 
			
		||||
            var request = new JoinGroupRequest(groupId);
 | 
			
		||||
            JoinGroup(session, groupId, request, CancellationToken.None);
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if a given session has joined a group.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>
 | 
			
		||||
        /// Not thread-safe, call only under groups-lock.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <param name="session">The session.</param>
 | 
			
		||||
        /// <returns><c>true</c> if the session has joined a group, <c>false</c> otherwise.</returns>
 | 
			
		||||
        private bool IsSessionInGroup(SessionInfo session)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_mapsLock)
 | 
			
		||||
            {
 | 
			
		||||
                return _sessionToGroupMap.ContainsKey(session.Id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the group joined by the given session, if any.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>
 | 
			
		||||
        /// Not thread-safe, call only under groups-lock.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <param name="session">The session.</param>
 | 
			
		||||
        /// <returns>The group identifier if the session has joined a group, an empty identifier otherwise.</returns>
 | 
			
		||||
        private Guid GetSessionGroup(SessionInfo session)
 | 
			
		||||
        /// <returns>The group.</returns>
 | 
			
		||||
        private IGroupController FindJoinedGroup(SessionInfo session)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_mapsLock)
 | 
			
		||||
            {
 | 
			
		||||
                _sessionToGroupMap.TryGetValue(session.Id, out var group);
 | 
			
		||||
            return group?.GroupId ?? Guid.Empty;
 | 
			
		||||
                return group;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the group identifier joined by the given session, if any.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="session">The session.</param>
 | 
			
		||||
        /// <returns>The group identifier if the session has joined a group, an empty identifier otherwise.</returns>
 | 
			
		||||
        private Guid FindJoinedGroupId(SessionInfo session)
 | 
			
		||||
        {
 | 
			
		||||
            return FindJoinedGroup(session)?.GroupId ?? Guid.Empty;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Maps a session to a group.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>
 | 
			
		||||
        /// Not thread-safe, call only under groups-lock.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <param name="session">The session.</param>
 | 
			
		||||
        /// <param name="group">The group.</param>
 | 
			
		||||
        /// <exception cref="InvalidOperationException">Thrown when the user is in another group already.</exception>
 | 
			
		||||
@ -316,6 +358,8 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                throw new InvalidOperationException("Session is null!");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            lock (_mapsLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (IsSessionInGroup(session))
 | 
			
		||||
                {
 | 
			
		||||
                    throw new InvalidOperationException("Session in other group already!");
 | 
			
		||||
@ -323,13 +367,11 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
 | 
			
		||||
                _sessionToGroupMap[session.Id] = group ?? throw new InvalidOperationException("Group is null!");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Unmaps a session from a group.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>
 | 
			
		||||
        /// Not thread-safe, call only under groups-lock.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        /// <param name="session">The session.</param>
 | 
			
		||||
        /// <param name="group">The group.</param>
 | 
			
		||||
        /// <exception cref="InvalidOperationException">Thrown when the user is not found in the specified group.</exception>
 | 
			
		||||
@ -345,6 +387,8 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                throw new InvalidOperationException("Group is null!");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            lock (_mapsLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (!IsSessionInGroup(session))
 | 
			
		||||
                {
 | 
			
		||||
                    throw new InvalidOperationException("Session not in any group!");
 | 
			
		||||
@ -356,6 +400,7 @@ namespace Emby.Server.Implementations.SyncPlay
 | 
			
		||||
                    throw new InvalidOperationException("Session was in wrong group!");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Checks if a given session is allowed to make a given request.
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user