//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Text;
using System.Threading;
namespace IBM.Data.DB2
{
///
///
///
/// One connection pool per connectionstring
internal sealed class DB2ConnectionPool
{
private ArrayList openFreeConnections; // list of pooled connections sorted by age. First connection is present at index 'connectionsUsableOffset'
private Queue openFreeMinimalAllocated;
private int connectionsOpen; // total number of connections open (in pool, an in use by application)
private int connectionsInUse; // total connection in use by application
private int connectionsUsableOffset; // Offset to the first pooled connection in 'openFreeConnections'
private Timer timer;
public string databaseProductName;
public string databaseVersion;
public int majorVersion;
public int minorVersion;
private DB2ConnectionSettings connectionSettings;
public DB2ConnectionPool(DB2ConnectionSettings connectionSettings)
{
this.connectionSettings = connectionSettings;
openFreeConnections = new ArrayList();
}
public DB2ConnectionSettings ConnectionSettings
{
get { return connectionSettings; }
}
public DB2OpenConnection GetOpenConnection(DB2Connection db2Conn)
{
DB2OpenConnection connection = null;
lock(openFreeConnections.SyncRoot)
{
if((connectionSettings.ConnectionPoolSizeMax > 0) &&
(connectionsOpen >= connectionSettings.ConnectionPoolSizeMax))
{
throw new ArgumentException("Maximum connections reached for connectionstring");
}
while(connectionsOpen > connectionsInUse)
{
connection = (DB2OpenConnection)openFreeConnections[openFreeConnections.Count - 1];
openFreeConnections.RemoveAt(openFreeConnections.Count - 1);
// check if connection is dead
int isDead;
short sqlRet = DB2CLIWrapper.SQLGetConnectAttr(connection.DBHandle, DB2Constants.SQL_ATTR_CONNECTION_DEAD, out isDead, 0, IntPtr.Zero);
if(((sqlRet == DB2Constants.SQL_SUCCESS_WITH_INFO) || (sqlRet == DB2Constants.SQL_SUCCESS)) &&
(isDead == DB2Constants.SQL_CD_FALSE))
{
connectionsInUse++;
break;
}
else
{
connectionsOpen--;
connection.Dispose();
connection = null;
}
}
if(connectionsOpen == connectionsInUse)
{
if(timer != null)
{
timer.Dispose();
timer = null;
}
}
}
if(connection == null)
{
openFreeConnections.Clear();
connectionsUsableOffset = 0;
connection = new DB2OpenConnection(connectionSettings, db2Conn);
connectionsOpen++;
connectionsInUse++;
}
return connection;
}
private void DisposeTimedoutConnections(object state)
{
lock(openFreeConnections.SyncRoot)
{
if(timer != null)
{
TimeSpan timeToDispose = TimeSpan.Zero;
DB2OpenConnection connection;
while(connectionsOpen > connectionsInUse)
{
connection = (DB2OpenConnection)openFreeConnections[connectionsUsableOffset];
timeToDispose = connection.poolDisposalTime.Subtract(DateTime.Now);
if((timeToDispose.Ticks < 0) || // time to die
(timeToDispose > connectionSettings.ConnectionLifeTime)) // messing with system clock
{
connection.Dispose();
openFreeConnections[connectionsUsableOffset] = null;
connectionsOpen--;
connectionsUsableOffset++;
}
else
{
break;
}
}
if(connectionsOpen > connectionsInUse)
{
connection = (DB2OpenConnection)openFreeConnections[connectionsUsableOffset];
timer.Change(timeToDispose, new TimeSpan(-1));
}
else
{
timer.Dispose();
timer = null;
}
}
if((connectionsUsableOffset > (openFreeConnections.Capacity / 2)) &&
(connectionsOpen > connectionsInUse))
{
openFreeConnections.RemoveRange(0, connectionsUsableOffset); // cleanup once in a while
connectionsUsableOffset = 0;
}
}
}
public void AddToFreeConnections(DB2OpenConnection connection)
{
lock(openFreeConnections.SyncRoot)
{
connection.poolDisposalTime = DateTime.Now.Add(connectionSettings.ConnectionLifeTime);
if(timer == null)
{
timer = new Timer(new TimerCallback(DisposeTimedoutConnections), null,
connectionSettings.ConnectionLifeTime, new TimeSpan(-1));
}
connectionsInUse--;
openFreeConnections.Add(connection);
}
}
public void OpenConnectionFinalized()
{
lock(openFreeConnections.SyncRoot)
{
connectionsOpen--;
connectionsInUse--;
}
}
///
/// Find a specific connection pool
///
///
///
static public DB2ConnectionPool FindConnectionPool(string connectionString)
{
return (DB2ConnectionPool)DB2Environment.Instance.connectionPools[connectionString];
}
///
/// Get a connection pool. If it doesn't exist yet, create it
///
///
///
static public DB2ConnectionPool GetConnectionPool(DB2ConnectionSettings connectionSettings)
{
DB2Environment environment = DB2Environment.Instance;
lock(environment.connectionPools.SyncRoot)
{
DB2ConnectionPool pool = (DB2ConnectionPool)environment.connectionPools[connectionSettings.ConnectionString];
if(pool == null)
{
pool = new DB2ConnectionPool(connectionSettings);
environment.connectionPools.Add(connectionSettings.ConnectionString, pool);
}
return pool;
}
}
}
}