//========== Background Methods ======================================================================================

        private void HeartbeatCheck()
        {
            try {             // Try catch needed because errors are otherwised silenced, somehow :-(
                // Remove dead connections (Ping not received timely)
                foreach (var kvp in LastSeenConnections.ToList())
                {
                    if (kvp.Value < DateTime.Now.AddSeconds(-CleanZombieAfter))
                    {
                        var connectionId = kvp.Key;
                        Log.SignalR(LogTag.CleaningZombieConnection, new { connectionId });
                        CleanConnection(connectionId);
                    }
                }

                // Remove inactive members from "crowded" rooms
                var chatState = ChatCtrl.GetState();
                foreach (var roomKvp in chatState.Rooms)
                {
                    if (roomKvp.Key.IsMonoLang() && roomKvp.Value.Count > BootAboveUserCount)
                    {
                        var mostInactiveUserKvp = roomKvp.Value.Aggregate((l, r) => l.Value.LastActive < r.Value.LastActive ? l : r);
                        if (mostInactiveUserKvp.Value.LastActive < DateTime.Now.AddMinutes(-BootOutOfRoomAfter))
                        {
                            Log.SignalR(LogTag.KickSlackerOutOfRoom, new { roomId = roomKvp.Key, userId = mostInactiveUserKvp.Key });
                            // Get the list of connections that have to leave the room
                            var userConnectionIds = UsersConnections.GetFromKey(mostInactiveUserKvp.Key)
                                                    .Where(connId => RoomsConnections.HasConnection(roomKvp.Key, connId))
                                                    .Select(connId => connId.ToString()).ToList();
                            Clients.Clients(userConnectionIds).LeaveRoom(roomKvp.Key);
                        }
                    }
                }

                // Check for inactive members
                foreach (var chatUser in chatState.AllUsers.Values)
                {
                    if (chatUser.LastActivity < DateTime.Now.AddMinutes(-UserIdleAfter))
                    {
                        ChatCtrl.SetIdleStatus(chatUser.Id);
                        Log.SignalR(LogTag.SetChatUserIdle, new { userId = chatUser.Id });
                    }
                }

                Log.SignalR(LogTag.HubHeartbeatCheckCompleted);
            } catch (Exception e) {
                Log.Error(LogTag.HeartbeatCheckError, e);
            }
        }
        private void PublishHealthReport()
        {
            var chatState = ChatCtrl.GetState();
            var report    = new {
                HubAliveConnections         = _heartBeat.GetConnections().Count,
                UsersConnectionsCount       = UsersConnections.Count,
                UsersConnectionsValuesCount = UsersConnections.Values.Count,
                RoomsConnectionsCount       = RoomsConnections.Count,
                RoomsConnectionsValuesCount = RoomsConnections.ValuesCount,
                ChatAllUsersCount           = chatState.AllUsers.Count,
                ChatRoomsCounts             = chatState.Rooms.Count,
                ChatRoomsUsersCounts        = chatState.Rooms.Values.Sum(list => list.Count),
                ManagedQueuesCount, UnackedManagedQueuesCount,
                FullDetails = new { HubAliveConnections = _heartBeat.GetConnections().Select(conn => new { conn.ConnectionId, conn.IsAlive, conn.IsTimedOut }), UsersConnections = UsersConnections.All, RoomsConnections = RoomsConnections.All }
            };

            Log.SignalR(LogTag.ChatHubHealthReport, new { report });
        }