Package bitronix.tm

Source Code of bitronix.tm.BitronixTransactionManager$ClearContextSynchronization

/*
* 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;

import bitronix.tm.internal.BitronixSystemException;
import bitronix.tm.internal.ThreadContext;
import bitronix.tm.internal.XAResourceManager;
import bitronix.tm.utils.ClassLoaderUtils;
import bitronix.tm.utils.Decoder;
import bitronix.tm.utils.InitializationException;
import bitronix.tm.utils.MonotonicClock;
import bitronix.tm.utils.Scheduler;
import bitronix.tm.utils.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;

/**
* Implementation of {@link TransactionManager} and {@link UserTransaction}.
*
* @author Ludovic Orban
*/
public class BitronixTransactionManager implements TransactionManager, UserTransaction, Referenceable, Service {

    private final static Logger log = LoggerFactory.getLogger(BitronixTransactionManager.class);
    private final static String MDC_GTRID_KEY = "btm-gtrid";

    private final SortedMap<BitronixTransaction, ClearContextSynchronization> inFlightTransactions;

    private volatile boolean shuttingDown;

    /**
     * Create the {@link BitronixTransactionManager}. Open the journal, load resources and perform recovery
     * synchronously. The recovery service then gets scheduled for background recovery.
     */
    public BitronixTransactionManager() {
        try {
            shuttingDown = false;
            logVersion();
            Configuration configuration = TransactionManagerServices.getConfiguration();
            configuration.buildServerIdArray(); // first call will initialize the ServerId

            if (log.isDebugEnabled()) { log.debug("starting BitronixTransactionManager using " + configuration); }
            TransactionManagerServices.getJournal().open();
            TransactionManagerServices.getResourceLoader().init();
            TransactionManagerServices.getRecoverer().run();

            int backgroundRecoveryInterval = TransactionManagerServices.getConfiguration().getBackgroundRecoveryIntervalSeconds();
            if (backgroundRecoveryInterval < 1) {
                throw new InitializationException("invalid configuration value for backgroundRecoveryIntervalSeconds, found '" + backgroundRecoveryInterval + "' but it must be greater than 0");
            }

            inFlightTransactions = createInFlightTransactionsMap();

            if (log.isDebugEnabled()) { log.debug("recovery will run in the background every " + backgroundRecoveryInterval + " second(s)"); }
            Date nextExecutionDate = new Date(MonotonicClock.currentTimeMillis() + (backgroundRecoveryInterval * 1000L));
            TransactionManagerServices.getTaskScheduler().scheduleRecovery(TransactionManagerServices.getRecoverer(), nextExecutionDate);
        } catch (IOException ex) {
            throw new InitializationException("cannot open disk journal", ex);
        } catch (Exception ex) {
            TransactionManagerServices.getJournal().shutdown();
            TransactionManagerServices.getResourceLoader().shutdown();
            throw new InitializationException("initialization failed, cannot safely start the transaction manager", ex);
        }
    }

    private SortedMap<BitronixTransaction, ClearContextSynchronization> createInFlightTransactionsMap()
            throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        final boolean debug = log.isDebugEnabled();
        if (debug) { log.debug("Creating sorted memory storage for inflight transactions."); }

        final Comparator<BitronixTransaction> timestampSortComparator = new Comparator<BitronixTransaction>() {
                public int compare(BitronixTransaction t1, BitronixTransaction t2) {
                    Long timestamp1 = t1.getResourceManager().getGtrid().extractTimestamp();
                    Long timestamp2 = t2.getResourceManager().getGtrid().extractTimestamp();

                    int compareTo = timestamp1.compareTo(timestamp2);
                    if (compareTo == 0 && !t1.getResourceManager().getGtrid().equals(t2.getResourceManager().getGtrid())) {
                        // if timestamps are equal, use the Uid as the tie-breaker.  the !equals() check above avoids an expensive string compare() here.
                        return t1.getGtrid().compareTo(t2.getGtrid());
                    }
                    return compareTo;
                }
            };

        if (debug) { log.debug("Attempting to use a concurrent sorted map of type 'ConcurrentSkipListMap' (from jre6 or custom supplied backport)"); }
        try {
            @SuppressWarnings("unchecked")
            SortedMap<BitronixTransaction, ClearContextSynchronization> mapInstance = (SortedMap<BitronixTransaction, ClearContextSynchronization>)
                    ClassLoaderUtils.loadClass("java.util.concurrent.ConcurrentSkipListMap").
                            getConstructor(Comparator.class).newInstance(timestampSortComparator);
            return mapInstance;
        } catch (ClassNotFoundException e) {
            if (debug) { log.debug("Concurrent sorted map 'ConcurrentSkipListMap' is not available. Falling back to a synchronized TreeMap."); }
            return Collections.synchronizedSortedMap(
                    new TreeMap<BitronixTransaction, ClearContextSynchronization>(timestampSortComparator));
        }
    }

    /**
     * Start a new transaction and bind the context to the calling thread.
     * @throws NotSupportedException if a transaction is already bound to the calling thread.
     * @throws SystemException if the transaction manager is shutting down.
     */
    public void begin() throws NotSupportedException, SystemException {
        if (log.isDebugEnabled()) { log.debug("beginning a new transaction"); }
        if (isShuttingDown())
            throw new BitronixSystemException("cannot start a new transaction, transaction manager is shutting down");

        if (log.isDebugEnabled()) {
          dumpTransactionContexts();
        }

        BitronixTransaction currentTx = getCurrentTransaction();
        if (currentTx != null)
            throw new NotSupportedException("nested transactions not supported");
        currentTx = createTransaction();

        ThreadContext threadContext = ThreadContext.getThreadContext();
        ClearContextSynchronization clearContextSynchronization = new ClearContextSynchronization(currentTx, threadContext);
        try {
            currentTx.getSynchronizationScheduler().add(clearContextSynchronization, Scheduler.ALWAYS_LAST_POSITION -1);
            currentTx.setActive(threadContext.getTimeout());
            inFlightTransactions.put(currentTx, clearContextSynchronization);
            if (log.isDebugEnabled()) { log.debug("begun new transaction at " + new Date(currentTx.getResourceManager().getGtrid().extractTimestamp())); }
        } catch (RuntimeException ex) {
            clearContextSynchronization.afterCompletion(Status.STATUS_NO_TRANSACTION);
            throw ex;
        } catch (SystemException ex) {
            clearContextSynchronization.afterCompletion(Status.STATUS_NO_TRANSACTION);
            throw ex;
        }
    }

    public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException {
        BitronixTransaction currentTx = getCurrentTransaction();
        if (log.isDebugEnabled()) { log.debug("committing transaction " + currentTx); }
        if (currentTx == null)
            throw new IllegalStateException("no transaction started on this thread");

        currentTx.commit();
    }

    public void rollback() throws IllegalStateException, SecurityException, SystemException {
        BitronixTransaction currentTx = getCurrentTransaction();
        if (log.isDebugEnabled()) { log.debug("rolling back transaction " + currentTx); }
        if (currentTx == null)
            throw new IllegalStateException("no transaction started on this thread");

        currentTx.rollback();
    }

    public int getStatus() throws SystemException {
        BitronixTransaction currentTx = getCurrentTransaction();
        if (currentTx == null)
           return Status.STATUS_NO_TRANSACTION;

        return currentTx.getStatus();
    }

    public Transaction getTransaction() throws SystemException {
        return getCurrentTransaction();
    }

    public void setRollbackOnly() throws IllegalStateException, SystemException {
        BitronixTransaction currentTx = getCurrentTransaction();
        if (log.isDebugEnabled()) { log.debug("marking transaction as rollback only: " + currentTx); }
        if (currentTx == null)
            throw new IllegalStateException("no transaction started on this thread");

        currentTx.setRollbackOnly();
    }

    public void setTransactionTimeout(int seconds) throws SystemException {
        if (seconds < 0)
            throw new BitronixSystemException("cannot set a timeout to less than 0 second (was: " + seconds + "s)");
        ThreadContext.getThreadContext().setTimeout(seconds);
    }

    public Transaction suspend() throws SystemException {
        BitronixTransaction currentTx = getCurrentTransaction();
        if (log.isDebugEnabled()) { log.debug("suspending transaction " + currentTx); }
        if (currentTx == null)
            return null;

        try {
            currentTx.getResourceManager().suspend();
            clearCurrentContextForSuspension();
            inFlightTransactions.get(currentTx).setThreadContext(null);
            MDC.remove(MDC_GTRID_KEY);
            return currentTx;
        } catch (XAException ex) {
            String extraErrorDetails = TransactionManagerServices.getExceptionAnalyzer().extractExtraXAExceptionDetails(ex);
            throw new BitronixSystemException("cannot suspend " + currentTx + ", error=" + Decoder.decodeXAExceptionErrorCode(ex) +
                    (extraErrorDetails == null ? "" : ", extra error=" + extraErrorDetails), ex);
        }
    }

    public void resume(Transaction transaction) throws InvalidTransactionException, IllegalStateException, SystemException {
        if (log.isDebugEnabled()) { log.debug("resuming " + transaction); }
        if (transaction == null)
            throw new InvalidTransactionException("resumed transaction cannot be null");
        if (!(transaction instanceof BitronixTransaction))
            throw new InvalidTransactionException("resumed transaction must be an instance of BitronixTransaction");

        BitronixTransaction tx = (BitronixTransaction) transaction;
        if (getCurrentTransaction() != null)
            throw new IllegalStateException("a transaction is already running on this thread");

        try {
            XAResourceManager resourceManager = tx.getResourceManager();
            resourceManager.resume();
            ThreadContext threadContext = ThreadContext.getThreadContext();
            threadContext.setTransaction(tx);
            inFlightTransactions.get(tx).setThreadContext(threadContext);
            MDC.put(MDC_GTRID_KEY, tx.getGtrid());
        } catch (XAException ex) {
            String extraErrorDetails = TransactionManagerServices.getExceptionAnalyzer().extractExtraXAExceptionDetails(ex);
            throw new BitronixSystemException("cannot resume " + tx + ", error=" + Decoder.decodeXAExceptionErrorCode(ex) +
                    (extraErrorDetails == null ? "" : ", extra error=" + extraErrorDetails), ex);
        }
    }


    /**
     * BitronixTransactionManager can only have a single instance per JVM so this method always returns a reference
     * with no special information to find back the sole instance. BitronixTransactionManagerObjectFactory will be used
     * by the JNDI server to get the BitronixTransactionManager instance of the JVM.
     *
     * @return an empty reference to get the BitronixTransactionManager.
     */
    public Reference getReference() throws NamingException {
        return new Reference(
                BitronixTransactionManager.class.getName(),
                new StringRefAddr("TransactionManager", "BitronixTransactionManager"),
                BitronixTransactionManagerObjectFactory.class.getName(),
                null
        );
    }

    /**
     * Return a count of the current in-flight transactions.  Currently this method is only called by unit tests.
     * @return a count of in-flight transactions
     */
    public int getInFlightTransactionCount() {
        return inFlightTransactions.size();
    }

    /**
     * Return the timestamp of the oldest in-flight transaction.
     * @return the timestamp or Long.MIN_VALUE if there is no in-flight transaction.
     */
    public long getOldestInFlightTransactionTimestamp() {
        try {
          // The inFlightTransactions map is sorted by timestamp, so the first transaction is always the oldest
          BitronixTransaction oldestTransaction = inFlightTransactions.firstKey();
          long oldestTimestamp = oldestTransaction.getResourceManager().getGtrid().extractTimestamp();
          if (log.isDebugEnabled()) { log.debug("oldest in-flight transaction's timestamp: " + oldestTimestamp); }
          return oldestTimestamp;
         
        } catch (NoSuchElementException e) {
          if (log.isDebugEnabled()) { log.debug("oldest in-flight transaction's timestamp: " + Long.MIN_VALUE); }
          return Long.MIN_VALUE;
        }
    }

    /**
     * Get the transaction currently registered on the current thread context.
     * @return the current transaction or null if no transaction has been started on the current thread.
     */
    public BitronixTransaction getCurrentTransaction() {
        return ThreadContext.getThreadContext().getTransaction();
    }

    /**
     * Check if the transaction manager is in the process of shutting down.
     * @return true if the transaction manager is in the process of shutting down.
     */
    private boolean isShuttingDown() {
        return shuttingDown;
    }

    /**
     * Dump an overview of all running transactions as debug logs.
     */
    public void dumpTransactionContexts() {
        if (!log.isDebugEnabled())
            return;

        // We're using an iterator, so we must synchronize on the collection
      synchronized (inFlightTransactions) {
          log.debug("dumping " + inFlightTransactions.size() + " transaction context(s)");
          for (BitronixTransaction tx : inFlightTransactions.keySet()) {
              log.debug(tx.toString());
          }
      }
    }

    /**
     * Shut down the transaction manager and release all resources held by it.
     * <p>This call will also close the resources pools registered by the {@link bitronix.tm.resource.ResourceLoader}
     * like JMS and JDBC pools. The manually created ones are left untouched.</p>
     * <p>The Transaction Manager will wait during a configurable graceful period before forcibly killing active
     * transactions.</p>
     * After this method is called, attempts to create new transactions (via calls to
     * {@link javax.transaction.TransactionManager#begin()}) will be rejected with a {@link SystemException}.</p>
     * @see Configuration#getGracefulShutdownInterval()
     */
    public synchronized void shutdown() {
        if (isShuttingDown()) {
            if (log.isDebugEnabled()) { log.debug("Transaction Manager has already shut down"); }
            return;
        }

        log.info("shutting down Bitronix Transaction Manager");
        internalShutdown();

        if (log.isDebugEnabled()) { log.debug("shutting down resource loader"); }
        TransactionManagerServices.getResourceLoader().shutdown();

        if (log.isDebugEnabled()) { log.debug("shutting down executor"); }
        TransactionManagerServices.getExecutor().shutdown();

        if (log.isDebugEnabled()) { log.debug("shutting down task scheduler"); }
        TransactionManagerServices.getTaskScheduler().shutdown();

        if (log.isDebugEnabled()) { log.debug("shutting down journal"); }
        TransactionManagerServices.getJournal().shutdown();

        if (log.isDebugEnabled()) { log.debug("shutting down recoverer"); }
        TransactionManagerServices.getRecoverer().shutdown();

        if (log.isDebugEnabled()) { log.debug("shutting down configuration"); }
        TransactionManagerServices.getConfiguration().shutdown();

        // clear references
        TransactionManagerServices.clear();

        if (log.isDebugEnabled()) { log.debug("shutdown ran successfully"); }
    }

    private void internalShutdown() {
        shuttingDown = true;
        dumpTransactionContexts();

        int seconds = TransactionManagerServices.getConfiguration().getGracefulShutdownInterval();
        int txCount = 0;
        try {
            txCount = inFlightTransactions.size();
            while (seconds > &&  txCount > 0) {
                if (log.isDebugEnabled()) { log.debug("still " + txCount + " in-flight transactions, waiting... (" + seconds + " second(s) left)"); }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    // ignore
                }
                seconds--;
                txCount = inFlightTransactions.size();
            }
        } catch (Exception ex) {
            log.error("cannot get a list of in-flight transactions", ex);
        }

        if (txCount > 0) {
            if (log.isDebugEnabled()) {
              log.debug("still " + txCount + " in-flight transactions, shutting down anyway");
              dumpTransactionContexts();
            }
        }
        else {
            if (log.isDebugEnabled()) { log.debug("all transactions finished, resuming shutdown"); }
        }
    }

    public String toString() {
        return "a BitronixTransactionManager with " + inFlightTransactions.size() + " in-flight transaction(s)";
    }

    /*
    * Internal impl
    */

    /**
     * Output BTM version information as INFO log.
     */
    private void logVersion() {
        log.info("Bitronix Transaction Manager version " + Version.getVersion());
        if (log.isDebugEnabled()) { log.debug("JVM version " + System.getProperty("java.version")); }
    }

    /**
     * Create a new transaction on the current thread's context.
     * @return the created transaction.
     */
    private BitronixTransaction createTransaction() {
        BitronixTransaction transaction = new BitronixTransaction();
        ThreadContext.getThreadContext().setTransaction(transaction);
        MDC.put(MDC_GTRID_KEY, transaction.getGtrid());

        return transaction;
    }

    /**
     * Unlink the transaction from the current thread's context.
     */
    private void clearCurrentContextForSuspension() {
        if (log.isDebugEnabled()) { log.debug("clearing current thread context: " + ThreadContext.getThreadContext()); }
        ThreadContext.getThreadContext().clearTransaction();
    }

    private final class ClearContextSynchronization implements Synchronization {
        private final BitronixTransaction currentTx;
        private AtomicReference<ThreadContext> threadContext;

        public ClearContextSynchronization(BitronixTransaction currentTx, ThreadContext threadContext) {
            this.currentTx = currentTx;
            this.threadContext = new AtomicReference<ThreadContext>(threadContext);
        }

        public void beforeCompletion() {
        }

        public void afterCompletion(int status) {
          ThreadContext context = threadContext.get();
          if (context != null) {
              if (log.isDebugEnabled()) { log.debug("clearing transaction from thread context: " + context); }
              context.clearTransaction();
          }
          else {
            if (log.isDebugEnabled()) { log.debug("thread context was null when clear context synchronization executed"); }
          }
            if (log.isDebugEnabled()) { log.debug("removing transaction from in-flight transactions: " + currentTx); }
            inFlightTransactions.remove(currentTx);
            MDC.remove(MDC_GTRID_KEY);
        }

        public void setThreadContext(ThreadContext threadContext) {
          this.threadContext.set(threadContext);
        }

        public String toString() {
            return "a ClearContextSynchronization for " + currentTx;
        }
    }
}
TOP

Related Classes of bitronix.tm.BitronixTransactionManager$ClearContextSynchronization

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.