/*
* IronJacamar, a Java EE Connector Architecture implementation
* Copyright 2015, Red Hat Inc, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the Eclipse Public License 1.0 as
* published by the Free Software Foundation.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse
* Public License for more details.
*
* You should have received a copy of the Eclipse Public License
* along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.ironjacamar.core.connectionmanager.pool.stable;
import org.ironjacamar.core.api.connectionmanager.pool.CapacityDecrementer;
import org.ironjacamar.core.api.connectionmanager.pool.FlushMode;
import org.ironjacamar.core.connectionmanager.Credential;
import org.ironjacamar.core.connectionmanager.listener.ConnectionListener;
import org.ironjacamar.core.connectionmanager.pool.AbstractManagedConnectionPool;
import org.ironjacamar.core.connectionmanager.pool.CapacityFiller;
import org.ironjacamar.core.connectionmanager.pool.CapacityRequest;
import org.ironjacamar.core.connectionmanager.pool.ConnectionValidator;
import org.ironjacamar.core.connectionmanager.pool.FillRequest;
import org.ironjacamar.core.connectionmanager.pool.IdleConnectionRemover;
import org.ironjacamar.core.connectionmanager.pool.ManagedConnectionPoolUtility;
import org.ironjacamar.core.connectionmanager.pool.PoolFiller;
import org.ironjacamar.core.connectionmanager.pool.capacity.DefaultCapacity;
import org.ironjacamar.core.connectionmanager.pool.capacity.TimedOutDecrementer;
import org.ironjacamar.core.connectionmanager.pool.capacity.TimedOutFIFODecrementer;
import org.ironjacamar.core.tracer.Tracer;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import javax.resource.ResourceException;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.ValidatingManagedConnectionFactory;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.DESTROY;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.DESTROYED;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.FLUSH;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.FREE;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.IN_USE;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.TO_POOL;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.VALIDATION;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.ZOMBIE;
/**
* The stable ManagedConnectionPool
*/
public class StableManagedConnectionPool extends AbstractManagedConnectionPool
{
/**
* The associated pool
*/
private StablePool pool;
/**
* The connection listeners
*/
private ConcurrentLinkedDeque<ConnectionListener> listeners;
/**
* Constructor
*
* @param pool The pool
* @param credential The credential
*/
public StableManagedConnectionPool(StablePool pool, Credential credential)
{
super(pool, credential);
this.pool = pool;
this.listeners = new ConcurrentLinkedDeque<ConnectionListener>();
if (credential.equals(pool.getPrefillCredential()) &&
pool.getConfiguration().isPrefill() &&
pool.getConfiguration().getInitialSize() > 0)
{
PoolFiller.fillPool(new FillRequest(this, pool.getConfiguration().getInitialSize()));
}
if (pool.getConfiguration().isBackgroundValidation()
&& pool.getConfiguration().getBackgroundValidationMillis() > 0)
{
//Register validation
ConnectionValidator.getInstance().registerPool(this, pool.getConfiguration().getBackgroundValidationMillis());
}
if (pool.getConfiguration().getIdleTimeoutMinutes() > 0)
{
//Register idle connection cleanup
IdleConnectionRemover.getInstance().registerPool(this,
pool.getConfiguration().getIdleTimeoutMinutes() * 60 * 1000L);
}
}
/**
* {@inheritDoc}
*/
public ConnectionListener getConnectionListener() throws ResourceException
{
if (pool.getLogger().isTraceEnabled())
{
synchronized (this)
{
pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this,
"getConnectionListener()",
pool.getConnectionManager().getManagedConnectionFactory(),
pool.getConnectionManager(),
pool, pool.getConfiguration(),
listeners, pool.getInternalStatistics(),
credential.getSubject(),
credential.getConnectionRequestInfo()));
}
}
else if (pool.getLogger().isDebugEnabled())
{
pool.getLogger().debug(ManagedConnectionPoolUtility.details("getConnectionListener()",
pool.getConfiguration().getId(),
getCount(IN_USE, listeners),
pool.getConfiguration().getMaxSize()));
}
// Use request semaphore, as a fair queue across all credentials
long timestamp = pool.getInternalStatistics().isEnabled() ? System.currentTimeMillis() : 0L;
try
{
if (pool.getRequestSemaphore().tryAcquire(pool.getConfiguration().getBlockingTimeout(), TimeUnit.MILLISECONDS))
{
Iterator<ConnectionListener> listenersIterator =
poolIsFifo ? listeners.iterator() : listeners.descendingIterator();
while (listenersIterator.hasNext())
{
ConnectionListener cl = listenersIterator.next();
if (cl.changeState(FREE, VALIDATION))
{
if (pool.getInternalStatistics().isEnabled())
pool.getInternalStatistics().deltaTotalBlockingTime(System.currentTimeMillis() - timestamp);
if (pool.getConfiguration().isValidateOnMatch())
{
ConnectionListener result = validateConnectionListener(listeners, cl, IN_USE);
if (result != null)
{
if (Tracer.isEnabled())
Tracer.getConnectionListener(pool.getConfiguration().getId(),
this, result, true, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
if (pool.getJanitor().isRecording())
pool.getJanitor().registerConnectionListener(result);
result.fromPool();
if (pool.getInternalStatistics().isEnabled())
{
pool.getInternalStatistics().deltaInUseCount(1);
pool.getInternalStatistics().deltaTotalGetTime(result.getFromPool() - timestamp);
pool.getInternalStatistics().deltaTotalPoolTime(result.getFromPool() - result.getToPool());
}
return result;
}
else
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(),
this, cl, false, false, true, false, false,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
if (pool.getConfiguration().isUseFastFail())
break;
}
}
else
{
if (cl.changeState(VALIDATION, IN_USE))
{
if (Tracer.isEnabled())
Tracer.getConnectionListener(pool.getConfiguration().getId(),
this, cl, true, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
if (pool.getJanitor().isRecording())
pool.getJanitor().registerConnectionListener(cl);
cl.fromPool();
if (pool.getInternalStatistics().isEnabled())
{
pool.getInternalStatistics().deltaInUseCount(1);
pool.getInternalStatistics().deltaTotalGetTime(cl.getFromPool() - timestamp);
pool.getInternalStatistics().deltaTotalPoolTime(cl.getFromPool() - cl.getToPool());
}
return cl;
}
else
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(),
this, cl, false, false, false, false, true,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
destroyAndRemoveConnectionListener(cl, listeners);
}
}
}
}
try
{
ConnectionListener cl = pool.createConnectionListener(credential, this);
cl.setState(IN_USE);
cl.fromPool();
listeners.addLast(cl);
if (Tracer.isEnabled())
Tracer.getConnectionListener(pool.getConfiguration().getId(),
this, cl, true, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
if (pool.getJanitor().isRecording())
pool.getJanitor().registerConnectionListener(cl);
return cl;
}
catch (ResourceException re)
{
throw re;
}
finally
{
prefill();
// Trigger capacity increase
if (pool.getCapacity().getIncrementer() != null)
CapacityFiller.schedule(new CapacityRequest(this));
}
}
else
{
if (pool.getInternalStatistics().isEnabled())
pool.getInternalStatistics().deltaWaitCount();
}
}
catch (Exception e)
{
// TODO
}
if (pool.getInternalStatistics().isEnabled())
pool.getInternalStatistics().deltaBlockingFailureCount();
throw new ResourceException("No ConnectionListener");
}
/**
* {@inheritDoc}
*/
public void returnConnectionListener(ConnectionListener cl, boolean kill) throws ResourceException
{
if (pool.getLogger().isTraceEnabled())
{
synchronized (this)
{
pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this,
"returnConnectionListener(" + Integer.toHexString(System.identityHashCode(cl)) + ", " + kill + ")",
pool.getConnectionManager().getManagedConnectionFactory(),
pool.getConnectionManager(),
pool, pool.getConfiguration(),
listeners, pool.getInternalStatistics(),
credential.getSubject(),
credential.getConnectionRequestInfo()));
}
}
else if (pool.getLogger().isDebugEnabled())
{
pool.getLogger().debug(ManagedConnectionPoolUtility.details(
"returnConnectionListener(" + Integer.toHexString(System.identityHashCode(cl)) + ", " + kill + ")",
pool.getConfiguration().getId(),
getCount(IN_USE, listeners),
pool.getConfiguration().getMaxSize()));
}
try
{
if (pool.getJanitor().isRecording())
pool.getJanitor().unregisterConnectionListener(cl);
if (cl.getState() != DESTROYED && pool.getInternalStatistics().isEnabled())
{
pool.getInternalStatistics().deltaInUseCount(-1);
pool.getInternalStatistics().deltaTotalUsageTime(System.currentTimeMillis() - cl.getFromPool());
}
if (!kill)
{
if (cl.changeState(IN_USE, TO_POOL))
{
try
{
cl.getManagedConnection().cleanup();
cl.toPool();
if (!cl.changeState(TO_POOL, FREE))
kill = true;
}
catch (ResourceException re)
{
kill = true;
}
}
else
{
kill = true;
}
}
if (kill && cl.getState() != DESTROYED)
{
try
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(),
this, cl, true, false, false, false, false,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
pool.destroyConnectionListener(cl);
}
finally
{
listeners.remove(cl);
}
}
}
finally
{
pool.getRequestSemaphore().release();
}
}
/**
* {@inheritDoc}
*/
public synchronized void shutdown()
{
if (pool.getConfiguration().isBackgroundValidation() &&
pool.getConfiguration().getBackgroundValidationMillis() > 0)
{
ConnectionValidator.getInstance().unregisterPool(this);
}
if (pool.getConfiguration().getIdleTimeoutMinutes() > 0)
{
IdleConnectionRemover.getInstance().unregisterPool(this);
}
for (ConnectionListener cl : listeners)
{
if (cl.getState() == IN_USE)
{
// TODO
}
else if (cl.getState() == DESTROY)
{
// TODO
}
try
{
if (Tracer.isEnabled())
Tracer.clearConnectionListener(pool.getConfiguration().getId(), this, cl);
pool.destroyConnectionListener(cl);
}
catch (ResourceException re)
{
// TODO
cl.setState(ZOMBIE);
}
}
listeners.clear();
}
/**
* Prefill
*/
@Override
public void prefill()
{
if (credential.equals(pool.getPrefillCredential()) &&
pool.getConfiguration().isPrefill() &&
pool.getConfiguration().getMinSize() > 0 &&
listeners.size() < pool.getConfiguration().getMinSize())
{
PoolFiller.fillPool(new FillRequest(this, pool.getConfiguration().getMinSize()));
}
}
/**
* Fill to
*
* @param size The size
*/
public void fillTo(int size)
{
if (size <= 0)
return;
if (pool.getLogger().isTraceEnabled())
{
synchronized (this)
{
pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this,
"fillTo(" + size + ")",
pool.getConnectionManager().getManagedConnectionFactory(),
pool.getConnectionManager(),
pool, pool.getConfiguration(),
listeners, pool.getInternalStatistics(),
credential.getSubject(),
credential.getConnectionRequestInfo()));
}
}
else if (pool.getLogger().isDebugEnabled())
{
pool.getLogger().debug(ManagedConnectionPoolUtility.details(
"fillTo(" + size + ")",
pool.getConfiguration().getId(),
getCount(IN_USE, listeners),
pool.getConfiguration().getMaxSize()));
}
while (!pool.isFull())
{
// Get a permit - avoids a race when the pool is nearly full
// Also avoids unnessary fill checking when all connections are checked out
try
{
//TODO:statistics
if (pool.getRequestSemaphore()
.tryAcquire(pool.getConfiguration().getBlockingTimeout(), TimeUnit.MILLISECONDS))
{
try
{
if (pool.isShutdown())
{
return;
}
// We already have enough connections
if (listeners.size() >= size)
{
return;
}
// Create a connection to fill the pool
try
{
ConnectionListener cl = pool.createConnectionListener(credential, this);
if (Tracer.isEnabled())
Tracer.createConnectionListener(pool.getConfiguration().getId(), this, cl,
cl.getManagedConnection(),
false, true, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
boolean added = false;
if (listeners.size() < size)
{
listeners.add(cl);
added = true;
}
if (!added)
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, false, false, false,
false, true, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
pool.destroyConnectionListener(cl);
return;
}
}
catch (ResourceException re)
{
return;
}
}
finally
{
pool.getRequestSemaphore().release();
}
}
}
catch (InterruptedException ignored)
{
Thread.interrupted();
//TODO:trace
}
}
}
/**
* {@inheritDoc}
*/
public void validateConnections()
{
boolean anyDestroyed = false;
ManagedConnectionFactory mcf = pool.getConnectionManager().getManagedConnectionFactory();
if (mcf instanceof ValidatingManagedConnectionFactory)
{
ValidatingManagedConnectionFactory vcf = (ValidatingManagedConnectionFactory) mcf;
long timestamp = System.currentTimeMillis();
for (ConnectionListener cl : listeners)
{
if (cl.changeState(FREE, VALIDATION))
{
if (cl.getValidated() + pool.getConfiguration().getBackgroundValidationMillis() <= timestamp)
{
ConnectionListener result = validateConnectionListener(listeners, cl, FREE);
if (result == null)
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, false, true,
false, false, false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
anyDestroyed = true;
}
}
else
{
if (!cl.changeState(VALIDATION, FREE))
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, false, false,
false, true, false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
destroyAndRemoveConnectionListener(cl, listeners);
}
}
}
}
}
else
{
log.validateOnMatchNonCompliantManagedConnectionFactory(mcf.getClass().getName());
}
if (anyDestroyed)
prefill();
}
/**
* {@inheritDoc}
*/
public void removeIdleConnections()
{
long now = System.currentTimeMillis();
long timeoutSetting = pool.getConfiguration().getIdleTimeoutMinutes() * 1000L * 60;
CapacityDecrementer decrementer = pool.getCapacity().getDecrementer();
if (decrementer == null || !credential.equals(pool.getPrefillCredential()))
{
decrementer = DefaultCapacity.DEFAULT_DECREMENTER;
}
if (TimedOutDecrementer.class.getName().equals(decrementer.getClass().getName()) ||
TimedOutFIFODecrementer.class.getName().equals(decrementer.getClass().getName()))
{
// Allow through each minute
if (now < (lastIdleCheck + 60000L))
return;
}
else
{
// Otherwise, strict check
if (now < (lastIdleCheck + timeoutSetting))
return;
}
lastIdleCheck = now;
long timeout = now - timeoutSetting;
int destroyed = 0;
if (pool.getLogger().isTraceEnabled())
{
synchronized (this)
{
pool.getLogger().trace(ManagedConnectionPoolUtility.fullDetails(this,
"removeIdleConnections(" + timeout + ")",
pool.getConnectionManager().getManagedConnectionFactory(),
pool.getConnectionManager(),
pool, pool.getConfiguration(),
listeners, pool.getInternalStatistics(),
credential.getSubject(),
credential.getConnectionRequestInfo()));
}
}
else if (pool.getLogger().isDebugEnabled())
{
pool.getLogger().debug(ManagedConnectionPoolUtility.details(
"removeIdleConnections(" + timeout + ")",
pool.getConfiguration().getId(),
getCount(IN_USE, listeners),
pool.getConfiguration().getMaxSize()));
}
for (ConnectionListener cl : listeners)
{
if (cl.changeState(FREE, VALIDATION))
{
if (decrementer
.shouldDestroy(cl, timeout, listeners.size(), pool.getConfiguration().getMinSize(), destroyed))
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, true, false, false, false,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
if (pool.getInternalStatistics().isEnabled())
pool.getInternalStatistics().deltaTimedOut();
destroyAndRemoveConnectionListener(cl, listeners);
destroyed++;
}
else
{
if (!cl.changeState(VALIDATION, FREE))
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, false, false, false, true,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
if (pool.getInternalStatistics().isEnabled())
pool.getInternalStatistics().deltaTimedOut();
destroyAndRemoveConnectionListener(cl, listeners);
destroyed++;
}
}
}
}
if (!pool.isShutdown())
{
boolean emptyManagedConnectionPool = false;
if (credential.equals(pool.getPrefillCredential()) && pool.getConfiguration().isPrefill())
{
if (pool.getConfiguration().getMinSize() > 0)
{
prefill();
}
else
{
emptyManagedConnectionPool = true;
}
}
else
{
emptyManagedConnectionPool = true;
}
// Empty pool
if (emptyManagedConnectionPool && listeners.isEmpty())
pool.emptyManagedConnectionPool(this);
}
}
/**
* {@inheritDoc}
*/
@Override
public void increaseCapacity()
{
//Only the prefill credential MCP should be affected by the incrementer
if (!credential.equals(pool.getPrefillCredential()))
return;
// We have already created one connection when this method is scheduled
int created = 1;
boolean create = true;
while (create && !pool.isFull())
{
try
{
long startWait = 0L;
if (pool.getRequestSemaphore()
.tryAcquire(pool.getConfiguration().getBlockingTimeout(), TimeUnit.MILLISECONDS))
{
try
{
if (pool.isShutdown())
{
return;
}
int currentSize = listeners.size();
create = pool.getCapacity().getIncrementer()
.shouldCreate(currentSize, pool.getConfiguration().getMaxSize(), created);
if (create)
{
try
{
ConnectionListener cl = pool.createConnectionListener(credential, this);
if (Tracer.isEnabled())
Tracer.createConnectionListener(pool.getConfiguration().getId(), this, cl,
cl.getManagedConnection(), false, false, true,
Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null);
boolean added = false;
if (listeners.size() < pool.getConfiguration().getMaxSize())
{
//if (trace)
// log.trace("Capacity fill: cl=" + cl);
listeners.add(cl);
created++;
added = true;
}
if (!added)
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl, false, false,
true, false, false, false, true,
Tracer.isRecordCallstacks() ? new Throwable("CALLSTACK") : null);
pool.destroyConnectionListener(cl);
return;
}
}
catch (ResourceException re)
{
//log.unableFillPool(re);
return;
}
}
}
finally
{
pool.getRequestSemaphore().release();
}
}
}
catch (InterruptedException ignored)
{
Thread.interrupted();
//if (trace)
// log.trace("Interrupted while requesting permit in increaseCapacity");
}
}
}
/**
* {@inheritDoc}
*/
public boolean isEmpty()
{
return listeners.isEmpty();
}
/**
* {@inheritDoc}
*/
public void flush(FlushMode mode)
{
// Destroy connections in the pool
for (ConnectionListener cl : listeners)
{
switch (mode)
{
case ALL:
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, false, false, true, false,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
destroyAndRemoveConnectionListener(cl, listeners);
break;
}
case INVALID:
{
if (cl.changeState(FREE, VALIDATION))
{
validateConnectionListener(listeners, cl, FREE);
}
break;
}
case IDLE:
{
if (cl.changeState(FREE, FLUSH))
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, false, false, true, false,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
destroyAndRemoveConnectionListener(cl, listeners);
}
break;
}
case GRACEFULLY:
{
if (cl.changeState(FREE, FLUSH))
{
if (Tracer.isEnabled())
Tracer.destroyConnectionListener(pool.getConfiguration().getId(), this, cl,
false, false, false, true, false,
false, false,
Tracer.isRecordCallstacks() ?
new Throwable("CALLSTACK") : null);
destroyAndRemoveConnectionListener(cl, listeners);
}
else if (cl.getState() == IN_USE || cl.getState() == TO_POOL || cl.getState() == VALIDATION)
{
cl.setState(DESTROY);
}
break;
}
}
}
// Trigger prefill
prefill();
}
/**
* {@inheritDoc}
*/
public ConnectionListener findConnectionListener(ManagedConnection mc, Object c)
{
return findConnectionListener(mc, c, listeners);
}
/**
* {@inheritDoc}
*/
public ConnectionListener removeConnectionListener(boolean free)
{
return removeConnectionListener(free, listeners);
}
}