Threading with connection pool

Hi

My application is an interface to ldap directory. I have not used any ldap open source api to retrieve data from ldap. I have written connection pool that will help the application to connect to the ldap. It's working fine, but it's creating threads which are not invited.

ConnectionPool class takes care of the connection storage and creation, while Housekeeping thread relases these connection when idle after a given time.

Can someone please help in finding the problem in the code that creates additional threads.

/**

*

*/

package com.ba.cdLookup.manager;

import com.ba.cdLookup.exception.CDLookupException;

import com.ba.cdLookup.server.CdLookupProperties;

import java.util.Vector;

import javax.naming.Context;

import javax.naming.NamingException;

/**

*

*/

publicclass HouseKeeperThreadextends Thread{

/**

* Apache Logger to log erro/info/debug statements.

*/

protectedstatic org.apache.commons.logging.Log log = org.apache.axis.components.logger.LogFactory

.getLog(HouseKeeperThread.class.getName());

privatestatic HouseKeeperThread houseKeeperThread;

/**

* Close all connections existing.

*

* @param connections

*void

*/

privatevoid closeConnections(Vector connections){

String methodIdentifier ="closeConnections";

int numOfConn = connections.size();

try{

for (int i = 0; i < numOfConn; i++){

Context context = (Context) connections.get(i);

if (context !=null){

context.close();

context =null;

connections.remove(i);

numOfConn--;

log.info(" connection name:" + context

+" removed. Threadcount =" + (connections.size()));

}

}

}catch (NamingException e){

String errMsg ="CDLdapBuilder connect() - failure while releasing connection "

+" Exception is " + e.toString();

log.error(errMsg);

}catch (Exception e){

String errMsg ="CDLdapBuilder connect() - failure while releasing connection "

+" Exception is " + e.toString();

log.error(errMsg);

}

}

/**

* Thread run method

*/

publicvoid run(){

String methodIdentifier ="run";

try{

while(true){

log.debug("house keeping :" +this +" sleep");

//sleep(100000);

log.debug("house keeping :" +this +" startd after sleep");

sleep(CdLookupProperties.getHouseKeepConnectionTime());

ConnectionPool connectionPool = ConnectionPool

.getConnectionPool();

Vector connList = connectionPool.getAvailableConnections();

closeConnections(connList);

}

}catch (CDLookupException cde){

log.error(methodIdentifier +" " + cde.getStackTrace());

}catch (InterruptedException ie){

log.error(methodIdentifier +" " + ie.getStackTrace());

}

}

/**

* @param connectionPool

* @return

* Thread

*/

publicstatic Thread getInstance(){

if(houseKeeperThread==null){

houseKeeperThread =new HouseKeeperThread();

}

return houseKeeperThread ;

}

}

***************************************************************************

/**

*

*/

package com.ba.cdLookup.manager;

import com.ba.cdLookup.exception.CDLookupException;

import com.ba.cdLookup.server.CdLookupProperties;

import com.ba.cdwebservice.schema.cdLookupPacket.LookupFailureReasons;

import java.util.Properties;

import java.util.Vector;

import javax.naming.Context;

import javax.naming.NamingException;

import javax.naming.directory.DirContext;

import javax.naming.directory.InitialDirContext;

/**

* ConnectionPool class manages, allocates LDAP connections. It works as a lazy

* binder and retrieves connections only when required. It doesn't allow

* connection greater then the maximum connection stated.

*

* To retrieve a connection the singelton method getConnectionPool is to used,

* which retruns thread safe singleton object for the connection.

*

*/

publicclass ConnectionPoolimplements Runnable{

privateint initialConnections = 0;

privateint maxConnections = 0;

privateboolean waitIfBusy =false;

private Vector availableConnections, busyConnections;

privateboolean connectionPending =false;

privatestaticint threadCount = 0;

/**

* classIdentifier

*/

privatefinal String classIdentifier ="ConnectionPool";

/**

* Apache Logger to log erro/info/debug statements.

*/

protectedstatic org.apache.commons.logging.Log log = org.apache.axis.components.logger.LogFactory

.getLog(CDLdapBuilder.class.getName());

/**

* To get the attribute a systemaccessfor out of the search result

*/

private String vendorContextFactoryClass ="com.sun.jndi.ldap.LdapCtxFactory";// "com.ibm.jndi.LDAPCtxFactory";

/**

* context factory to use

*/

private String ldapServerUrl ="LDAP://test.ldap.com";// default ldap

/**

* server live used by default

*/

private String searchBase;

/**

* environment properties.

*/

private Properties env;

/**

* DirContext

*/

private javax.naming.directory.DirContext ctx;

/**

* default search base to be used in Corporate Directory searches

*/

private String defaultSearchBase ="dc=Pathway";

/**

* search criteria

*/

private String searchAttributes;

/**

* search filter to retrieve data from CD

*/

private String searchFilter;

/**

* CorporateDirectoryLookup Constructor

*

* loads the setup parameters from the properties file and stores them

* Makes a connection to the directory and sets default search base

*

* @throws CDLookupException

*

* @throws CDLookupException

*/

private ConnectionPool()throws CDLookupException{

this.maxConnections = CdLookupProperties.getMaxConnection();// maxConnections;

this.initialConnections = CdLookupProperties.getInitialConnection();

this.waitIfBusy = CdLookupProperties.isWaitIfBusy();

this.searchBase = CdLookupProperties.getDefaultSearchBase();

//for local env testing

// this.maxConnections = 5;

// this.initialConnections = 1;

// this.waitIfBusy = true;

/*

* For keeping no of connections in the connection pool if

* (initialConnections > maxConnections) { initialConnections =

* maxConnections; }

*/

availableConnections =new Vector(maxConnections);

busyConnections =new Vector(maxConnections);

for (int i = 0; i < maxConnections; i++){

availableConnections.add(makeNewConnection());

}

}

/**

* ConnectionPoolHolder provide Thread safe singleton

* instance of ConnectionPool class

*

*/

privatestaticclass ConnectionPoolHolder{

/**

* connection pool instance

*/

privatestatic ConnectionPool connectionPool =null;

/**

* If no ConnectionPool object is present, it creates instance of

* ConnectionPool class and initiates thread on that.

*

* @return ConnectionPool Returns singleton object of ConnectionPool

* class.

* @throws CDLookupException

*/

privatestatic ConnectionPool getInstance()throws CDLookupException{

if (connectionPool ==null){

connectionPool =new ConnectionPool();

new Thread(connectionPool).start();

// Initiate house keeping thread.

HouseKeeperThread.getInstance().start();

}

return connectionPool;

}

}

/**

* Returns singleton object of ConnectionPool class.

*

* @return ConnectionPool

* @throws CDLookupException

*

*/

publicstatic ConnectionPool getConnectionPool()throws CDLookupException{

return ConnectionPoolHolder.getInstance();

}

/**

* getConnection retrieves connections to the corp directory. In case

* there is no available connections in the pool then it'll try to

* create one, if the max connection limit for the connection pool

* reaches then this waits to retrieve one.

*

* @return Context

* @throws CDLookupException

*

*/

publicsynchronized Context getConnection()throws CDLookupException{

String methodIdentifier ="getConnection";

if (!availableConnections.isEmpty()){

int connectionSize = availableConnections.size() - 1;

DirContext existingConnection = (DirContext) availableConnections

.get(connectionSize);

availableConnections.remove(connectionSize);

/*

* If connection on available list is closed (e.g., it timed

* out), then remove it from available list and repeat the

* process of obtaining a connection. Also wake up threads that

* were waiting for a connection because maxConnection limit was

* reached.

*/

if (existingConnection ==null){

notifyAll();// Freed up a spot for anybody waiting

return (getConnection());

}else{

busyConnections.add(existingConnection);

return (existingConnection);

}

}else{

/*

* Three possible cases: 1) You haven't reached maxConnections

* limit. So establish one in the background if there isn't

* already one pending, then wait for the next available

* connection (whether or not it was the newly established one).

* 2) You reached maxConnections limit and waitIfBusy flag is

* false. Throw SQLException in such a case. 3) You reached

* maxConnections limit and waitIfBusy flag is true. Then do the

* same thing as in second part of step 1: wait for next

* available connection.

*/

if ((totalConnections() < maxConnections) && !connectionPending){

makeBackgroundConnection();

}elseif (!waitIfBusy){

thrownew CDLookupException("Connection limit reached", 0);

}

/*

* Wait for either a new connection to be established (if you

* called makeBackgroundConnection) or for an existing

* connection to be freed up.

*/

try{

wait();

}catch (InterruptedException ie){

String errMsg ="Exception raised =" + ie.getStackTrace();

log.error(errMsg);

thrownew CDLookupException(classIdentifier, methodIdentifier,

errMsg, ie);

}

// connection freed up, so try again.

return (getConnection());

}

}

/**

* You can't just make a new connection in the foreground when none are

* available, since this can take several seconds with a slow network

* connection. Instead, start a thread that establishes a new

* connection, then wait. You get woken up either when the new

* connection is established or if someone finishes with an existing

* connection.

*

*/

privatevoid makeBackgroundConnection(){

connectionPending =true;

try{

Thread connectThread =new Thread(this);

log.debug("background thread created");

connectThread.start();

}catch (OutOfMemoryError oome){

log.error("makeBackgroundConnection ="+ oome.getStackTrace());

}

}

/**

* Thread run method

*/

publicvoid run(){

String methodIdentifier ="run";

try{

Context connection = makeNewConnection();

synchronized (this){

availableConnections.add(connection);

connectionPending =false;

notifyAll();

}

}catch (Exception e){// SQLException or OutOfMemory

// Give up on new connection and wait for existing one

// to free up.

String errMsg ="Exception raised =" + e.getStackTrace();

log.error(errMsg);

}

}

/**

* This explicitly makes a new connection. Called in the foreground when

* initializing the ConnectionPool, and called in the background when

* running.

*

* @return Context

* @throws CDLookupException

*

*/

private Context makeNewConnection()throws CDLookupException{

String methodIdentifier ="makeNewConnection";

Context context =null;

env =new Properties();

log.debug("inside " + methodIdentifier);

try{

env.put(Context.INITIAL_CONTEXT_FACTORY,

getVendorContextFactoryClass());

env.put(Context.PROVIDER_URL, getLdapServerUrl());

env.put("com.sun.jndi.ldap.connect.pool","true");

context =new InitialDirContext(env);

}catch (NamingException e){

String errMsg ="CDLdapBuilder connect() - failure while attempting to contact "

+ ldapServerUrl +" Exception is " + e.toString();

thrownew CDLookupException(classIdentifier, methodIdentifier,

errMsg, e, LookupFailureReasons.serviceUnavailable);

}catch (Exception e){

String errMsg ="CDLdapBuilder connect() - failure while attempting to contact "

+ ldapServerUrl +" Exception is " + e.toString();

thrownew CDLookupException(classIdentifier, methodIdentifier,

errMsg, e, LookupFailureReasons.serviceUnavailable);

}

log.info("new connection :" + (threadCount++) +" name =" + context);

log.debug("exit " + methodIdentifier);

return context;

}

/**

* releases connection to the free pool

*

* @param context

*/

publicsynchronizedvoid free(Context context){

busyConnections.remove(context);

availableConnections.add(context);

// Wake up threads that are waiting for a connection

notifyAll();

}

/**

*

* @return int give total no of avail connections.

*

*/

publicsynchronizedint totalConnections(){

return (availableConnections.size() + busyConnections.size());

}

/**

* Close all the connections. Use with caution: be sure no connections

* are in use before calling. Note that you are not <I>required</I> to

* call this when done with a ConnectionPool, since connections are

* guaranteed to be closed when garbage collected. But this method gives

* more control regarding when the connections are closed.

*/

publicsynchronizedvoid closeAllConnections(){

closeConnections(availableConnections);

availableConnections =new Vector();

closeConnections(busyConnections);

busyConnections =new Vector();

}

/**

* Close all connections existing.

*

* @param connections

*void

*/

privatevoid closeConnections(Vector connections){

String methodIdentifier ="closeConnections";

try{

for (int i = 0; i < connections.size(); i++){

Context context = (Context) connections.get(i);

if (context !=null){

log.info(" connection name:" + context

+" removed. Threadcount =" + (threadCount++));

context.close();

context =null;

}

}

}catch (NamingException e){

String errMsg ="CDLdapBuilder connect() - failure while attempting to contact "

+ ldapServerUrl +" Exception is " + e.toString();

log.error(errMsg);

}

}

publicsynchronized String toString(){

String info ="ConnectionPool(" + getLdapServerUrl() +","

+ getVendorContextFactoryClass() +")" +", available="

+ availableConnections.size() +", busy="

+ busyConnections.size() +", max=" + maxConnections;

return (info);

}

/**

* @return the defaultSearchBase

*/

publicfinal String getDefaultSearchBase(){

return defaultSearchBase;

}

/**

* @param defaultSearchBase

*the defaultSearchBase to set

*/

publicfinalvoid setDefaultSearchBase(String defaultSearchBase){

this.defaultSearchBase = defaultSearchBase;

}

/**

* @return the ldapServerUrl

*/

publicfinal String getLdapServerUrl(){

return ldapServerUrl;

}

/**

* @param ldapServerUrl

*the ldapServerUrl to set

*/

publicfinalvoid setLdapServerUrl(String ldapServerUrl){

this.ldapServerUrl = ldapServerUrl;

}

/**

* @return the vendorContextFactoryClass

*/

publicfinal String getVendorContextFactoryClass(){

return vendorContextFactoryClass;

}

/**

* @param vendorContextFactoryClass

*the vendorContextFactoryClass to set

*/

publicfinalvoid setVendorContextFactoryClass(

String vendorContextFactoryClass){

this.vendorContextFactoryClass = vendorContextFactoryClass;

}

/**

* @return the availableConnections

*/

publicfinal Vector getAvailableConnections(){

return availableConnections;

}

}

[29698 byte] By [RajivBravoa] at [2007-10-3 5:19:22]
# 1
You can throw all this away. The Sun JDNI provider for LDAP already does connection pooling.
ejpa at 2007-7-14 23:26:17 > top of Java-index,Core,Core APIs...
# 2

hi ejp

Thx for the reply.

// Enable connection pooling

env.put("com.sun.jndi.ldap.connect.pool", "true");

Is this suffice to get the connection pool working,

Should i merely have a thread to maintain the connection with the ldap that uses sun's connection pool; or allow requestes to create new object for the connection and still this pool will hold.

for example in the above code instead to housekeep the thread merely maintain connection with the pool

or

should I directly connect each object with the ldap?

I am unable to understand how exactly sun's connection pool is working and how it should be used. I have gone thru the following example but picture is still hazy and undigestable to me.

java.sun.com/products/jndi/tutorial/ldap/connect/pool.html

Rgds

RajivBravoa at 2007-7-14 23:26:17 > top of Java-index,Core,Core APIs...
# 3
You don't need to do anything except set that property. You certainly don't need your own thread. Just create Contexts and use them. It all happens behind the scenes.
ejpa at 2007-7-14 23:26:17 > top of Java-index,Core,Core APIs...
# 4
the point which have further raised confusion over the pooling is : http://blogs.warwick.ac.uk/kieranshaw/entry/ldap_connection_pooling/Morover, a sincere thx 'ejp' for the reply as this is the first time i am using any forum.Rgds
RajivBravoa at 2007-7-14 23:26:17 > top of Java-index,Core,Core APIs...
# 5
I don't see any source of confusion there. It just says to close everything that can be closed: contexts, naming enumerations, ... Good advice.
ejpa at 2007-7-14 23:26:17 > top of Java-index,Core,Core APIs...
# 6

a snippet from the site

>>>>

This turns on connection pooling. However, we didn't use pooling in the other bit of code. So, one of the other wasn't working. Trying out pooling on both bits of code didn't improve things either, basically because it is a multi杢hreaded application with 100's of requests a minute, if you just keep creating new LdapContext's from a LdapCtxFactory, you are using a new LdapCtxFactory every time

>>>>

This is pointing towards using of new LdapContext's from a LdapCtxFactory everytime a request is recieved. My application is also load intensive ...so this is raising doubts in me ...

Rgds

RajivBravoa at 2007-7-14 23:26:17 > top of Java-index,Core,Core APIs...
# 7
Not if you read the whole article to the conclusion (that I've already quoted).
ejpa at 2007-7-14 23:26:17 > top of Java-index,Core,Core APIs...