Package bitronix.tm.resource.common

Source Code of bitronix.tm.resource.common.LocalVisitor

/*
* Copyright (C) 2006-2013 Bitronix Software (http://www.bitronix.be)
*
* 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.
*/
package bitronix.tm.resource.common;

import bitronix.tm.BitronixTransaction;
import bitronix.tm.BitronixXid;
import bitronix.tm.TransactionManagerServices;
import bitronix.tm.internal.BitronixRuntimeException;
import bitronix.tm.internal.XAResourceHolderState;
import bitronix.tm.recovery.IncrementalRecoverer;
import bitronix.tm.recovery.RecoveryException;
import bitronix.tm.utils.Decoder;
import bitronix.tm.utils.MonotonicClock;
import bitronix.tm.utils.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.transaction.Synchronization;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* 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 Ludovic Orban
* @author Brett Wooldridge
*/
public class XAPool implements StateChangeListener {

    private final static Logger log = LoggerFactory.getLogger(XAPool.class);

    /**
     * The stateTransitionLock makes sure that transitions of XAStatefulHolders from one state to another
     * (movement from one pool to another) are atomic.  A ReentrantReadWriteLock allows any number of
     * readers to access and iterate the accessiblePool and inaccessiblePool without blocking.  Readers
     * are blocked only for the instant when a connection is moving between pools.  These locks are
     * sufficient to protect the collections, which are left intentionally non-concurrent so that failures
     * in locking logic will be quickly uncovered.
     */
    private final ReentrantReadWriteLock stateTransitionLock = new ReentrantReadWriteLock();

    private final BlockingQueue<XAStatefulHolder> availablePool = new LinkedBlockingQueue<XAStatefulHolder>();
    private final Queue<XAStatefulHolder> accessiblePool = new LinkedList<XAStatefulHolder>();
    private final Queue<XAStatefulHolder> inaccessiblePool = new LinkedList<XAStatefulHolder>();

    private final AtomicInteger poolSize = new AtomicInteger();

    /**
     * This map is used to implement the connection sharing feature of Bitronix.
     */
    private final Map<Uid, StatefulHolderThreadLocal> statefulHolderTransactionMap = new ConcurrentHashMap<Uid, StatefulHolderThreadLocal>();

    private final ResourceBean bean;
    private final XAResourceProducer xaResourceProducer;
    private final Object xaFactory;
    private final AtomicBoolean failed = new AtomicBoolean();
    private final Object poolGrowthShrinkLock = new Object();

    public XAPool(XAResourceProducer xaResourceProducer, ResourceBean bean, Object xaFactory) 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());

        if (xaFactory == null) {
            this.xaFactory = XAFactoryHelper.createXAFactory(bean);
        } else {
            this.xaFactory = xaFactory;
        }
        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 void init() throws Exception {
        growUntilMinPoolSize();

        if (bean.getMaxIdleTime() > 0 || bean.getMaxLifeTime() > 0) {
            TransactionManagerServices.getTaskScheduler().schedulePoolShrinking(this);
        }
    }

    /**
     * Close down and cleanup this XAPool instance.
     */
    public void close() {
        synchronized (poolGrowthShrinkLock) {
            if (log.isDebugEnabled()) { log.debug("closing all connections of " + this); }
           
            for (XAStatefulHolder xaStatefulHolder : getXAResourceHolders()) {
                try {
                    xaStatefulHolder.close();
                } catch (Exception ex) {
                    if (log.isDebugEnabled()) { log.debug("ignoring exception while closing connection " + xaStatefulHolder, ex); }
                }
            }
   
            if (TransactionManagerServices.isTaskSchedulerRunning())
                TransactionManagerServices.getTaskScheduler().cancelPoolShrinking(this);
   
            stateTransitionLock.writeLock().lock();
            try {
                availablePool.clear();
                accessiblePool.clear();
                inaccessiblePool.clear();
                failed.set(false);
            }
            finally {
                stateTransitionLock.writeLock().unlock();
            }
        }
    }

    /**
     * Get a connection handle from this pool.
     *
     * @return a connection handle
     * @throws Exception throw in the pool is unrecoverable or a timeout occurs getting a connection
     */
    public Object getConnectionHandle() throws Exception {
        return getConnectionHandle(true);
    }
   
    /**
     * Get a connection handle from this pool.
     *
     * @param recycle true if we should try to get a connection in the NON_ACCESSIBLE pool in the same transaction
     * @return a connection handle
     * @throws Exception throw in the pool is unrecoverable or a timeout occurs getting a connection
     */
    public Object getConnectionHandle(boolean recycle) throws Exception {
        synchronized (poolGrowthShrinkLock) {
            if (isFailed()) {
                reinitializePool();
            }
        }

        long remainingTimeMs = TimeUnit.SECONDS.toMillis(bean.getAcquisitionTimeout());
        while (true) {
            long before = MonotonicClock.currentTimeMillis();
            XAStatefulHolder xaStatefulHolder = null;
            if (recycle) {
                if (bean.getShareTransactionConnections()) {
                    xaStatefulHolder = getSharedXAStatefulHolder();
                }
                else {
                    xaStatefulHolder = getNotAccessible();
                }
            }

            if (xaStatefulHolder == null) {
                xaStatefulHolder = getInPool(remainingTimeMs);
            }

            if (log.isDebugEnabled()) { log.debug("found " + Decoder.decodeXAStatefulHolderState(xaStatefulHolder.getState()) + " connection " + xaStatefulHolder + " from " + this); }

            try {
                // getConnectionHandle() 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);
                }

                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); }
                }
                finally {                   
                    if (log.isDebugEnabled()) { log.debug("removed invalid connection " + xaStatefulHolder + " from " + this); }
                    if (xaStatefulHolder.getState() != XAStatefulHolder.STATE_CLOSED) {
                        stateChanged(xaStatefulHolder, xaStatefulHolder.getState(), XAStatefulHolder.STATE_CLOSED);
                    }

                    if (log.isDebugEnabled()) { log.debug("waiting " + bean.getAcquisitionInterval() + "s before trying to acquire a connection again from " + this); }
                    long waitTime = TimeUnit.SECONDS.toMillis(bean.getAcquisitionInterval());
                    if (waitTime > 0) {
                        try {
                            Thread.sleep(waitTime);
                        } catch (InterruptedException ex2) {
                            // ignore
                        }
                    }
                }

                // check for timeout
                long now = MonotonicClock.currentTimeMillis();
                remainingTimeMs -= (now - before);
                before = now;
                if (remainingTimeMs <= 0) {
                    throw new BitronixRuntimeException("cannot get valid connection from " + this + " after trying for " + bean.getAcquisitionTimeout() + "s", ex);
                }
            }
        } // while true
    }

    /* -----------------------------------------------------------------------------------
     * Pool Transition.  stateChanging() and stateChanged() obtain the stateTransitionLock
     * write lock to prevent other threads from iterating the pools while a coonection is
     * in transition from one pool to another.  However, this window of time is extremely
     * small, and in general allows high-concurrency.
     * ----------------------------------------------------------------------------------*/

    public void stateChanging(XAStatefulHolder source, int currentState, int futureState) {
        stateTransitionLock.writeLock().lock();
        try {
            switch (currentState) {
            case XAStatefulHolder.STATE_IN_POOL:
              // no-op.  calling availablePool.remove(source) here is reduncant because it was
              // already removed when availablePool.poll() was called.
                break;
            case XAStatefulHolder.STATE_ACCESSIBLE:
                if (log.isDebugEnabled()) { log.debug("removed " + source + " from the accessible pool"); }
                accessiblePool.remove(source);
                break;
            case XAStatefulHolder.STATE_NOT_ACCESSIBLE:
                if (log.isDebugEnabled()) { log.debug("removed " + source + " from the inaccessible pool"); }
                inaccessiblePool.remove(source);
                break;
            case XAStatefulHolder.STATE_CLOSED:
                break;
            }
        }
        finally {
            stateTransitionLock.writeLock().unlock();
        }
    }

    public void stateChanged(XAStatefulHolder source, int oldState, int newState) {
        stateTransitionLock.writeLock().lock();
        try {
          switch (newState) {
          case XAStatefulHolder.STATE_IN_POOL:
                if (log.isDebugEnabled()) { log.debug("added " + source + " to the available pool"); }
                availablePool.add(source);
            break;
          case XAStatefulHolder.STATE_ACCESSIBLE:
            if (log.isDebugEnabled()) { log.debug("added " + source + " to the accessible pool"); }
            accessiblePool.add(source);
            break;
          case XAStatefulHolder.STATE_NOT_ACCESSIBLE:
            if (log.isDebugEnabled()) { log.debug("added " + source + " to the inaccessible pool"); }
            inaccessiblePool.add(source);
            break;
          case XAStatefulHolder.STATE_CLOSED:
                source.removeStateChangeEventListener(this);
                poolSize.decrementAndGet();
            break;
          }
        }
        finally {
            stateTransitionLock.writeLock().unlock();
        }
    }

    /* ------------------------------------------------------------------------
     * Methods to obtain a connection from one of the internal pools.
     * ------------------------------------------------------------------------*/

    /**
     * Get an IN_POOL connection.  This method blocks for up to remainingTimeMs milliseconds
     * for someone to return or create a connection in the available pool.  If remainingTimeMs
     * expires, an exception is thrown.  It does not use stateTransitionLock.readLock() because
     * the availablePool [a LinkedBlockingQueue] is already thread safe.
     *
     * @param remainingTimeMs the maximum time to wait for a connection
     * @return a connection from the available (IN_POOL) pool
     * @throws Exception thrown in no connection is available before the remainingTimeMs time expires
     */
    private XAStatefulHolder getInPool(long remainingTimeMs) throws Exception {
        if (inPoolSize() == 0) {
            if (log.isDebugEnabled()) { log.debug("no more free connections in " + this + ", trying to grow it"); }
            grow();
        }

        if (log.isDebugEnabled()) { log.debug("getting IN_POOL connection from " + this + ", waiting if necessary"); }

        try {
            XAStatefulHolder xaStatefulHolder = availablePool.poll(remainingTimeMs, TimeUnit.MILLISECONDS);
            if (xaStatefulHolder == null) {
                if (TransactionManagerServices.isTransactionManagerRunning())
                    TransactionManagerServices.getTransactionManager().dumpTransactionContexts();
               
                throw new BitronixRuntimeException("XA pool of resource " + bean.getUniqueName() + " still empty after " + bean.getAcquisitionTimeout() + "s wait time");
            }

            if (expireStatefulHolder(xaStatefulHolder, false)) {
                return getInPool(remainingTimeMs);
            }

            return xaStatefulHolder;
        } catch (InterruptedException e) {
            throw new BitronixRuntimeException("Interrupted while waiting for IN_POOL connection.");
        }
    }

    /**
     * Get a XAStatefulHolder (connection) from the NOT_ACCESSIBLE pool.  This method obtains
     * the stateTransitionLock.readLock() which prevents any modification during iteration, but
     * allows multiple threads to iterate simultaneously.
     *
     * @return a connection, or null if there are no connections in the inaccessible pool for the current transaction
     */
    private XAStatefulHolder getNotAccessible() {
        if (log.isDebugEnabled()) { log.debug("trying to recycle a NOT_ACCESSIBLE connection of " + this); }
        BitronixTransaction transaction = TransactionContextHelper.currentTransaction();
        if (transaction == null) {
            if (log.isDebugEnabled()) { log.debug("no current transaction, no connection can be in state NOT_ACCESSIBLE when there is no global transaction context"); }
            return null;
        }
        Uid currentTxGtrid = transaction.getResourceManager().getGtrid();
        if (log.isDebugEnabled()) { log.debug("current transaction GTRID is [" + currentTxGtrid + "]"); }

        stateTransitionLock.readLock().lock();
        try {
            for (XAStatefulHolder xaStatefulHolder : inaccessiblePool) {
                if (log.isDebugEnabled()) { log.debug("found a connection in NOT_ACCESSIBLE state: " + xaStatefulHolder); }
                if (containsXAResourceHolderMatchingGtrid(xaStatefulHolder, currentTxGtrid))
                    return xaStatefulHolder;
            }
   
            if (log.isDebugEnabled()) { log.debug("no NOT_ACCESSIBLE connection enlisted in this transaction"); }
            return null;
        }
        finally {
            stateTransitionLock.readLock().unlock();
        }
    }

    /**
     * Try to get a shared XAStatefulHolder.  This method will either return a shared
     * XAStatefulHolder or <code>null</code>.  If there is no current transaction,
     * XAStatefulHolder's are not shared.  If there is a transaction <i>and</i> there is
     * a XAStatefulHolder associated with this thread already, we return that XAStatefulHolder
     * (provided it is ACCESSIBLE or NOT_ACCESSIBLE).
     *
     * @return a shared XAStatefulHolder or <code>null</code>
     */
    private XAStatefulHolder getSharedXAStatefulHolder() {
        BitronixTransaction transaction = TransactionContextHelper.currentTransaction();
        if (transaction == null) {
            if (log.isDebugEnabled()) { log.debug("no current transaction, shared connection map will not be used"); }
            return null;
        }
        Uid currentTxGtrid = transaction.getResourceManager().getGtrid();

        StatefulHolderThreadLocal threadLocal = statefulHolderTransactionMap.get(currentTxGtrid);
        if (threadLocal != null) {
            XAStatefulHolder xaStatefulHolder = (XAStatefulHolder) threadLocal.get();
            // Additional sanity checks...
            if (xaStatefulHolder != null &&
                xaStatefulHolder.getState() != XAStatefulHolder.STATE_IN_POOL &&
                xaStatefulHolder.getState() != XAStatefulHolder.STATE_CLOSED) {

                if (log.isDebugEnabled()) { log.debug("sharing connection " + xaStatefulHolder + " in transaction " + currentTxGtrid); }
                return xaStatefulHolder;
            }
        }

        return null;
    }

    private boolean containsXAResourceHolderMatchingGtrid(XAStatefulHolder xaStatefulHolder, final Uid currentTxGtrid) {
        List<XAResourceHolder> xaResourceHolders = xaStatefulHolder.getXAResourceHolders();
        if (log.isDebugEnabled()) { log.debug(xaResourceHolders.size() + " xa resource(s) created by connection in NOT_ACCESSIBLE state: " + xaStatefulHolder); }

        class LocalVisitor implements XAResourceHolderStateVisitor {
            private boolean found;
            public boolean visit(XAResourceHolderState xaResourceHolderState) {
                // compare GTRIDs
                BitronixXid bitronixXid = xaResourceHolderState.getXid();
                Uid resourceGtrid = bitronixXid.getGlobalTransactionIdUid();
                if (log.isDebugEnabled()) { log.debug("NOT_ACCESSIBLE xa resource GTRID: " + resourceGtrid); }
                if (currentTxGtrid.equals(resourceGtrid)) {
                    if (log.isDebugEnabled()) { log.debug("NOT_ACCESSIBLE xa resource's GTRID matched this transaction's GTRID, recycling it"); }
                    found = true;
                }
                return !found; // continue visitation if not found, stop visitation if found
            }
        }

        for (XAResourceHolder xaResourceHolder : xaResourceHolders) {
            LocalVisitor xaResourceHolderStateVisitor = new LocalVisitor();
            xaResourceHolder.acceptVisitorForXAResourceHolderStates(currentTxGtrid, xaResourceHolderStateVisitor);
            if (xaResourceHolderStateVisitor.found) {
              return true;
            }
        }
        return false;
    }

    /* ------------------------------------------------------------------------
     * Pool growth and pooled object creation
     * ------------------------------------------------------------------------*/

    /**
     * Grow the pool by "acquire increment" amount up to the max pool size.
     *
     * @throws Exception thrown if creating a pooled objects fails
     */
    private void grow() throws Exception {
        synchronized (poolGrowthShrinkLock) {
          final long totalPoolSize = totalPoolSize();
            if (totalPoolSize < bean.getMaxPoolSize()) {
                long increment = bean.getAcquireIncrement();
                if (totalPoolSize + increment > bean.getMaxPoolSize()) {
                    increment = bean.getMaxPoolSize() - totalPoolSize;
                }
   
                if (log.isDebugEnabled()) { log.debug("incrementing " + bean.getUniqueName() + " pool size by " + increment + " unit(s) to reach " + (totalPoolSize() + increment) + " connection(s)"); }
                for (int i=0; i < increment ;i++) {
                    createPooledObject(xaFactory);
                }
            }
            else {
                if (log.isDebugEnabled()) { log.debug("pool " + bean.getUniqueName() + " already at max size of " + totalPoolSize() + " connection(s), not growing it"); }
            }

            if (totalPoolSize() < bean.getMinPoolSize()) {
              growUntilMinPoolSize();
            }
        }
    }

    private void growUntilMinPoolSize() throws Exception {
        synchronized (poolGrowthShrinkLock) {
            if (log.isDebugEnabled()) { log.debug("growing " + this + " to minimum pool size " + bean.getMinPoolSize()); }
            for (int i = totalPoolSize(); i < bean.getMinPoolSize(); i++) {
                createPooledObject(xaFactory);
            }
        }
    }

    private void createPooledObject(Object xaFactory) throws Exception {
        XAStatefulHolder xaStatefulHolder = xaResourceProducer.createPooledConnection(xaFactory, bean);
        xaStatefulHolder.addStateChangeEventListener(this);
        availablePool.add(xaStatefulHolder);
        poolSize.incrementAndGet();
    }

    /* ------------------------------------------------------------------------
     * Pool shrinking and pooled object expiration.
     * ------------------------------------------------------------------------*/

    public Date getNextShrinkDate() {
        return new Date(MonotonicClock.currentTimeMillis() + TimeUnit.SECONDS.toMillis(bean.getMaxIdleTime()));
    }
   
    public void shrink() throws Exception {
        synchronized (poolGrowthShrinkLock) {
            if (log.isDebugEnabled()) { log.debug("shrinking " + this); }
            expireOrCloseStatefulHolders(false);
            if (log.isDebugEnabled()) { log.debug("shrunk " + this); }
        }
    }

    public void reset() throws Exception {
        synchronized (poolGrowthShrinkLock) {
            if (log.isDebugEnabled()) { log.debug("resetting " + this); }
            expireOrCloseStatefulHolders(true);
            if (log.isDebugEnabled()) { log.debug("reset " + this); }
        }
    }

    private void expireOrCloseStatefulHolders(boolean forceClose) throws Exception {
        int closed = 0;
        final int availableSize = availablePool.size();
        for (int i = 0; i < availableSize; i++) {
            XAStatefulHolder xaStatefulHolder = availablePool.poll();
            if (xaStatefulHolder == null) {
                break;
            }

            if (expireStatefulHolder(xaStatefulHolder, forceClose)) {
                closed++;
            } else {
                availablePool.add(xaStatefulHolder);
            }
        }

        if (log.isDebugEnabled()) { log.debug("closed " + closed + (forceClose ? " " : " idle ") + "connection(s)"); }

        growUntilMinPoolSize();
    }

    private boolean expireStatefulHolder(XAStatefulHolder xaStatefulHolder, boolean forceClose) {
        long expirationTime = Long.MAX_VALUE;
        if (bean.getMaxIdleTime() > 0) {
            expirationTime = xaStatefulHolder.getLastReleaseDate().getTime() + TimeUnit.SECONDS.toMillis(bean.getMaxIdleTime());
        }

        if (bean.getMaxLifeTime() > 0) {
            long endOfLife = xaStatefulHolder.getCreationDate().getTime() + TimeUnit.SECONDS.toMillis(bean.getMaxLifeTime());
            expirationTime = Math.min(expirationTime, endOfLife);
        }

        final long now = MonotonicClock.currentTimeMillis();
        if (!forceClose && log.isDebugEnabled()) { log.debug("checking if connection can be closed: " + xaStatefulHolder + " - closing time: " + expirationTime + ", now time: " + now); }
        if (expirationTime <= now || forceClose) {
            try {
                xaStatefulHolder.close();
            } catch (Exception ex) {
                log.warn("error closing " + xaStatefulHolder, ex);
            }
            return true;
        }
        return false;
    }

    private void reinitializePool() {
        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);
        }
    }

    /* ------------------------------------------------------------------------
     * Miscellaneous public methods
     * ------------------------------------------------------------------------*/

    /**
     * Get the XAFactory (XADataSource) that produces objects for this pool.
     *
     * @return the factory (XADataSource) object
     */
    public Object getXAFactory() {
        return xaFactory;
    }

    /**
     * Sets this XAPool as failed or unfailed, requiring recovery.
     *
     * @param failed true if this XAPool has failed and requires recovery, false if it is ok
     */
    public void setFailed(boolean failed) {
        this.failed.set(failed);
    }

    /**
     * Is the XAPool in a failed state?
     *
     * @return true if this XAPool has failed, false otherwise
     */
    public boolean isFailed() {
        return failed.get();
    }

    /**
     * Get the total size of this pool.
     *
     * @return the total size of this pool
     */
    public int totalPoolSize() {
        return poolSize.get();
    }

    /**
     * Get the number of objects in the available pool.
     *
     * @return the number of available objects
     */
    public int inPoolSize() {
        return availablePool.size();
    }

    public List<XAStatefulHolder> getXAResourceHolders() {
        stateTransitionLock.readLock().lock();
        try {
            List<XAStatefulHolder> holders = new ArrayList<XAStatefulHolder>();
            holders.addAll(availablePool);
            holders.addAll(accessiblePool);
            holders.addAll(inaccessiblePool);
            return holders;
        }
        finally {
            stateTransitionLock.readLock().unlock();
        }
    }

    public String toString() {
        return "an XAPool of resource " + bean.getUniqueName() + " with " + totalPoolSize() + " connection(s) (" + inPoolSize() + " still available)" + (isFailed() ? " -failed-" : "");
    }

    /* ------------------------------------------------------------------------
     * Shared Connection Handling
     * ------------------------------------------------------------------------*/

    /**
     * Try to share a XAStatefulHolder with other callers on this thread.  If
     * there is no current transaction, the XAStatefulHolder is not put into the
     * ThreadLocal.  If there is a transaction, and it is the first time we're
     * attempting to share a XAStatefulHolder on this thread, then we register
     * a Synchronization so we can pull the ThreadLocals out of the shared map
     * when the transaction completes (either commit() or rollback()).  Without
     * the Synchronization we would "leak".
     *
     * @param xaStatefulHolder a XAStatefulHolder to share with other callers
     *    on this thread.
     */
    private void putSharedXAStatefulHolder(final XAStatefulHolder xaStatefulHolder) {
        BitronixTransaction transaction = TransactionContextHelper.currentTransaction();
        if (transaction == null) {
            if (log.isDebugEnabled()) { log.debug("no current transaction, not adding " + xaStatefulHolder + " to shared connection map"); }
            return;
        }
        final Uid currentTxGtrid = transaction.getResourceManager().getGtrid();

        StatefulHolderThreadLocal threadLocal = statefulHolderTransactionMap.get(currentTxGtrid);
        if (threadLocal == null) {
            // This is the first time this TxGtrid/ThreadLocal is going into the map,
            // register interest in synchronization so we can remove it at commit/rollback
            try {
                transaction.registerSynchronization(new SharedStatefulHolderCleanupSynchronization(currentTxGtrid));
            } catch (Exception e) {
                // OK, forget it.  The transaction is either rollback only or already finished.
                return;
            }

            threadLocal = new StatefulHolderThreadLocal();
            statefulHolderTransactionMap.put(currentTxGtrid, threadLocal);
            if (log.isDebugEnabled()) { log.debug("added shared connection mapping for " + currentTxGtrid + " holder " + xaStatefulHolder); }
        }

        // Set the XAStatefulHolder on the ThreadLocal.  Even if we've already set it before,
        // it's safe -- checking would be more expensive than just setting it again.
        threadLocal.set(xaStatefulHolder);
    }

    private final class SharedStatefulHolderCleanupSynchronization implements Synchronization {
        private final Uid gtrid;

        private SharedStatefulHolderCleanupSynchronization(Uid gtrid) {
            this.gtrid = gtrid;
        }

        public void beforeCompletion() {
        }

        public void afterCompletion(int status) {
            statefulHolderTransactionMap.remove(gtrid);
            if (log.isDebugEnabled()) { log.debug("deleted shared connection mappings for " + gtrid); }
        }

        public String toString() {
            return "a SharedStatefulHolderCleanupSynchronization with GTRID [" + gtrid + "]";
        }
    }

    private static final class StatefulHolderThreadLocal extends ThreadLocal<XAStatefulHolder> {
        @Override
      public XAStatefulHolder get() {
        return super.get();
      }

      @Override
      public void set(XAStatefulHolder value) {
        super.set(value);
      }
    }
}
TOP

Related Classes of bitronix.tm.resource.common.LocalVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.