private void SubscribeToGroupManagementChannel()
        {
            _bus.Subscribe(_channels.GroupManagement, async(c, data) =>
            {
                try
                {
                    var groupMessage = _protocol.ReadGroupCommand(data);

                    var connection = _connections[groupMessage.ConnectionId];
                    if (connection == null)
                    {
                        // user not on this server
                        return;
                    }

                    if (groupMessage.Action == GroupAction.Remove)
                    {
                        await RemoveGroupAsyncCore(connection, groupMessage.GroupName);
                    }

                    if (groupMessage.Action == GroupAction.Add)
                    {
                        await AddGroupAsyncCore(connection, groupMessage.GroupName);
                    }

                    // Send an ack to the server that sent the original command.
                    await PublishAsync(_channels.Ack(groupMessage.ServerName), _protocol.WriteAck(groupMessage.Id));
                }
                catch (Exception ex)
                {
                    RedisLog.InternalMessageFailed(_logger, ex);
                }
            });
        }
        private Task SubscribeToGroup(string groupChannel, GroupData group)
        {
            RedisLog.Subscribing(_logger, groupChannel);
            return(_bus.SubscribeAsync(groupChannel, async(c, data) =>
            {
                try
                {
                    var invocation = _protocol.ReadInvocation(data);

                    var tasks = new List <Task>();
                    foreach (var groupConnection in group.Connections)
                    {
                        if (invocation.ExcludedIds?.Contains(groupConnection.ConnectionId) == true)
                        {
                            continue;
                        }

                        tasks.Add(groupConnection.WriteAsync(invocation.Message).AsTask());
                    }

                    await Task.WhenAll(tasks);
                }
                catch (Exception ex)
                {
                    RedisLog.FailedWritingMessage(_logger, ex);
                }
            }));
        }
        private void SubscribeToAll()
        {
            RedisLog.Subscribing(_logger, _channels.All);
            _bus.Subscribe(_channels.All, async(c, data) =>
            {
                try
                {
                    RedisLog.ReceivedFromChannel(_logger, _channels.All);

                    var invocation = _protocol.ReadInvocation(data);

                    var tasks = new List <Task>(_connections.Count);

                    foreach (var connection in _connections)
                    {
                        if (invocation.ExcludedIds == null || !invocation.ExcludedIds.Contains(connection.ConnectionId))
                        {
                            tasks.Add(connection.WriteAsync(invocation.Message).AsTask());
                        }
                    }

                    await Task.WhenAll(tasks);
                }
                catch (Exception ex)
                {
                    RedisLog.FailedWritingMessage(_logger, ex);
                }
            });
        }
        public override Task OnDisconnectedAsync(HubConnectionContext connection)
        {
            _connections.Remove(connection);

            var tasks = new List <Task>();

            var connectionChannel = _channels.Connection(connection.ConnectionId);

            RedisLog.Unsubscribe(_logger, connectionChannel);
            tasks.Add(_bus.UnsubscribeAsync(connectionChannel));

            var feature    = connection.Features.Get <IRedisFeature>();
            var groupNames = feature.Groups;

            if (groupNames != null)
            {
                // Copy the groups to an array here because they get removed from this collection
                // in RemoveFromGroupAsync
                foreach (var group in groupNames.ToArray())
                {
                    // Use RemoveGroupAsyncCore because the connection is local and we don't want to
                    // accidentally go to other servers with our remove request.
                    tasks.Add(RemoveGroupAsyncCore(connection, group));
                }
            }

            if (!string.IsNullOrEmpty(connection.UserIdentifier))
            {
                tasks.Add(RemoveUserAsync(connection));
            }

            return(Task.WhenAll(tasks));
        }
        public override Task OnDisconnectedAsync(HubConnectionContext connection)
        {
            _connections.Remove(connection);

            var tasks = new List <Task>();

            var feature = connection.Features.Get <IRedisFeature>();

            var redisSubscriptions = feature.Subscriptions;

            if (redisSubscriptions != null)
            {
                foreach (var subscription in redisSubscriptions)
                {
                    RedisLog.Unsubscribe(_logger, subscription);
                    tasks.Add(_bus.UnsubscribeAsync(subscription));
                }
            }

            var groupNames = feature.Groups;

            if (groupNames != null)
            {
                // Copy the groups to an array here because they get removed from this collection
                // in RemoveGroupAsync
                foreach (var group in groupNames.ToArray())
                {
                    // Use RemoveGroupAsyncCore because the connection is local and we don't want to
                    // accidentally go to other servers with our remove request.
                    tasks.Add(RemoveGroupAsyncCore(connection, group));
                }
            }

            return(Task.WhenAll(tasks));
        }
        private Task SubscribeToUser(HubConnectionContext connection)
        {
            var userChannel = _channels.User(connection.UserIdentifier);

            return(_users.AddSubscriptionAsync(userChannel, connection, async(channelName, subscriptions) =>
            {
                await _bus.SubscribeAsync(channelName, async(c, data) =>
                {
                    try
                    {
                        var invocation = _protocol.ReadInvocation((byte[])data);

                        var tasks = new List <Task>();
                        foreach (var userConnection in subscriptions)
                        {
                            tasks.Add(userConnection.WriteAsync(invocation.Message).AsTask());
                        }

                        await Task.WhenAll(tasks);
                    }
                    catch (Exception ex)
                    {
                        RedisLog.FailedWritingMessage(_logger, ex);
                    }
                });
            }));
        }
        private async Task PublishAsync(string channel, byte[] payload)
        {
            await EnsureRedisServerConnection();

            RedisLog.PublishToChannel(_logger, channel);
            await _bus.PublishAsync(channel, payload);
        }
        private async Task EnsureRedisServerConnection()
        {
            if (_redisServerConnection == null)
            {
                await _connectionLock.WaitAsync();

                try
                {
                    if (_redisServerConnection == null)
                    {
                        var writer = new LoggerTextWriter(_logger);
                        _redisServerConnection = await _options.ConnectAsync(writer);

                        _bus = _redisServerConnection.GetSubscriber();

                        _redisServerConnection.ConnectionRestored += (_, e) =>
                        {
                            // We use the subscription connection type
                            // Ignore messages from the interactive connection (avoids duplicates)
                            if (e.ConnectionType == ConnectionType.Interactive)
                            {
                                return;
                            }

                            RedisLog.ConnectionRestored(_logger);
                        };

                        _redisServerConnection.ConnectionFailed += (_, e) =>
                        {
                            // We use the subscription connection type
                            // Ignore messages from the interactive connection (avoids duplicates)
                            if (e.ConnectionType == ConnectionType.Interactive)
                            {
                                return;
                            }

                            RedisLog.ConnectionFailed(_logger, e.Exception);
                        };

                        if (_redisServerConnection.IsConnected)
                        {
                            RedisLog.Connected(_logger);
                        }
                        else
                        {
                            RedisLog.NotConnected(_logger);
                        }

                        SubscribeToAll();
                        SubscribeToGroupManagementChannel();
                        SubscribeToAckChannel();
                    }
                }
                finally
                {
                    _connectionLock.Release();
                }
            }
        }
        private Task RemoveUserAsync(HubConnectionContext connection)
        {
            var userChannel = _channels.User(connection.UserIdentifier);

            return(_users.RemoveSubscriptionAsync(userChannel, connection, async channelName =>
            {
                RedisLog.Unsubscribe(_logger, channelName);
                await _bus.UnsubscribeAsync(channelName);
            }));
        }
        private Task SubscribeToConnection(HubConnectionContext connection)
        {
            var connectionChannel = _channels.Connection(connection.ConnectionId);

            RedisLog.Subscribing(_logger, connectionChannel);
            return(_bus.SubscribeAsync(connectionChannel, async(c, data) =>
            {
                var invocation = _protocol.ReadInvocation((byte[])data);
                await connection.WriteAsync(invocation.Message);
            }));
        }
        public RedisHubLifetimeManager(ILogger <RedisHubLifetimeManager <THub> > logger,
                                       IOptions <RedisOptions> options,
                                       IHubProtocolResolver hubProtocolResolver)
        {
            _logger     = logger;
            _options    = options.Value;
            _ackHandler = new AckHandler();
            _channels   = new RedisChannels(typeof(THub).FullName);
            _protocol   = new RedisProtocol(hubProtocolResolver.AllProtocols);

            var writer = new LoggerTextWriter(logger);

            RedisLog.ConnectingToEndpoints(_logger, options.Value.Options.EndPoints, _serverName);
            _redisServerConnection = _options.Connect(writer);

            _redisServerConnection.ConnectionRestored += (_, e) =>
            {
                // We use the subscription connection type
                // Ignore messages from the interactive connection (avoids duplicates)
                if (e.ConnectionType == ConnectionType.Interactive)
                {
                    return;
                }

                RedisLog.ConnectionRestored(_logger);
            };

            _redisServerConnection.ConnectionFailed += (_, e) =>
            {
                // We use the subscription connection type
                // Ignore messages from the interactive connection (avoids duplicates)
                if (e.ConnectionType == ConnectionType.Interactive)
                {
                    return;
                }

                RedisLog.ConnectionFailed(_logger, e.Exception);
            };

            if (_redisServerConnection.IsConnected)
            {
                RedisLog.Connected(_logger);
            }
            else
            {
                RedisLog.NotConnected(_logger);
            }
            _bus = _redisServerConnection.GetSubscriber();

            SubscribeToAll();
            SubscribeToGroupManagementChannel();
            SubscribeToAckChannel();
        }
        public RedisHubLifetimeManager(ILogger <RedisHubLifetimeManager <THub> > logger,
                                       IOptions <RedisOptions> options,
                                       IHubProtocolResolver hubProtocolResolver)
        {
            _logger     = logger;
            _options    = options.Value;
            _ackHandler = new AckHandler();
            _channels   = new RedisChannels(typeof(THub).FullName);
            _protocol   = new RedisProtocol(hubProtocolResolver.AllProtocols);

            RedisLog.ConnectingToEndpoints(_logger, options.Value.Options.EndPoints, _serverName);
        }
        /// <summary>
        /// This takes <see cref="HubConnectionContext"/> because we want to remove the connection from the
        /// _connections list in OnDisconnectedAsync and still be able to remove groups with this method.
        /// </summary>
        private async Task RemoveGroupAsyncCore(HubConnectionContext connection, string groupName)
        {
            var groupChannel = _channels.Group(groupName);

            await _groups.RemoveSubscriptionAsync(groupChannel, connection, async channelName =>
            {
                RedisLog.Unsubscribe(_logger, channelName);
                await _bus.UnsubscribeAsync(channelName);
            });

            var feature    = connection.Features.Get <IRedisFeature>();
            var groupNames = feature.Groups;

            if (groupNames != null)
            {
                lock (groupNames)
                {
                    groupNames.Remove(groupName);
                }
            }
        }
        /// <summary>
        /// This takes <see cref="HubConnectionContext"/> because we want to remove the connection from the
        /// _connections list in OnDisconnectedAsync and still be able to remove groups with this method.
        /// </summary>
        private async Task RemoveGroupAsyncCore(HubConnectionContext connection, string groupName)
        {
            var groupChannel = _channels.Group(groupName);

            if (!_groups.TryGetValue(groupChannel, out var group))
            {
                return;
            }

            var feature    = connection.Features.Get <IRedisFeature>();
            var groupNames = feature.Groups;

            if (groupNames != null)
            {
                lock (groupNames)
                {
                    groupNames.Remove(groupName);
                }
            }

            await group.Lock.WaitAsync();

            try
            {
                if (group.Connections.Count > 0)
                {
                    group.Connections.Remove(connection);

                    if (group.Connections.Count == 0)
                    {
                        RedisLog.Unsubscribe(_logger, groupChannel);
                        await _bus.UnsubscribeAsync(groupChannel);
                    }
                }
            }
            finally
            {
                group.Lock.Release();
            }
        }
 public override void WriteLine(string value)
 {
     RedisLog.ConnectionMultiplexerMessage(_logger, value);
 }
 private Task PublishAsync(string channel, byte[] payload)
 {
     RedisLog.PublishToChannel(_logger, channel);
     return(_bus.PublishAsync(channel, payload));
 }