[pgsqlclient-checkins] SF.net SVN: pgsqlclient: [116] trunk/PostgreSqlClient/source/PostgreSql/Data/
Status: Inactive
Brought to you by:
carlosga_fb
From: <car...@us...> - 2006-04-14 13:10:59
|
Revision: 116 Author: carlosga_fb Date: 2006-04-14 06:10:41 -0700 (Fri, 14 Apr 2006) ViewCVS: http://svn.sourceforge.net/pgsqlclient/?rev=116&view=rev Log Message: ----------- 2006-04-14 Carlos Guzman Alvarez (car...@te...) * Ported the connection pooling implementation from the FirebirdClient provider. * Bug fix for the Decimal parameters handling ( found running nhibernate test suite ) * Bug fix on named parameters handling. Modified Paths: -------------- trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnection.cs trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionInternal.cs trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionPool.cs Added Paths: ----------- trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/EmptyPoolEventHandler.cs trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgPoolManager.cs Added: trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/EmptyPoolEventHandler.cs =================================================================== --- trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/EmptyPoolEventHandler.cs (rev 0) +++ trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/EmptyPoolEventHandler.cs 2006-04-14 13:10:41 UTC (rev 116) @@ -0,0 +1,23 @@ +/* + * PgSqlClient - ADO.NET Data Provider for PostgreSQL 7.4+ + * + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. + * + * Software distributed under the License is distributed on + * an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * Copyright (c) 2003, 2006 Carlos Guzman Alvarez + * All Rights Reserved. + */ + +using System; + +namespace PostgreSql.Data.PostgreSqlClient +{ + internal delegate void EmptyPoolEventHandler(object sender, EventArgs e); +} Modified: trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnection.cs =================================================================== --- trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnection.cs 2006-04-13 22:33:18 UTC (rev 115) +++ trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnection.cs 2006-04-14 13:10:41 UTC (rev 116) @@ -340,21 +340,21 @@ // Open connection if (this.options.Pooling) { - this.connectionInternal = PgConnectionPool.GetConnection(this); + this.connectionInternal = PgPoolManager.Instance.GetPool(this.connectionString).CheckOut(); } else { - this.connectionInternal = new PgConnectionInternal(this); - - this.SslSetup(); - - this.connectionInternal.OwningConnection = this; + this.connectionInternal = new PgConnectionInternal(this.connectionString); this.connectionInternal.Pooled = false; - this.connectionInternal.Connect(); } + + this.SslSetup(); + this.connectionInternal.OwningConnection = this; + this.connectionInternal.Connect(); // Set connection state to Open this.state = ConnectionState.Open; + if (this.StateChange != null) { this.StateChange(this, new StateChangeEventArgs(ConnectionState.Closed, state)); @@ -402,7 +402,7 @@ // Close connection permanently or send it back to the pool if (this.connectionInternal.Pooled) { - PgConnectionPool.FreeConnection(this.connectionInternal); + PgPoolManager.Instance.GetPool(this.connectionString).CheckIn(this.connectionInternal); } else { Modified: trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionInternal.cs =================================================================== --- trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionInternal.cs 2006-04-13 22:33:18 UTC (rev 115) +++ trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionInternal.cs 2006-04-14 13:10:41 UTC (rev 116) @@ -32,7 +32,8 @@ private PgConnectionOptions options; private PgTransaction activeTransaction; private ArrayList preparedCommands; - private long created; + private long created; + private long lifetime; private bool pooled; #endregion @@ -93,17 +94,23 @@ set { this.owningConnection = value; } } + public long Lifetime + { + get { return this.lifetime; } + set { this.lifetime = value; } + } + #endregion #region \xB7 Constructors \xB7 - public PgConnectionInternal(PgConnection owningConnection) + public PgConnectionInternal(string connectionString) { - this.owningConnection = owningConnection; - this.options = new PgConnectionOptions(owningConnection.ConnectionString); - this.database = new PgDatabase(this.options); - this.created = 0; - this.pooled = true; + this.options = new PgConnectionOptions(connectionString); + this.database = new PgDatabase(this.options); + this.created = 0; + this.lifetime = 0; + this.pooled = true; } #endregion @@ -123,15 +130,26 @@ } public void Disconnect() - { - try - { - this.database.Disconnect(); - } - catch (PgClientException ex) - { - throw new PgException(ex.Message, ex); - } + { + try + { + this.database.Disconnect(); + } + catch (PgClientException ex) + { + throw new PgException(ex.Message, ex); + } + finally + { + this.owningConnection = null; + this.database = null; + this.options = null; + this.activeTransaction = null; + this.preparedCommands = null; + this.created = 0; + this.lifetime = 0; + this.pooled = false; + } } public PgTransaction BeginTransaction(IsolationLevel level) Modified: trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionPool.cs =================================================================== --- trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionPool.cs 2006-04-13 22:33:18 UTC (rev 115) +++ trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgConnectionPool.cs 2006-04-14 13:10:41 UTC (rev 116) @@ -16,224 +16,396 @@ */ using System; -using System.Data; using System.Collections; using System.Threading; +using PostgreSql.Data.Protocol; + namespace PostgreSql.Data.PostgreSqlClient { - internal class PgConnectionPool : MarshalByRefObject - { - #region \xB7 Fields \xB7 + internal sealed class PgConnectionPool : MarshalByRefObject + { + #region \xB7 Inner Types \xB7 + + private enum MoveType + { + LockedToUnlocked, + UnlockedToLocked + } - private static ConnectionPool pool = null; + #endregion + #region \xB7 Events \xB7 + + public event EmptyPoolEventHandler EmptyPool; + + #endregion + + #region \xB7 Fields \xB7 + + private PgConnectionOptions options; + private ArrayList locked; + private ArrayList unlocked; + private Thread cleanUpThread; + private string connectionString; + private bool isRunning; + private long lifeTime; + private object syncObject; + #endregion - - #region \xB7 Methods \xB7 - public static void Init() - { - if (pool == null) - { - pool = new ConnectionPool(); - } - } - - public static PgConnectionInternal GetConnection(PgConnection owningConnection) - { - Init(); + #region \xB7 Properties \xB7 - return ((PgConnectionInternal)pool.CheckOut(owningConnection)); - } + public object SyncObject + { + get + { + if (this.syncObject == null) + { + Interlocked.CompareExchange(ref this.syncObject, new object(), null); + } - public static void FreeConnection(PgConnectionInternal c) + return this.syncObject; + } + } + + public int Count { - pool.CheckIn(c); + get + { + lock (this.unlocked.SyncRoot) + { + return this.unlocked.Count + this.locked.Count; + } + } } - #endregion - } - - internal class ConnectionPool - { - #region \xB7 Fields \xB7 + public bool HasUnlocked + { + get { return this.unlocked.Count > 0; } + } - private ArrayList locked; - private ArrayList unlocked; - private Thread cleanUpThread; - #endregion #region \xB7 Constructors \xB7 - public ConnectionPool() - { - this.locked = ArrayList.Synchronized(new ArrayList()); - this.unlocked = ArrayList.Synchronized(new ArrayList()); + public PgConnectionPool(string connectionString) + { + this.connectionString = connectionString; + this.options = new PgConnectionOptions(connectionString); + this.lifeTime = this.options.ConnectionLifeTime * TimeSpan.TicksPerSecond; - this.cleanUpThread = new Thread(new ThreadStart(RunCleanUp)); - this.cleanUpThread.Name = "CleanUp Thread"; - this.cleanUpThread.Start(); - this.cleanUpThread.IsBackground = true; + if (this.options.MaxPoolSize == 0) + { + this.locked = ArrayList.Synchronized(new ArrayList()); + this.unlocked = ArrayList.Synchronized(new ArrayList()); + } + else + { + this.locked = ArrayList.Synchronized(new ArrayList(this.options.MaxPoolSize)); + this.unlocked = ArrayList.Synchronized(new ArrayList(this.options.MaxPoolSize)); + } + + // If a minimun number of connections is requested initialize the pool + this.Initialize(); + + // Start the cleanup thread only if needed + if (this.lifeTime != 0) + { + this.isRunning = true; + + this.cleanUpThread = new Thread(new ThreadStart(this.RunCleanup)); + this.cleanUpThread.Name = "Cleanup Thread"; + this.cleanUpThread.Start(); + this.cleanUpThread.IsBackground = true; + } } #endregion #region \xB7 Methods \xB7 - - public PgConnectionInternal CheckOut(PgConnection owningConnection) + + public void CheckIn(PgConnectionInternal connection) { - string connectionString = owningConnection.ConnectionString; - PgConnectionInternal newConnection = null; + connection.OwningConnection = null; + connection.Created = System.DateTime.Now.Ticks; - lock (typeof(PgConnectionPool)) + this.MoveConnection(connection, MoveType.LockedToUnlocked); + } + + public PgConnectionInternal CheckOut() + { + PgConnectionInternal newConnection = null; + + lock (this.SyncObject) + { + // 1. Try to Get a connection from the unlocked connection list. + newConnection = this.GetConnection(); + if (newConnection != null) + { + return newConnection; + } + + // 2. Check if we have reached the max number of allowed connections + this.CheckMaxPoolSize(); + + // 3. Try to Get a connection from the unlocked connection list. + newConnection = this.GetConnection(); + if (newConnection != null) + { + return newConnection; + } + + // 4. In any other case create a new connection + newConnection = this.Create(); + + // Set connection pooling settings to the new connection + newConnection.Lifetime = this.options.ConnectionLifeTime; + newConnection.Pooled = true; + + // Added to the locked connections list. + this.locked.Add(newConnection); + } + + return newConnection; + } + + public void Clear() + { + lock (this.SyncObject) { - if (this.unlocked.Count > 0) + // Stop cleanup thread + if (this.cleanUpThread != null) { - long now = System.DateTime.Now.Ticks; + this.cleanUpThread.Abort(); + this.cleanUpThread.Join(); + } - PgConnectionInternal[] list = new PgConnectionInternal[this.unlocked.Count]; - this.unlocked.CopyTo(0, list, 0, list.Length); + // Close all unlocked connections + PgConnectionInternal[] list = (PgConnectionInternal[])this.unlocked.ToArray(typeof(PgConnectionInternal)); - foreach (PgConnectionInternal connection in list) - { - if (this.Validate(connection, connectionString)) - { - if (connection.Options.ConnectionLifeTime != 0) - { - if ((now - connection.Created) > connection.Options.ConnectionLifeTime) - { - this.unlocked.Remove(connection); - this.Expire(connection); - } - else - { - this.unlocked.Remove(connection); - this.locked.Add(connection); - - connection.OwningConnection = owningConnection; - - return connection; - } - } - else - { - this.unlocked.Remove(connection); - this.locked.Add(connection); - - connection.OwningConnection = owningConnection; - - return connection; - } - } - else - { - this.unlocked.Remove(connection); - this.Expire(connection); - } - } + foreach (PgConnectionInternal connection in list) + { + connection.Disconnect(); } - newConnection = this.Create(owningConnection); - - newConnection.OwningConnection = owningConnection; - newConnection.Pooled = true; - newConnection.Created = System.DateTime.Now.Ticks; + // Close all locked connections + list = (PgConnectionInternal[])this.locked.ToArray(typeof(PgConnectionInternal)); - owningConnection.SslSetup(); + foreach (PgConnectionInternal connection in list) + { + connection.Disconnect(); + } - newConnection.Connect(); + // Clear lists + this.unlocked.Clear(); + this.locked.Clear(); - this.locked.Add(newConnection); - } - - return newConnection; - } + // Raise EmptyPool event + if (this.EmptyPool != null) + { + this.EmptyPool(this.connectionString.GetHashCode(), null); + } - public void CheckIn(PgConnectionInternal connection) - { - lock (typeof(PgConnectionPool)) - { - connection.Created = System.DateTime.Now.Ticks; - connection.OwningConnection = null; - - this.locked.Remove(connection); - this.unlocked.Add(connection); + // Reset fields + this.unlocked = null; + this.locked = null; + this.connectionString = null; + this.cleanUpThread = null; + this.EmptyPool = null; } } - #endregion + #endregion - #region \xB7 Private Methods \xB7 + #region \xB7 Private Methods \xB7 - private void RunCleanUp() - { - TimeSpan interval = new TimeSpan(0, 0, 10); + private void Initialize() + { + lock (this.SyncObject) + { + for (int i = 0; i < this.options.MinPoolSize; i++) + { + this.unlocked.Add(this.Create()); + } + } + } - while (true) - { - this.CleanUp(null); + private PgConnectionInternal Create() + { + PgConnectionInternal connection = new PgConnectionInternal(this.connectionString); + connection.Connect(); - Thread.Sleep(interval); - } + connection.Pooled = true; + connection.Created = DateTime.Now.Ticks; + + return connection; + } + + private PgConnectionInternal GetConnection() + { + PgConnectionInternal result = null; + long check = -1; + + lock (this.unlocked.SyncRoot) + { + PgConnectionInternal[] connections = (PgConnectionInternal[])this.unlocked.ToArray(typeof(PgConnectionInternal)); + + for (int i = connections.Length - 1; i >= 0; i--) + { + if (connections[i].Verify()) + { + if (this.lifeTime != 0) + { + long now = DateTime.Now.Ticks; + long expire = connections[i].Created + this.lifeTime; + + if (now >= expire) + { + if (this.CheckMinPoolSize()) + { + this.unlocked.Remove(connections[i]); + this.Expire(connections[i]); + } + } + else + { + if (expire > check) + { + check = expire; + result = connections[i]; + } + } + } + else + { + result = connections[i]; + break; + } + } + else + { + this.unlocked.Remove(connections[i]); + this.Expire(connections[i]); + } + } + + if (result != null) + { + this.MoveConnection(result, MoveType.UnlockedToLocked); + } + } + + return result; + } + + private bool CheckMinPoolSize() + { + return !(this.options.MinPoolSize > 0 && this.Count == this.options.MinPoolSize); } - private PgConnectionInternal Create(PgConnection owningConnection) + private void CheckMaxPoolSize() { - try + if (this.options.MaxPoolSize > 0 && this.Count >= this.options.MaxPoolSize) + { + long timeout = this.options.ConnectionTimeout * TimeSpan.TicksPerSecond; + long start = DateTime.Now.Ticks; + + /* + Loop brakes without errors in next situations: + 1. connection was returned from locked to unlocked by calling CheckIn in other thread (HasUnlocked = true) + 2. connection was moved from locked to unlocked (by Checkin) and then cleaned (removed from unlocked by Cleanup) + */ + while (true) + { + if (this.Count >= this.options.MaxPoolSize && this.HasUnlocked == false) + { + if ((DateTime.Now.Ticks - start) > timeout) + { + throw new SystemException("Timeout exceeded."); + } + + Thread.Sleep(100); + } + else + { + break; + } + } + } + } + + private void RunCleanup() + { + int interval = Convert.ToInt32(TimeSpan.FromTicks(this.lifeTime).TotalMilliseconds); + + if (interval > 60000) { - return new PgConnectionInternal(owningConnection); + interval = 60000; } - catch + + try { - throw; + while (this.isRunning) + { + Thread.Sleep(interval); + + this.Cleanup(); + + if (this.Count == 0) + { + lock (this.SyncObject) + { + // Empty pool + if (this.EmptyPool != null) + { + this.EmptyPool(this.connectionString.GetHashCode(), null); + } + + // Stop running + this.isRunning = false; + } + } + } } - } - - private bool Validate(PgConnectionInternal connection, string connectionString) - { - try - { - return (connection.OwningConnection.ConnectionString == connectionString && connection.Verify()); - } - catch + catch (ThreadAbortException) { - return false; + this.isRunning = false; } } private void Expire(PgConnectionInternal connection) { - try + try { if (connection.Verify()) { connection.Disconnect(); } } - catch + catch (Exception) { - throw new PgException("Error closing database connection."); + // Do not raise an exception as the connection could be invalid due to several reasons + // ( network problems, server shutdown, ... ) } } - - private void CleanUp(object State) + + private void Cleanup() { - long now = System.DateTime.Now.Ticks; - lock (this.unlocked.SyncRoot) { - if (this.unlocked.Count > 0) + if (this.unlocked.Count > 0 && this.lifeTime != 0) { - PgConnectionInternal[] list = new PgConnectionInternal[this.unlocked.Count]; - this.unlocked.CopyTo(0, list, 0, list.Length); + PgConnectionInternal[] list = (PgConnectionInternal[])this.unlocked.ToArray(typeof(PgConnectionInternal)); foreach (PgConnectionInternal connection in list) { - if (connection.Options.ConnectionLifeTime != 0) + long now = DateTime.Now.Ticks; + long expire = connection.Created + this.lifeTime; + + if (now >= expire) { - if ((now - connection.Created) >= connection.Options.ConnectionLifeTime) + if (this.CheckMinPoolSize()) { this.unlocked.Remove(connection); this.Expire(connection); @@ -244,6 +416,30 @@ } } + private void MoveConnection(PgConnectionInternal connection, MoveType moveType) + { + if (null == connection) + { + return; + } + + lock (this.unlocked.SyncRoot) + { + switch (moveType) + { + case MoveType.LockedToUnlocked: + this.locked.Remove(connection); + this.unlocked.Add(connection); + break; + + case MoveType.UnlockedToLocked: + this.unlocked.Remove(connection); + this.locked.Add(connection); + break; + } + } + } + #endregion - } + } } Added: trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgPoolManager.cs =================================================================== --- trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgPoolManager.cs (rev 0) +++ trunk/PostgreSqlClient/source/PostgreSql/Data/PostgreSqlClient/PgPoolManager.cs 2006-04-14 13:10:41 UTC (rev 116) @@ -0,0 +1,260 @@ +/* + * PgSqlClient - ADO.NET Data Provider for PostgreSQL 7.4+ + * + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. + * + * Software distributed under the License is distributed on + * an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * Copyright (c) 2003, 2006 Carlos Guzman Alvarez + * All Rights Reserved. + */ + +using System; +using System.Collections; +using System.Threading; + +namespace PostgreSql.Data.PostgreSqlClient +{ + internal sealed class PgPoolManager + { + #region \xB7 Static Fields \xB7 + + private static readonly PgPoolManager instance = new PgPoolManager(); + + #endregion + + #region \xB7 Static Properties \xB7 + + public static PgPoolManager Instance + { + get { return PgPoolManager.instance; } + } + + #endregion + + #region \xB7 Fields \xB7 + + private Hashtable pools; + private Hashtable handlers; + private object syncObject; + + #endregion + + #region \xB7 Properties \xB7 + + public int PoolsCount + { + get + { + if (this.pools != null) + { + return this.pools.Count; + } + return 0; + } + } + + #endregion + + #region \xB7 Private Properties \xB7 + + private Hashtable Pools + { + get + { + if (this.pools == null) + { + this.pools = Hashtable.Synchronized(new Hashtable()); + } + + return this.pools; + } + } + + private Hashtable Handlers + { + get + { + if (this.handlers == null) + { + this.handlers = Hashtable.Synchronized(new Hashtable()); + } + + return this.handlers; + } + } + + private object SyncObject + { + get + { + if (this.syncObject == null) + { + Interlocked.CompareExchange(ref this.syncObject, new object(), null); + } + + return this.syncObject; + } + } + + #endregion + + #region \xB7 Constructors \xB7 + + private PgPoolManager() + { + } + + #endregion + + #region \xB7 Methods \xB7 + + public PgConnectionPool GetPool(string connectionString) + { + PgConnectionPool pool = this.FindPool(connectionString); + + if (pool == null) + { + pool = this.CreatePool(connectionString); + } + + return pool; + } + + public PgConnectionPool FindPool(string connectionString) + { + PgConnectionPool pool = null; + + lock (this.SyncObject) + { + int hashCode = connectionString.GetHashCode(); + + if (this.Pools.ContainsKey(hashCode)) + { + pool = (PgConnectionPool)pools[hashCode]; + } + } + + return pool; + } + + public PgConnectionPool CreatePool(string connectionString) + { + PgConnectionPool pool = null; + + lock (this.SyncObject) + { + pool = this.FindPool(connectionString); + + if (pool == null) + { + lock (this.pools.SyncRoot) + { + int hashcode = connectionString.GetHashCode(); + + // Create an empty pool handler + EmptyPoolEventHandler handler = new EmptyPoolEventHandler(this.OnEmptyPool); + + this.Handlers.Add(hashcode, handler); + + // Create the new connection pool + pool = new PgConnectionPool(connectionString); + + this.pools.Add(hashcode, pool); + + pool.EmptyPool += handler; + } + } + } + + return pool; + } + + public void ClearAllPools() + { + lock (this.SyncObject) + { + lock (this.pools.SyncRoot) + { + PgConnectionPool[] tempPools = new PgConnectionPool[this.pools.Count]; + + this.pools.Values.CopyTo(tempPools, 0); + + foreach (PgConnectionPool pool in tempPools) + { + // Clear pool + pool.Clear(); + } + + // Clear Hashtables + this.pools.Clear(); + this.handlers.Clear(); + } + } + } + + public void ClearPool(string connectionString) + { + lock (this.SyncObject) + { + lock (this.pools.SyncRoot) + { + int hashCode = connectionString.GetHashCode(); + + if (this.pools.ContainsKey(hashCode)) + { + PgConnectionPool pool = (PgConnectionPool)this.pools[hashCode]; + + // Clear pool + pool.Clear(); + } + } + } + } + + public int GetPooledConnectionCount(string connectionString) + { + PgConnectionPool pool = this.FindPool(connectionString); + + return (pool != null) ? pool.Count : 0; + } + + #endregion + + #region \xB7 Event Handlers \xB7 + + private void OnEmptyPool(object sender, EventArgs e) + { + lock (this.Pools.SyncRoot) + { + int hashCode = (int)sender; + + if (this.pools.ContainsKey(hashCode)) + { + PgConnectionPool pool = (PgConnectionPool)this.Pools[hashCode]; + + lock (pool.SyncObject) + { + EmptyPoolEventHandler handler = (EmptyPoolEventHandler)this.Handlers[hashCode]; + + pool.EmptyPool -= handler; + + this.Pools.Remove(hashCode); + this.Handlers.Remove(hashCode); + + pool = null; + handler = null; + } + } + } + } + + #endregion + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |