All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atomikos.datasource.pool.ConnectionPool Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
/**
 * Copyright (C) 2000-2020 Atomikos 
 *
 * LICENSE CONDITIONS
 *
 * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
 */

package com.atomikos.datasource.pool;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;
import com.atomikos.thread.InterruptedExceptionHelper;
import com.atomikos.thread.TaskManager;
import com.atomikos.timing.AlarmTimer;
import com.atomikos.timing.AlarmTimerListener;
import com.atomikos.timing.PooledAlarmTimer;


public abstract class ConnectionPool implements XPooledConnectionEventListener
{
	private static Logger LOGGER = LoggerFactory.createLogger(ConnectionPool.class);

	private final static int DEFAULT_MAINTENANCE_INTERVAL = 60;

	protected List> connections = new ArrayList>();
	private ConnectionFactory connectionFactory;
	private ConnectionPoolProperties properties;
	private boolean destroyed;
	private PooledAlarmTimer maintenanceTimer;
	private String name;


	public ConnectionPool ( ConnectionFactory connectionFactory , ConnectionPoolProperties properties ) throws ConnectionPoolException
	{
		this.connectionFactory = connectionFactory;
		this.properties = properties;
		this.destroyed = false;
		this.name = properties.getUniqueResourceName();
		init();
	}

	private void assertNotDestroyed() throws ConnectionPoolException
	{
		if (destroyed) throw new ConnectionPoolException ( "Pool was already destroyed - you can no longer use it" );
	}

	private void init() throws ConnectionPoolException
	{
		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": initializing..." );
		addConnectionsIfMinPoolSizeNotReached();
		launchMaintenanceTimer();
	}

	private void launchMaintenanceTimer() {
		int maintenanceInterval = properties.getMaintenanceInterval();
		if ( maintenanceInterval <= 0 ) {
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": using default maintenance interval..." );
			maintenanceInterval = DEFAULT_MAINTENANCE_INTERVAL;
		}
		maintenanceTimer = new PooledAlarmTimer ( maintenanceInterval * 1000 );
		maintenanceTimer.addAlarmTimerListener(new AlarmTimerListener() {
			public void alarm(AlarmTimer timer) {
				reapPool();
				removeConnectionsThatExceededMaxLifetime();
				addConnectionsIfMinPoolSizeNotReached();
				removeIdleConnectionsIfMinPoolSizeExceeded();
			}
		});
		TaskManager.SINGLETON.executeTask ( maintenanceTimer );
	}

	private synchronized void addConnectionsIfMinPoolSizeNotReached() {
		int connectionsToAdd = properties.getMinPoolSize() - totalSize();
		for ( int i = 0 ; i < connectionsToAdd ; i++ ) {
			try {
				XPooledConnection xpc = createPooledConnection();
				connections.add ( xpc );
				xpc.registerXPooledConnectionEventListener ( this );
			} catch ( Exception dbDown ) {
				//see case 26380
				if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": could not establish initial connection" , dbDown );
			}
		}
	}

	private XPooledConnection createPooledConnection()
			throws CreateConnectionException {
		XPooledConnection xpc = connectionFactory.createPooledConnection();
		return xpc;
	}

	protected abstract ConnectionType recycleConnectionIfPossible() throws Exception;

	/**
	 * Borrows a connection from the pool.
	 * @return The connection
	 * @throws CreateConnectionException If the pool attempted to grow but failed.
	 * @throws PoolExhaustedException If the pool could not grow because it is exhausted.
	 * @throws ConnectionPoolException Other errors.
	 */
	public ConnectionType borrowConnection() throws CreateConnectionException , PoolExhaustedException, ConnectionPoolException
	{
		assertNotDestroyed();

		ConnectionType ret = null;	
		ret = findExistingOpenConnectionForCallingThread();	
		if (ret == null) {
			ret = findOrWaitForAnAvailableConnection();		
		}
		return ret;
	}

	private ConnectionType findOrWaitForAnAvailableConnection() throws ConnectionPoolException {
		ConnectionType ret = null;
		long remainingTime = properties.getBorrowConnectionTimeout() * 1000L;		
		do {
			ret = retrieveFirstAvailableConnectionAndGrowPoolIfNecessary();
			if ( ret == null ) {
				remainingTime = waitForAtLeastOneAvailableConnection(remainingTime);
				assertNotDestroyed();
			}
		} while ( ret == null );
		return ret;
	}

	private ConnectionType retrieveFirstAvailableConnectionAndGrowPoolIfNecessary() throws CreateConnectionException {
		
		ConnectionType ret = retrieveFirstAvailableConnection();
		if ( ret == null && canGrow() ) {
			growPool();
			ret = retrieveFirstAvailableConnection();
		}		
		return ret;
	}

	private ConnectionType findExistingOpenConnectionForCallingThread() {
		ConnectionType recycledConnection = null ;
		try {
			recycledConnection = recycleConnectionIfPossible();
		} catch (Exception e) {
			//ignore but log
			LOGGER.logDebug ( this + ": error while trying to recycle" , e );
		}
		return recycledConnection;
	}

	protected void logCurrentPoolSize() {
		if ( LOGGER.isTraceEnabled() )  {
			LOGGER.logTrace( this +  ": current size: " + availableSize() + "/" + totalSize());
		}
	}

	private boolean canGrow() {
		return totalSize() < properties.getMaxPoolSize();
	}

	protected abstract ConnectionType retrieveFirstAvailableConnection();

	private synchronized void growPool() throws CreateConnectionException {
		XPooledConnection xpc = createPooledConnection();
		connections.add ( xpc );
		xpc.registerXPooledConnectionEventListener(this);
		logCurrentPoolSize();
	}

	private synchronized void removeIdleConnectionsIfMinPoolSizeExceeded() {
		if (connections == null || properties.getMaxIdleTime() <= 0 )
			return;

		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": trying to shrink pool" );
		List> connectionsToRemove = new ArrayList>();
		int maxConnectionsToRemove = totalSize() - properties.getMinPoolSize();
		if ( maxConnectionsToRemove > 0 ) {
			for ( int i=0 ; i < connections.size() ; i++ ) {
				XPooledConnection xpc = connections.get(i);
				long lastRelease = xpc.getLastTimeReleased();
				long maxIdle = properties.getMaxIdleTime();
				long now = System.currentTimeMillis();
				if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": connection idle for " + (now - lastRelease) + "ms");
				if ( xpc.isAvailable() &&  ( (now - lastRelease) >= (maxIdle * 1000L) ) && ( connectionsToRemove.size() < maxConnectionsToRemove ) ) {
					if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": connection idle for more than " + maxIdle + "s, closing it: " + xpc);
					destroyPooledConnection(xpc, false);
					connectionsToRemove.add(xpc);
				}
			}
		}
		connections.removeAll(connectionsToRemove);
		logCurrentPoolSize();
	}

	protected void destroyPooledConnection(XPooledConnection xpc, boolean reap) {
		xpc.destroy(reap);
	}

	public synchronized void reapPool()
	{
		long maxInUseTime = properties.getReapTimeout();
		if ( connections == null || maxInUseTime <= 0 ) return;

		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": reaping old connections" );

		Iterator> it = connections.iterator();
		while ( it.hasNext() ) {
			XPooledConnection xpc = it.next();
			long lastTimeReleased = xpc.getLastTimeAcquired();
			boolean inUse = !xpc.isAvailable();

			long now = System.currentTimeMillis();
			if ( inUse && ( ( now - maxInUseTime * 1000 ) > lastTimeReleased ) ) {
				if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": connection in use for more than " + maxInUseTime + "s, reaping it: " + xpc );
				xpc.destroy(true);
				it.remove();
			}
		}
		logCurrentPoolSize();
	}
	
	private synchronized void removeConnectionsThatExceededMaxLifetime()
	{
		long maxLifetime = properties.getMaxLifetime() * 1000L;
		if ( connections == null || maxLifetime <= 0 ) return;

		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": closing connections that exceeded maxLifetime" );

		Iterator> it = connections.iterator();
		long now = System.currentTimeMillis();
		while ( it.hasNext() ) {
			XPooledConnection xpc = it.next();
			long creationTime = xpc.getCreationTime();
			if ( xpc.isAvailable() ) {
				if ((now - creationTime) >= (maxLifetime)) {
					if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": connection in use for more than maxLifetime, destroying it: " + xpc );
					destroyPooledConnection(xpc, false);
					it.remove();
				}
			}
		}
		logCurrentPoolSize();
	}

	public synchronized void destroy()
	{

		if ( ! destroyed ) {
			LOGGER.logInfo ( this + ": destroying pool..." );
			for ( int i=0 ; i < connections.size() ; i++ ) {
				XPooledConnection xpc =  connections.get(i);
				if ( !xpc.isAvailable() ) {
					LOGGER.logWarning ( this + ": connection is still in use on pool destroy: " + xpc +
					" - please check your shutdown sequence to avoid heuristic termination " +
					"of ongoing transactions!" );
				}
				destroyPooledConnection(xpc, false);
			}
			connections = null;
			destroyed = true;
			maintenanceTimer.stopTimer();
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": pool destroyed." );
		}
	}
	
	public synchronized void refresh() {
		List> connectionsToRemove = new ArrayList>();
		for (XPooledConnection conn : connections) {
			if (conn.isAvailable()) {
				connectionsToRemove.add(conn);
				destroyPooledConnection(conn, false);
			}
		}
		connections.removeAll(connectionsToRemove);
		addConnectionsIfMinPoolSizeNotReached();
	}

	/**
	 * Wait until the connection pool contains an available connection or a timeout happens.
	 * Returns immediately if the pool already contains a connection in state available.
	 * @throws CreateConnectionException if a timeout happened while waiting for a connection
	 */
	private synchronized long waitForAtLeastOneAvailableConnection(long waitTime) throws PoolExhaustedException
	{
        while (availableSize() == 0) {
        	if ( waitTime <= 0 ) throw new PoolExhaustedException ( "ConnectionPool: pool is empty - increase either maxPoolSize or borrowConnectionTimeout" );
            long before = System.currentTimeMillis();
        	try {
        		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": about to wait for connection during " + waitTime + "ms...");
        		this.wait (waitTime);

			} catch (InterruptedException ex) {
				// cf bug 67457
				InterruptedExceptionHelper.handleInterruptedException ( ex );
				// ignore
				if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": interrupted during wait" , ex );
			}
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": done waiting." );
			long now = System.currentTimeMillis();
            waitTime -= (now - before);
        }
        return waitTime;
	}

	/**
	 * The amount of pooled connections in state available.
	 * @return the amount of pooled connections in state available.
	 */
	public synchronized int availableSize()
	{
		int ret = 0;

		if ( !destroyed ) {
			int count = 0;
			for ( int i=0 ; i < connections.size() ; i++ ) {
				XPooledConnection xpc = connections.get(i);
				if (xpc.isAvailable()) count++;
			}
			ret = count;
		}
		return ret;
	}

	/**
	 * The total amount of pooled connections in any state.
	 * @return the total amount of pooled connections in any state
	 */
	public synchronized int totalSize()
	{
		if ( destroyed ) return 0;

		return connections.size();
	}

	public synchronized void onXPooledConnectionTerminated(XPooledConnection connection) {
		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace( this +  ": connection " + connection + " became available, notifying potentially waiting threads");
		this.notify();

	}
		
	public String toString() {
		return "atomikos connection pool '" + name + "'";
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy