using System; using System.Data; using System.Threading; using MySql.Data.MySqlClient; namespace Ion.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; public int databaseClients { get { return GetOpenConnectionCount(); } } 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 public int GetOpenConnectionCount() { int n = 0; for (int i = 0; i < mClients.Length; i++) if (mClients[i].State == ConnectionState.Open) n++; return n; } #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(); } /// /// returns starvation number /// public int getStarvationNumber() { return mClientStarvationCounter; } /// /// 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. /// // ONE OF THE THINGS TO BE FIXED 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 >= 45) // Not used in the last 30 seconds { mClients[i].Disconnect(); // Temporarily close connection } } } } Thread.Sleep(10000); // 10 seconds } catch (ThreadAbortException) { } // Nothing special catch (Exception ex) { Holo.Out.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) { // TODO: exception handling mClients[i].Connect(); } // Is this client ready? if (mClients[i].State == ConnectionState.Open) { 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(); return pAnonymous; } } public void ReleaseClient(uint Handle) { if (mClients.Length >= (Handle - 1)) // Ensure client exists { mClientAvailable[Handle - 1] = true; } } /// /// 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 void INSERT(IDataObject obj) { using (DatabaseClient dbClient = GetClient()) { obj.INSERT(dbClient); } } public void DELETE(IDataObject obj) { using (DatabaseClient dbClient = GetClient()) { obj.DELETE(dbClient); } } public void UPDATE(IDataObject obj) { using (DatabaseClient dbClient = GetClient()) { obj.UPDATE(dbClient); } } public override string ToString() { return mServer.ToString() + ":" + mDatabase.Name; } #endregion } }