bitronix.tm.resource.common.XAPool Maven / Gradle / Ivy
/*
* Bitronix Transaction Manager
*
* Copyright (c) 2010, Bitronix Software.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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 GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package bitronix.tm.resource.common;
import java.util.*;
import javax.transaction.Synchronization;
import javax.transaction.xa.XAResource;
import org.slf4j.*;
import bitronix.tm.*;
import bitronix.tm.internal.*;
import bitronix.tm.recovery.*;
import bitronix.tm.utils.*;
import bitronix.tm.utils.CryptoEngine;
/**
* Generic XA pool. {@link XAStatefulHolder} instances are created by the {@link XAPool} out of a
* {@link XAResourceProducer}. Those objects are then pooled and can be retrieved and/or recycled by the pool
* depending on the running XA transaction's and the {@link XAStatefulHolder}'s states.
*
* @author lorban
*/
public class XAPool implements StateChangeListener {
private final static Logger log = LoggerFactory.getLogger(XAPool.class);
private final static String PASSWORD_PROPERTY_NAME = "password";
private final Map> statefulHolderTransactionMap = new HashMap>();
private final List objects = new ArrayList();
private final ResourceBean bean;
private final XAResourceProducer xaResourceProducer;
private final Object xaFactory;
private boolean failed = false;
public XAPool(XAResourceProducer xaResourceProducer, ResourceBean bean) throws Exception {
this.xaResourceProducer = xaResourceProducer;
this.bean = bean;
if (bean.getMaxPoolSize() < 1 || bean.getMinPoolSize() > bean.getMaxPoolSize())
throw new IllegalArgumentException("cannot create a pool with min " + bean.getMinPoolSize() + " connection(s) and max " + bean.getMaxPoolSize() + " connection(s)");
if (bean.getAcquireIncrement() < 1)
throw new IllegalArgumentException("cannot create a pool with a connection acquisition increment less than 1, configured value is " + bean.getAcquireIncrement());
xaFactory = createXAFactory(bean);
init();
if (bean.getIgnoreRecoveryFailures())
log.warn("resource '" + bean.getUniqueName() + "' is configured to ignore recovery failures, make sure this setting is not enabled on a production system!");
}
private synchronized void init() throws Exception {
growUntilMinPoolSize();
if (bean.getMaxIdleTime() > 0) {
TransactionManagerServices.getTaskScheduler().schedulePoolShrinking(this);
}
}
public Object getXAFactory() {
return xaFactory;
}
public synchronized void setFailed(boolean failed) {
this.failed = failed;
}
public synchronized boolean isFailed() {
return failed;
}
public synchronized Object getConnectionHandle() throws Exception {
return getConnectionHandle(true);
}
public synchronized Object getConnectionHandle(boolean recycle) throws Exception {
if (failed) {
try {
if (log.isDebugEnabled()) log.debug("resource '" + bean.getUniqueName() + "' is marked as failed, resetting and recovering it before trying connection acquisition");
close();
init();
IncrementalRecoverer.recover(xaResourceProducer);
}
catch (RecoveryException ex) {
throw new BitronixRuntimeException("incremental recovery failed when trying to acquire a connection from failed resource '" + bean.getUniqueName() + "'", ex);
}
catch (Exception ex) {
throw new BitronixRuntimeException("pool reset failed when trying to acquire a connection from failed resource '" + bean.getUniqueName() + "'", ex);
}
}
long remainingTime = bean.getAcquisitionTimeout() * 1000L;
while (true) {
long before = MonotonicClock.currentTimeMillis();
XAStatefulHolder xaStatefulHolder = null;
if (recycle) {
if (bean.getShareTransactionConnections()) {
xaStatefulHolder = getSharedXAStatefulHolder();
}
else {
xaStatefulHolder = getNotAccessible();
}
}
if (xaStatefulHolder == null) {
xaStatefulHolder = getInPool();
}
if (log.isDebugEnabled()) log.debug("found " + Decoder.decodeXAStatefulHolderState(xaStatefulHolder.getState()) + " connection " + xaStatefulHolder + " from " + this);
try {
// getConnection() here could throw an exception, if it doesn't the connection is
// still alive and we can share it (if sharing is enabled)
Object connectionHandle = xaStatefulHolder.getConnectionHandle();
if (bean.getShareTransactionConnections()) {
putSharedXAStatefulHolder(xaStatefulHolder);
}
growUntilMinPoolSize();
return connectionHandle;
} catch (Exception ex) {
if (log.isDebugEnabled()) log.debug("connection is invalid, trying to close it", ex);
try {
xaStatefulHolder.close();
} catch (Exception ex2) {
if (log.isDebugEnabled()) log.debug("exception while trying to close invalid connection, ignoring it", ex2);
}
objects.remove(xaStatefulHolder);
if (log.isDebugEnabled()) log.debug("removed invalid connection " + xaStatefulHolder + " from " + this);
if (log.isDebugEnabled()) log.debug("waiting " + bean.getAcquisitionInterval() + "s before trying to acquire a connection again from " + this);
long waitTime = bean.getAcquisitionInterval() * 1000L;
if (waitTime > 0) {
try {
wait(waitTime);
} catch (InterruptedException ex2) {
// ignore
}
}
// check for timeout
long now = MonotonicClock.currentTimeMillis();
remainingTime -= (now - before);
if (remainingTime <= 0) {
throw new BitronixRuntimeException("cannot get valid connection from " + this + " after trying for " + bean.getAcquisitionTimeout() + "s", ex);
}
}
} // while true
}
public synchronized void close() {
if (log.isDebugEnabled()) log.debug("closing all connections of " + this);
for (XAStatefulHolder xaStatefulHolder : objects) {
try {
// This change is unrelated to BTM-35, but suppresses noise in the unit test output.
// Connections that are already in STATE_CLOSED should not be closed again.
if (xaStatefulHolder.getState() != XAStatefulHolder.STATE_CLOSED) {
xaStatefulHolder.close();
}
} catch (Exception ex) {
if (log.isDebugEnabled()) log.debug("ignoring exception while closing connection " + xaStatefulHolder, ex);
}
}
if (TransactionManagerServices.isTaskSchedulerRunning())
TransactionManagerServices.getTaskScheduler().cancelPoolShrinking(this);
objects.clear();
failed = false;
}
public synchronized long totalPoolSize() {
return objects.size();
}
public synchronized long inPoolSize() {
int count = 0;
for (XAStatefulHolder xaStatefulHolder : objects) {
if (xaStatefulHolder.getState() == XAStatefulHolder.STATE_IN_POOL)
count++;
}
return count;
}
public void stateChanged(XAStatefulHolder source, int oldState, int newState) {
if (newState == XAStatefulHolder.STATE_IN_POOL) {
if (log.isDebugEnabled()) log.debug("a connection's state changed to IN_POOL, notifying a thread eventually waiting for a connection");
synchronized (this) {
notify();
}
}
}
public void stateChanging(XAStatefulHolder source, int currentState, int futureState) {
}
public synchronized XAResourceHolder findXAResourceHolder(XAResource xaResource) {
for (XAStatefulHolder xaStatefulHolder : objects) {
List xaResourceHolders = xaStatefulHolder.getXAResourceHolders();
for (XAResourceHolder holder : xaResourceHolders) {
if (holder.getXAResource() == xaResource)
return holder;
}
}
return null;
}
// used for testing
List getXAResourceHolders() {
return Collections.unmodifiableList(objects);
}
public Date getNextShrinkDate() {
return new Date(MonotonicClock.currentTimeMillis() + bean.getMaxIdleTime() * 1000);
}
public synchronized void shrink() throws Exception {
if (log.isDebugEnabled()) log.debug("shrinking " + this);
List toRemoveXaStatefulHolders = new ArrayList();
long now = MonotonicClock.currentTimeMillis();
for (XAStatefulHolder xaStatefulHolder : objects) {
if (xaStatefulHolder.getState() != XAStatefulHolder.STATE_IN_POOL)
continue;
long expirationTime = (xaStatefulHolder.getLastReleaseDate().getTime() + (bean.getMaxIdleTime() * 1000));
if (log.isDebugEnabled()) log.debug("checking if connection can be closed: " + xaStatefulHolder + " - closing time: " + expirationTime + ", now time: " + now);
if (expirationTime <= now) {
try {
xaStatefulHolder.close();
} catch (Exception ex) {
log.warn("error closing " + xaStatefulHolder, ex);
}
toRemoveXaStatefulHolders.add(xaStatefulHolder);
}
} // for
if (log.isDebugEnabled()) log.debug("closed " + toRemoveXaStatefulHolders.size() + " idle connection(s)");
objects.removeAll(toRemoveXaStatefulHolders);
growUntilMinPoolSize();
if (log.isDebugEnabled()) log.debug("shrunk " + this);
}
public synchronized void reset() throws Exception {
if (log.isDebugEnabled()) log.debug("resetting " + this);
List toRemoveXaStatefulHolders = new ArrayList();
for (XAStatefulHolder xaStatefulHolder : objects) {
if (xaStatefulHolder.getState() != XAStatefulHolder.STATE_IN_POOL)
continue;
try {
xaStatefulHolder.close();
} catch (Exception ex) {
log.warn("error closing " + xaStatefulHolder, ex);
}
toRemoveXaStatefulHolders.add(xaStatefulHolder);
}
if (log.isDebugEnabled()) log.debug("closed " + toRemoveXaStatefulHolders.size() + " connection(s)");
objects.removeAll(toRemoveXaStatefulHolders);
growUntilMinPoolSize();
if (log.isDebugEnabled()) log.debug("reset " + this);
}
public String toString() {
return "an XAPool of resource " + bean.getUniqueName() + " with " + totalPoolSize() + " connection(s) (" + inPoolSize() + " still available)" + (failed ? " -failed-" : "");
}
private synchronized void createPooledObject(Object xaFactory) throws Exception {
XAStatefulHolder xaStatefulHolder = xaResourceProducer.createPooledConnection(xaFactory, bean);
xaStatefulHolder.addStateChangeEventListener(this);
objects.add(xaStatefulHolder);
}
private static Object createXAFactory(ResourceBean bean) throws Exception {
String className = bean.getClassName();
if (className == null)
throw new IllegalArgumentException("className cannot be null");
Class xaFactoryClass = ClassLoaderUtils.loadClass(className);
Object xaFactory = xaFactoryClass.newInstance();
for (Map.Entry
© 2015 - 2024 Weber Informatics LLC | Privacy Policy