com.gemstone.gemfire.internal.datasource.AbstractPoolCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gemfire-core Show documentation
Show all versions of gemfire-core Show documentation
SnappyData store based off Pivotal GemFireXD
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.internal.datasource;
import java.util.*;
import java.io.Serializable;
import java.sql.Connection;
import com.gemstone.gemfire.CancelException;
import com.gemstone.gemfire.SystemFailure;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.LogWriterImpl;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.jta.TransactionUtils;
/**
* AbstractPoolCache implements the ConnectionPoolCache interface. This is base
* class for the all connection pools. The class also implements the
* Serializable interface. The pool maintain a list for keeping the available
* connections(not assigned to user) and the active connections(assigned to
* user) This is a thread safe class.
*
* @author tnegi
* @author Asif Second Version .Modified the synchronization code & objects on
* which locks were being taken. Changed the logic of retrieval of
* connection & returning of connection. The beahviour of cleaner thread
* has been modified such that it waits on activeCache if it is empty.
* Prevention of deadlocks & optmization of code.
*/
public abstract class AbstractPoolCache implements ConnectionPoolCache,
Serializable {
protected int INIT_LIMIT;
private int MAX_LIMIT;
protected transient Map availableCache;
protected transient Map activeCache;
protected EventListener connEventListner;
// private String error = "";
protected ConfiguredDataSourceProperties configProps;
//Asif:expirationTime is for the available
//connection which are expired in milliseconds
protected int expirationTime;
//Asif:timeOut is for the Active connection which are time out in
// milliseconds
protected int timeOut;
//Client Timeout in milliseconds
protected int loginTimeOut;
// private final boolean DEBUG = false;
public transient ConnectionCleanUpThread cleaner;
private int totalConnections = 0;
private int activeConnections = 0;
private List expiredConns = null;
protected long sleepTime = -1;
private Thread th = null;//cleaner thread
/**
* Constructor initializes the AbstractPoolCache properties.
*
* @param eventListner The event listner for the database connections.
* @param configs The ConfiguredDataSourceProperties object containing the
* configuration for the pool.
* @throws PoolException
*/
public AbstractPoolCache(EventListener eventListner,
ConfiguredDataSourceProperties configs) throws PoolException {
availableCache = new HashMap();
activeCache = Collections.synchronizedMap(new LinkedHashMap());
connEventListner = eventListner;
expiredConns = Collections.synchronizedList(new ArrayList());
MAX_LIMIT = configs.getMaxPoolSize();
expirationTime = configs.getConnectionExpirationTime() * 1000;
timeOut = configs.getConnectionTimeOut() * 1000;
loginTimeOut = configs.getLoginTimeOut() * 1000;
INIT_LIMIT = Math.min(configs.getInitialPoolSize(), MAX_LIMIT);
configProps = configs;
cleaner = this.new ConnectionCleanUpThread();
ThreadGroup group = LogWriterImpl
.createThreadGroup("Cleaner threads", (LogWriterI18n) null);
th = new Thread(group, cleaner);
th.setDaemon(true);
th.start();
}
protected void initializePool() {
if (INIT_LIMIT > 0) {
long currTime = System.currentTimeMillis();
for (int count = 0; count < INIT_LIMIT; count++) {
try {
availableCache.put(getNewPoolConnection(), Long.valueOf(currTime));
++totalConnections;
}
catch (Exception ex) {
LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
if (writer.fineEnabled())
writer
.fine(
"AbstractPoolCache::initializePool:Error in creating connection",
ex.getCause());
}
}
}
}
/**
* Authenticates the username and password for the database connection.
*
* @param user The username for the database connection
* @param pass The password for the database connection
* @throws SQLException
*
* public abstract void checkCredentials(String user, String pass)
*/
/**
* @throws PoolException
* @return ???
*/
public abstract Object getNewPoolConnection() throws PoolException;
/**
* Returns the connection to the available pool Asif: When the connection is
* returned to the pool, notify any one thread waiting on availableCache so
* that it can go into connection seeking state.
*
* @param connectionObject PooledConnection object for return.
*
*/
public void returnPooledConnectionToPool(Object connectionObject) {
boolean returnedHappened = false;
if (connectionObject != null) {
//Asif: Take a lock on activeCache while removing the Connection
// from the map. It is possible that while the connection is
// being returned the cleaner thread migh have also removed
// it from the activeMap. If that is the case we don't do
// anything bcoz the cleaner thread will take care of decrementing
// the total & available connection. We wil take a lock on the
//individual connection rather than complete activeMap
// bcoz there is no point in blocking other threads from
//returning their own connections
synchronized (connectionObject) {
if (this.activeCache.containsKey(connectionObject)) {
//Asif: Remove teh connection from activeCache
// Don't add it to availabel pool now. Add it when we have
// taken a lock on availableCache & then we will modify the
// count.
this.activeCache.remove(connectionObject);
returnedHappened = true;
}
}
}
if (returnedHappened) {
//Asif: take the lock on availableCache & decrement the
// count of active connections. Call notify on availableConnections
// so that any waiting client thread will go into connection
// seeking state
synchronized (this.availableCache) {
--this.activeConnections;
this.availableCache.put(connectionObject, Long.valueOf(System
.currentTimeMillis()));
this.availableCache.notify();
}
}
}
/**
* Expires connection in the available pool.
*
* @param connectionObject
*/
abstract void destroyPooledConnection(Object connectionObject);
/**
* Returns number of connections currently in use.
*
* @return int Number of active connections
*/
public int getActiveCacheSize() {
return this.activeConnections;
}
/**
* Returns number of connections available for use
*
* @return Size of available cache.
*/
public int getAvailableCacheSize() {
return (this.totalConnections - this.activeConnections);
}
/**
* @see com.gemstone.gemfire.internal.datasource.ConnectionPoolCache#expirePooledConnection(Object)
* @param connectionObject Asif: This function will set the timestamp
* associated with the Connection object such that it will get timed
* out by the cleaner thread. Normally when this function is called,
* the connection object will be present in activeCache as there is
* no way client can return the object. But it is possible that the
* cleaner thread may have already picked it up ,so we still have to
* check whether it is contained in the map or not
*
*/
public void expirePooledConnection(Object connectionObject) {
synchronized (connectionObject) {
if (this.activeCache.containsKey(connectionObject)) {
//Change the time stamp associated with the object
long prev = ((Long) this.activeCache.get(connectionObject)).longValue();
prev = prev - this.timeOut - 1000;
this.activeCache.put(connectionObject, Long.valueOf(prev));
}
}
}
/**
* Gets the connection from the pool. The specified user and password are
* used. If the available pool is not empty a connection is fetched from the
* available pool. If the available pool is empty and the total connections in
* the pool(available + active) are less then the Max limit, a new connection
* is created and returned to the client. If the Max limit is reached the
* client waits for the connection to be available.
*
* Asif: The client thread checks if the total number of active connections
* have exhausted the limit. If yes it waits on the availableCache. When
* another thread returns the connection to the pool, the waiting thread gets
* notified. If a thread experiences timeout while waiting , a SQLException
* will be thrown. Other case is that there are available connections in map.
* Now while getting connection from avaialbel map , we may or may not obtain
* connetion bcoz the connection in available map may have expired. But if the
* checkOutConnection() returns null , this iteslf guarantees that atleast one
* connection expired so the current thread can safely demand one new
* connection.
*
* @throws PoolException
* @return Object connection object from the pool.
*/
public Object getPooledConnectionFromPool() throws PoolException {
Object poolConn = null;
//checkCredentials(username, password);
/*if (availableCache == null) {
synchronized (this) {
if (availableCache == null) {
initializePool();
}
}
}*/
long now = System.currentTimeMillis();
synchronized (this.availableCache) {
while ((totalConnections - activeConnections) == 0
&& totalConnections == MAX_LIMIT) {
try {
this.availableCache.wait(loginTimeOut);
long newtime = System.currentTimeMillis();
long duration = newtime - now;
if (duration > loginTimeOut)
throw new PoolException(LocalizedStrings.AbstractPoolCache_ABSTRACTPOOLEDCACHEGETPOOLEDCONNECTIONFROMPOOLLOGIN_TIMEOUT_EXCEEDED.toLocalizedString());
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
// TODO add a cancellation check?
LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
if (writer.fineEnabled()) writer.fine("AbstractPooledCache::getPooledConnectionFromPool:InterruptedException in waiting thread");
throw new PoolException(LocalizedStrings.AbstractPoolCache_ABSTRACTPOOLEDCACHEGETPOOLEDCONNECTIONFROMPOOLINTERRUPTEDEXCEPTION_IN_WAITING_THREAD.toLocalizedString());
}
}
if ((totalConnections - activeConnections) > 0) {
poolConn = checkOutConnection(now);
if (poolConn != null) {
activeCache.put(poolConn, Long.valueOf(now));
++activeConnections;
}
}
if (poolConn == null) {
poolConn = getNewPoolConnection();
activeCache.put(poolConn, Long.valueOf(now));
++totalConnections;
++activeConnections;
}
}
//Asif: Notify the cleaner thread if it is waiting
// on the active Cache as there is an entry now
synchronized (activeCache) {
activeCache.notify();
}
return poolConn;
}
/**
* Returns the max pool limit.
*
* @return int
*/
public int getMaxLimit() {
return MAX_LIMIT;
}
/**
* Remove a connection from the pool. Modified by Asif : This function is
* called from a synch block where the lock is taken on this.availableCache
*
* @param now Current time in milliseconds
* @throws PoolException
* @return Object Connection object from the pool.
*/
private Object checkOutConnection(long now) throws PoolException {
// boolean expiryCheck = false;
Object retConn = null;
Set entryset = availableCache.entrySet();
Iterator itr = entryset.iterator();
Map.Entry entry = null;
while (itr.hasNext()) {
entry = (Map.Entry) itr.next();
long time = ((Long) entry.getValue()).longValue();
if ((now - time) <= expirationTime) {
retConn = entry.getKey();
itr.remove();
break;
}
else {
//Asif : Take a lock on expiredConns, so that clean up thread
// does not miss it while emptying.
synchronized (this.expiredConns) {
this.expiredConns.add(entry.getKey());
}
itr.remove();
//Asif :Reduce the total number of available connections
// We will not notify the cleaner thread. It will clean
// the list next time when the thread awakes. The cleaner thread
// will be either waiting on the activeCache or sleeping.
//It will get notified whenever there is an elemnt added
// is added in the activeCache. If there is atleast one
// object in the activeMap it will sleep rather than wait
--totalConnections;
}
}
return retConn;
}
//Code for Clean up thread
/**
* Asif: The Cleaner thread calls this function periodically to clear the
* expired connection list & also to add those connections to expiry list ,
* which have timed out while being active. The thread first iterates over the
* map of active connections. It does by getting array of keys contained in
* the map so that there is no backing of Map. It collects all the actiev
* connections timeout & if there is atleast one such connection , it will
* issue a notify or notify all ( if more than one connections have timed
* out). After that it will just clear the list of expired connections *
*/
protected void cleanUp() {
//Asif Get an array of keys in the activeConnection map
//Since we are using LinkedHashMap the keys are guaranteed to be in order
// of oldest conn as the first element
// PooledConnection[] activeConnArr = (PooledConnection[])
// this.activeCache.keySet().toArray(new PooledConnection[0]);
int numConnTimedOut = 0;
long now = System.currentTimeMillis();
sleepTime = -1;
boolean toContinue = true;
//int len = activeConnArr.length;
//for (int i = 0; i < len; ++i) {
while (toContinue) {
Object conn = null;
Long associatedValue = null;
//Get the connection which is the oldest
synchronized (this.activeCache) {
Set set = this.activeCache.entrySet();
Iterator itr = set.iterator();
if (itr.hasNext()) {
Map.Entry entry = (Map.Entry) itr.next();
conn = entry.getKey();
associatedValue = (Long) entry.getValue();
}
else {
toContinue = false;
continue;
}
}
synchronized (conn) {
if (this.activeCache.containsKey(conn)) {
//Asif: We need to again get the assocaited value & check
//if it is same as before. As it is possibel that by the time
//we reach here , the connection was returned to available map
// and again put in active map , but this time it will be
//at the extreme end !!! . So we cannot use it.
// If that is the case we need to skip the entry
//Check the timeout
Long associatedValueInWindow = (Long) this.activeCache.get(conn);
long then = associatedValueInWindow.longValue();
if (associatedValueInWindow.longValue() == associatedValue.longValue()) {
if ((now - then) > timeOut) {
// Asif :remove the connection from activeMap so
// that destroy can be called on it.
// We will first collect all the connections
// which have timed out & then expire them.
// In that gap even if teh client genuinely closes
// teh connection , it will not be returned to the
// pool as the active map no longer contains it
this.activeCache.remove(conn);
this.expiredConns.add(conn);
++numConnTimedOut;
}
else {
//AsifTODO: Just keep a final expitry time
sleepTime = then + timeOut - now;
toContinue = false;
}
}
}
}
}
if (numConnTimedOut > 0) {
//Asif : In case only one connection timed out , take a lock
//on availableCache & call notify . If more than one timed out
// call notify all . Delete the total connections & number of active
// connections by the right amount
synchronized (this.availableCache) {
this.activeConnections -= numConnTimedOut;
this.totalConnections -= numConnTimedOut;
if (numConnTimedOut == 1)
this.availableCache.notify();
else
this.availableCache.notifyAll();
}
}
//Asif : Create a temp list which copies the connections in
// the expired list & releases the lock on expired list
// immediately . Then it is safe to clear the expiredConnList
List temp = new ArrayList();
synchronized (this.expiredConns) {
temp.addAll(this.expiredConns);
this.expiredConns.clear();
}
//Asif: destroy the connections contained in the temp list
// & clear it
int size = temp.size();
for (int i = 0; i < size; ++i) {
Object conn = temp.get(i);
this.destroyPooledConnection(conn);
}
temp.clear();
}
/**
* It will clean all the thread
*/
public void clearUp() {
cleaner.toContinueRunning = false;
try {
th.interrupt();
}
catch (Exception e) {
LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
if (writer.fineEnabled())
writer
.fine(
"AbstractPoolCache::clearUp: Exception in interrupting the thread",
e);
}
// closing all the connection
try {
Iterator availableCacheItr = availableCache.keySet().iterator();
Iterator activeCacheItr = activeCache.keySet().iterator();
while (activeCacheItr.hasNext()) {
((Connection) activeCacheItr.next()).close();
}
while (availableCacheItr.hasNext()) {
((Connection) availableCacheItr.next()).close();
}
}
catch (Exception e) {
LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
if (writer.fineEnabled())
writer
.fine("AbstractPoolCache::clearUp: Exception in closing connections. Ignoring this exception)");
}
/*
* try { th.join(); } catch (Exception e) { e.printStackTrace(); }
*/
}
/**
* Inner class for Clean up thread. Asif: The inner class implements Runnable
*/
class ConnectionCleanUpThread implements Runnable {
// private AbstractPoolCache poolCache;
//private int sleepTime;
protected volatile boolean toContinueRunning = true;
/*
* public ConnectionCleanUpThread(int time) { // poolCache = pool; sleepTime =
* time;
*/
public void run() {
LogWriterI18n writer = TransactionUtils.getLogWriterI18n();
while (toContinueRunning) {
SystemFailure.checkFailure();
try {
cleanUp();
if (sleepTime != -1)
Thread.sleep(sleepTime);
//Asif : The cleaner thread will wait on activeCache if it is
// empty . Else it will sleep for a while & again do the
// clean up. If the activeMap is empty cleaner thread will
// wait till some cleint obtains the connection. At this point
// the thread will awaken .It will sleep for stipulated time
// & then check on the connections
synchronized (activeCache) {
if (activeCache.isEmpty() && toContinueRunning) {
activeCache.wait();
}
} // synchronized
}
catch (InterruptedException e) {
// No need to reset the bit, we'll exit.
if (toContinueRunning) {
writer.fine("ConnectionCleanupThread: interrupted", e);
}
break;
}
catch (CancelException e) {
if (toContinueRunning) {
writer.fine("ConnectionCleanupThread: cancelled", e);
}
break;
}
catch (Exception e) {
if (writer.fineEnabled() && toContinueRunning)
writer.fine(
"ConnectionCleanUpThread::run: Thread encountered Exception. e="
+ e.toString() + ". Ignoring the exception", e);
}
} // while
} // method
} // CleanupThread
}