/// <summary>
        ///     Handles the event for a new server joining the cluster.
        /// </summary>
        /// <param name="id">The id of the server joining.</param>
        /// <param name="remoteServer">The server joining.</param>
        protected void HandleServerJoinEvent(ushort id, IRemoteServer remoteServer)
        {
            EventHandler <ServerJoinedEventArgs> handler = ServerJoined;

            if (handler != null)
            {
                void DoServerJoinEvent()
                {
                    long startTimestamp = Stopwatch.GetTimestamp();

                    try
                    {
                        handler?.Invoke(this, new ServerJoinedEventArgs(remoteServer, id, this));
                    }
                    catch (Exception e)
                    {
                        serverJoinedEventFailuresCounter.Increment();

                        // TODO this seems bad, shoudln't we disconnect them?
                        logger.Error("A plugin encountered an error whilst handling the ServerJoined event. The server will still be connected. (See logs for exception)", e);
                    }

                    double time = (double)(Stopwatch.GetTimestamp() - startTimestamp) / Stopwatch.Frequency;

                    serverJoinedEventTimeHistogram.Report(time);
                }

                threadHelper.DispatchIfNeeded(DoServerJoinEvent);
            }
        }
        internal void Connect()
        {
            IEnumerable <IPAddress> addresses = Dns.GetHostEntry(Host).AddressList;

            // Try to connect to an IP address
            // TODO this might not reconnect to the same IP, break out option to prioritised last connected to.
            // TODO this will always try the same IP address, break out round robin option for load balancing
            this.connection = GetResultOfFirstSuccessfulInvocationOf(addresses, (address) =>
            {
                NetworkClientConnection c = serverGroup.GetConnection(address, Port);

                c.MessageReceived += MessageReceivedHandler;
                c.Disconnected    += DisconnectedHandler;

                c.Connect();

                return(c);
            });

            using (DarkRiftWriter writer = DarkRiftWriter.Create())
            {
                writer.Write(remoteServerManager.ServerID);

                using (Message message = Message.Create((ushort)CommandCode.Identify, writer))
                {
                    message.IsCommandMessage = true;
                    SendMessage(message, SendMode.Reliable);
                }
            }

            EventHandler <ServerConnectedEventArgs> handler = ServerConnected;

            if (handler != null)
            {
                void DoServerConnectedEvent()
                {
                    long startTimestamp = Stopwatch.GetTimestamp();

                    try
                    {
                        handler?.Invoke(this, new ServerConnectedEventArgs(this));
                    }
                    catch (Exception e)
                    {
                        serverConnectedEventFailuresCounter.Increment();

                        // TODO this seems bad, shouldn't we disconenct them?
                        logger.Error("A plugin encountered an error whilst handling the ServerConnected event. The server will still be connected. (See logs for exception)", e);
                    }

                    double time = (double)(Stopwatch.GetTimestamp() - startTimestamp) / Stopwatch.Frequency;

                    serverConnectedEventTimeHistogram.Report(time);
                }

                threadHelper.DispatchIfNeeded(DoServerConnectedEvent);
            }
        }
        /// <summary>
        ///     Sets the connection being used by this remote server.
        /// </summary>
        /// <param name="pendingServer">The connection to switch to.</param>
        internal void SetConnection(PendingDownstreamRemoteServer pendingServer)
        {
            if (connection != null)
            {
                connection.MessageReceived -= MessageReceivedHandler;
                connection.Disconnected    -= DisconnectedHandler;
            }

            connection = pendingServer.Connection;

            // Switch out message received handler from the pending server
            connection.MessageReceived = MessageReceivedHandler;
            connection.Disconnected    = DisconnectedHandler;

            EventHandler <ServerConnectedEventArgs> handler = ServerConnected;

            if (handler != null)
            {
                void DoServerConnectedEvent()
                {
                    long startTimestamp = Stopwatch.GetTimestamp();

                    try
                    {
                        handler?.Invoke(this, new ServerConnectedEventArgs(this));
                    }
                    catch (Exception e)
                    {
                        serverConnectedEventFailuresCounter.Increment();

                        logger.Error("A plugin encountered an error whilst handling the ServerConnected event. The server will still be connected. (See logs for exception)", e);
                    }

                    double time = (double)(Stopwatch.GetTimestamp() - startTimestamp) / Stopwatch.Frequency;

                    serverConnectedEventTimeHistogram.Report(time);
                }

                threadHelper.DispatchIfNeeded(DoServerConnectedEvent);
            }

            // Handle all messages that had queued
            foreach (PendingDownstreamRemoteServer.QueuedMessage queuedMessage in pendingServer.GetQueuedMessages())
            {
                HandleMessage(queuedMessage.Message, queuedMessage.SendMode);

                queuedMessage.Message.Dispose();
            }
        }
Example #4
0
        /// <summary>
        ///     Invokes the callback.
        /// </summary>
        /// <param name="_">Unused.</param>
        private void InvokeCallback(object _)
        {
            void DoInvoke()
            {
                Callback.Invoke(this);
            }

            threadHelper.DispatchIfNeeded(DoInvoke);
        }
        /// <summary>
        ///     Invokes the given command.
        /// </summary>
        /// <param name="rawCommand">The command as entered into the console.</param>
        /// <param name="command">The command to run.</param>
        private void InvokeCommand(string rawCommand, Command command)
        {
            CommandEventArgs args = BuildCommandEventArgs(rawCommand, command);

            threadHelper.DispatchIfNeeded(() =>
            {
                try
                {
                    command.Handler.Invoke(this, args);
                }
                catch (CommandSyntaxException e)
                {
                    logger.Error($"Syntax Error: {e.Message}\nUsage: {command.Usage}");
                }
                catch (Exception e)
                {
                    logger.Error($"A plugin encountered an error whilst handling the command '{rawCommand}'. (See logs for exception)", e);
                }
            });
        }
        /// <summary>
        ///     Called when a new client connects.
        /// </summary>
        /// <param name="connection">The new client.</param>
        internal void HandleNewConnection(NetworkServerConnection connection)
        {
            //Allocate ID and add to list
            ushort id;

            try
            {
                id = ReserveID();
            }
            catch (InvalidOperationException)
            {
                logger.Info($"New client could not be connected as there were no IDs available to allocate to them [{connection.RemoteEndPoints.Format()}].");

                connection.Disconnect();

                return;
            }


            Client client;

            try
            {
                client = Client.Create(
                    connection,
                    id,
                    this,
                    threadHelper,
                    clientLogger
#if PRO
                    , clientMetricsCollector
#endif
                    );
            }
            catch (Exception e)
            {
                logger.Error("An exception ocurred while connecting a client. The client has been dropped.", e);

                connection.Disconnect();

                DeallocateID(id, out int _);

                return;
            }

            AllocateIDToClient(id, client, out int noClients);

            // TODO if a client sends immediately after connecting then the message will be missed as the Connected event has not yet fired

            connection.Client = client;

            logger.Info($"New client [{client.ID}] connected [{client.RemoteEndPoints.Format()}].");
#if PRO
            clientsConnectedGauge.Report(noClients);
#endif

            //Inform plugins of the new connection
            EventHandler <ClientConnectedEventArgs> handler = ClientConnected;
            if (handler != null)
            {
                threadHelper.DispatchIfNeeded(
                    delegate()
                {
#if PRO
                    long startTimestamp = Stopwatch.GetTimestamp();
#endif
                    try
                    {
                        handler.Invoke(this, new ClientConnectedEventArgs(client));
                    }
                    catch (Exception e)
                    {
                        logger.Error("A plugin encountered an error whilst handling the ClientConnected event. The client will be disconnected. (See logs for exception)", e);

                        client.DropConnection();

#if PRO
                        clientConnectedEventFailuresCounter.Increment();
#endif
                        return;
                    }

#if PRO
                    double time = (double)(Stopwatch.GetTimestamp() - startTimestamp) / Stopwatch.Frequency;
                    clientConnectedEventTimeHistogram.Report(time);
#endif
                },
                    (_) => client.StartListening()
                    );
            }
        }