using System; using System.Data; using System.Threading; using System.Collections.Generic; using System.Text; using Aleeda.Storage; using MySql.Data.MySqlClient; namespace Aleeda.Storage { /// /// DatabaseManager acts as a proxy towards an encapsulated Database at a DatabaseServer. /// public class DatabaseManager { #region Fields private DatabaseServer mServer; private Database mDatabase; private DatabaseClient[] mClients = new DatabaseClient[0]; private bool[] mClientAvailable = new bool[0]; private int mClientStarvationCounter; private Thread mClientMonitor; #endregion #region Properties #endregion #region Constructor /// /// Constructs a DatabaseManager for a given DatabaseServer and Database. /// /// The DatabaseServer for this database proxy. /// The Database for this database proxy. public DatabaseManager(DatabaseServer pServer, Database pDatabase) { mServer = pServer; mDatabase = pDatabase; } /// /// Constructs a DatabaseManager for given database server and database details. /// /// The network host of the database server, eg 'localhost' or '127.0.0.1'. /// The network port of the database server as an unsigned 32 bit integer. /// The username to use when connecting to the database. /// The password to use in combination with the username when connecting to the database. /// The name of the database to connect to. /// The minimum connection pool size for the database. /// The maximum connection pool size for the database. public DatabaseManager(string sServer, uint Port, string sUser, string sPassword, string sDatabase, uint minPoolSize, uint maxPoolSize) { mServer = new DatabaseServer(sServer, Port, sUser, sPassword); mDatabase = new Database(sDatabase, minPoolSize, maxPoolSize); mClientMonitor = new Thread(MonitorClientsLoop); mClientMonitor.Priority = ThreadPriority.Lowest; mClientMonitor.Start(); } #endregion #region Methods /// /// Starts the client monitor thread. The client monitor disconnects inactive clients etc. /// public void StartMonitor() { mClientMonitor = new Thread(MonitorClientsLoop); mClientMonitor.Priority = ThreadPriority.Lowest; mClientMonitor.Start(); } /// /// Stops the client monitor thread. /// public void StopMonitor() { if (mClientMonitor != null) { mClientMonitor.Abort(); } } /// /// Disconnects and destroys all database clients. /// public void DestroyClients() { lock (this) { for (int i = 0; i < mClients.Length; i++) { mClients[i].Destroy(); mClients[i] = null; } } } /// /// Nulls all instance fields of the database manager. /// public void DestroyManager() { mServer = null; mDatabase = null; mClients = null; mClientAvailable = null; mClientMonitor = null; } /// /// Closes the connections of database clients that have been inactive for too long. Connections can be opened again when needed. /// private void MonitorClientsLoop() { while (true) { try { lock (this) { DateTime dtNow = DateTime.Now; for (int i = 0; i < mClients.Length; i++) { if (mClients[i].State != ConnectionState.Closed) { if (mClients[i].Inactivity >= 60) // Not used in the last %x% seconds { mClients[i].Disconnect(); // Temporarily close connection //LimeEnvironment.GetLog().WriteInformation("Disconnected database client #" + mClients[i].Handle); } } } } Thread.Sleep(10000); // 10 seconds } catch (ThreadAbortException) { } // Nothing special catch (Exception ex) { AleedaEnvironment.GetLog().WriteError(ex.Message); } } } /// /// Creates the connection string for this database proxy. /// public string CreateConnectionString() { MySqlConnectionStringBuilder pCSB = new MySqlConnectionStringBuilder(); // Server pCSB.Server = mServer.Host; pCSB.Port = mServer.Port; pCSB.UserID = mServer.User; pCSB.Password = mServer.Password; // Database pCSB.Database = mDatabase.Name; pCSB.MinimumPoolSize = mDatabase.minPoolSize; pCSB.MaximumPoolSize = mDatabase.maxPoolSize; return pCSB.ToString(); } public DatabaseClient GetClient() { // Let other threads wait if they contact this DatabaseManager while we're busy with it lock (this) { // Try to find an available client for (uint i = 0; i < mClients.Length; i++) { // Somebody here? if (mClientAvailable[i] == true) { // No starvation anymore mClientStarvationCounter = 0; // Is this connection broken? //if (mClients[i].State == ConnectionState.Broken) //{ // mClients[i] = new DatabaseClient((i + 1), this); // Create new client //} // Is this connection closed? if (mClients[i].State == ConnectionState.Closed) { mClients[i].Connect(); //LimeEnvironment.GetLog().WriteInformation("Opening connection for database client #" + mClients[i].Handle); } // Is this client ready? if (mClients[i].State == ConnectionState.Open) { //LimeEnvironment.GetLog().WriteLine("Handed out client #" + mClients[i].Handle); mClientAvailable[i] = false; // BRB mClients[i].UpdateLastActivity(); return mClients[i]; } } } // No clients? mClientStarvationCounter++; // Are we having a structural lack of clients? if (mClientStarvationCounter >= ((mClients.Length + 1) / 2)) // Database hungry much? { // Heal starvation mClientStarvationCounter = 0; // Increase client amount by 0.3 SetClientAmount((uint)(mClients.Length + 1 * 1.3f)); // Re-enter this method return GetClient(); } DatabaseClient pAnonymous = new DatabaseClient(0, this); pAnonymous.Connect(); //LimeEnvironment.GetLog().WriteLine("Handed out anonymous client."); return pAnonymous; } } public void ReleaseClient(uint Handle) { if (mClients.Length >= (Handle - 1)) // Ensure client exists { mClientAvailable[Handle - 1] = true; //LimeEnvironment.GetLog().WriteLine("Released client #" + Handle); } } /// /// Sets the amount of clients that will be available to requesting methods. If the new amount is lower than the current amount, the 'excluded' connections are destroyed. If the new connection amount is higher than the current amount, new clients are prepared. Already existing clients and their state will be maintained. /// /// The new amount of clients. public void SetClientAmount(uint Amount) { lock (this) { if (mClients.Length == Amount) return; if (Amount < mClients.Length) // Client amount shrinks, dispose clients that will die { for (uint i = Amount; i < mClients.Length; i++) { mClients[i].Destroy(); mClients[i] = null; } } DatabaseClient[] pClients = new DatabaseClient[Amount]; bool[] pClientAvailable = new bool[Amount]; for (uint i = 0; i < Amount; i++) { if (i < mClients.Length) // Keep the existing client and it's available state { pClients[i] = mClients[i]; pClientAvailable[i] = mClientAvailable[i]; } else // We are in need of more clients, so make another one { pClients[i] = new DatabaseClient((i + 1), this); pClientAvailable[i] = true; // Elegant? } } // Update the instance fields mClients = pClients; mClientAvailable = pClientAvailable; } } public bool INSERT(IDataObject obj) { using (DatabaseClient dbClient = GetClient()) { return obj.INSERT(dbClient); } } public bool DELETE(IDataObject obj) { using (DatabaseClient dbClient = GetClient()) { return obj.DELETE(dbClient); } } public bool UPDATE(IDataObject obj) { using (DatabaseClient dbClient = GetClient()) { return obj.UPDATE(dbClient); } } public override string ToString() { return mServer.ToString() + ":" + mDatabase.Name; } #endregion } }