Exemplo n.º 1
0
        /// <summary>
        /// Create a new DatabaseConnector object, which manages the secure
        /// tunnel to the database.  If the tunnel is not enabled, then the
        /// object does nothing and returns immediately, otherwise, the tunnel
        /// process is started (and monitored and restarted in the event of a
        /// connection failure).  In this case, the game server has two minutes
        /// to successfully connect to the tunnel or else the server process
        /// will be exited, because a game server does not function without a
        /// database being present during startup.
        ///
        /// During the wait for the database to come online, the logger queue
        /// is periodically written out by this module as the
        /// ACR_ServerCommunicator (which normally assumes responsibility for
        /// flushing the logger queue to the server log file) has not yet been
        /// initialized.
        /// </summary>
        /// <param name="Script">Supplies a script object used to access the
        /// NWScript API.</param>
        public DatabaseConnector(CLRScriptFramework.CLRScriptBase Script)
        {
            SafeWaitHandle Job;
            int            Timeouts;

            //
            // If the secure tunnel is not managed by this module, then return
            // without setting anything up.
            //

            if (String.IsNullOrEmpty(GetTunnelProcessCmdLine()))
            {
                Logger.Log("DatabaseConnector.DatabaseConnector: Not using secure tunnel process as <NWNX4-Install-Path>\\DatabaseConnector.ini, [Settings], CommandLine is not set.");
                Logger.FlushLogMessages(Script);
                return;
            }

            //
            // Create a job object and appropriately configure it.
            //

            Job = CreateJob();

            ConfigureJob(Job);

            JobObject = Job;

            //
            // Extract PLINK.EXE and store it to disk so that it may be used to
            // launch the secure tunnel.
            //

            PlinkFilePath = ExtractResource("PLINK.EXE", ModuleLinkage.KnownResources["SGTatham_Plink"]);

            //
            // Start the monitor thread for the PLINK.EXE process.  This will
            // also create the secure tunnel for the database connection.
            //

            MonitorThread = new Thread(DatabaseConnectionMonitorThread);
            MonitorThread.Start();

            //
            // Give the monitor two minutes to establish a secure tunnel and
            // for the MySQL client library to successfully execute one SQL
            // query before allowing the server to proceed.
            //

            Logger.Log("DatabaseConnector.DatabaseConnector: Waiting two minutes for database connection to come online...");

            Timeouts = 120;

            while (InitCompletedEvent.WaitOne(1000) == false)
            {
                Logger.FlushLogMessages(Script);

                Timeouts -= 1;
                if (Timeouts == 0)
                {
                    break;
                }
            }

            //
            // Create a database connection and wait for a SQL query to
            // complete successfully before allowing the server to proceed with
            // initialization.
            //
            // Note that this must be a query that succeeds even if the
            // database schema has not yet been initialized.  The old-style get
            // server address from database routine queries a built-in table.
            //

            using (ALFA.MySQLDatabase Database = new ALFA.MySQLDatabase())
            {
                while (Timeouts != 0)
                {
                    Logger.Log("DatabaseConnector.DatabaseConnector: Checking for database connectivity...");

                    Logger.FlushLogMessages(Script);

                    try
                    {
                        Database.ACR_SQLQuery("SELECT UNIX_TIMESTAMP();");
                        if (Database.ACR_SQLFetch())
                        {
                            break;
                        }
                    }
                    catch
                    {
                    }

                    Thread.Sleep(1000);
                    Timeouts -= 1;
                }
            }

            if (Timeouts == 0)
            {
                throw new ApplicationException("Database connection did not start successfully in two minutes, exiting the server process.  Check that DatabaseConnector.ini is correctly configured, that the public key for the ALFA central server matches the PuTTY registry on this machine, and that the network is online and connectivity to the ALFA central server is functional.");
            }

            //
            // Fall through to allow the server to complete normal
            // initialization.  If a database connectivity break is encountered
            // then the monitor thread will attempt to reconnect.
            //
        }
Exemplo n.º 2
0
        /// <summary>
        /// The thread procedure for the thread that monitors the PLINK.EXE
        /// instance that provides a secure SSH port forward to the MySQL
        /// database.  Initially, it creates a PLINK.EXE process, and from
        /// that point it ensures that if the PLINK.EXE process closes, that a
        /// new process is started to connect out to the database automatically
        /// in the event of a temporary connectivity interruption.
        /// </summary>
        private void DatabaseConnectionMonitorThread()
        {
            int Timeout = 1000;

            for (;;)
            {
                uint StartTick;

                try
                {
                    Process TunnelProcess;
                    string  CmdLine = GetTunnelProcessCmdLine();

                    TunnelProcess = CreateProcessInJob(PlinkFilePath, CmdLine, JobObject);

                    try
                    {
                        TunnelProcess.WaitForInputIdle(20000);
                    }
                    catch (System.InvalidOperationException)
                    {
                        //
                        // Process wasn't a GUI app, don't bother waiting.
                        //
                    }
                    catch
                    {
                        try
                        {
                            TunnelProcess.Kill();
                        }
                        catch
                        {
                        }

                        throw;
                    }

                    InitCompletedEvent.Set();

                    StartTick = (uint)Environment.TickCount;

                    TunnelProcess.WaitForExit();

                    //
                    // Clear the reconnect timeout to the minimum value if the
                    // process appeared to start successfully and stay online
                    // for more than 30 seconds.
                    //

                    if ((uint)Environment.TickCount - StartTick > 30000)
                    {
                        Timeout = 1000;
                    }
                    else
                    {
                        if (Timeout < 32000)
                        {
                            Timeout *= 2;
                        }
                    }
                }
                catch (Exception e)
                {
                    Logger.Log("DatabaseConnector.DatabaseConnectionMonitorThread: Exception managing database secure tunnel: {0}", e);

                    if (Timeout < 32000)
                    {
                        Timeout *= 2;
                    }
                }

                //
                // Wait for a truncated expontential backoff before trying
                // again.
                //

                Logger.Log("DatabaseConnector.DatabaseConnectionMonitorThread: Database secure tunnel closed, attempting reconnect in {0} milliseconds.", Timeout);

                Thread.Sleep(Timeout);
            }
        }