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

org.neo4j.kernel.impl.transaction.TxManager Maven / Gradle / Ivy

/**
 * Copyright (c) 2002-2013 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.impl.transaction;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;

import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.graphdb.event.ErrorState;
import org.neo4j.helpers.Exceptions;
import org.neo4j.helpers.UTF8;
import org.neo4j.kernel.impl.core.KernelPanicEventGenerator;
import org.neo4j.kernel.impl.core.TransactionState;
import org.neo4j.kernel.impl.nioneo.store.FileSystemAbstraction;
import org.neo4j.kernel.impl.transaction.xaframework.ForceMode;
import org.neo4j.kernel.impl.transaction.xaframework.XaDataSource;
import org.neo4j.kernel.impl.transaction.xaframework.XaResource;
import org.neo4j.kernel.impl.util.ExceptionCauseSetter;
import org.neo4j.kernel.impl.util.StringLogger;
import org.neo4j.kernel.impl.util.ThreadLocalWithSize;
import org.neo4j.kernel.lifecycle.Lifecycle;

/**
 * Default transaction manager implementation
 */
public class TxManager extends AbstractTransactionManager implements Lifecycle
{
    private ThreadLocalWithSize txThreadMap;

    private final File txLogDir;
    private File logSwitcherFileName = null;
    private String txLog1FileName = "tm_tx_log.1";
    private String txLog2FileName = "tm_tx_log.2";
    private final int maxTxLogRecordCount = 1000;
    private int eventIdentifierCounter = 0;

    private final Map branches = new HashMap();
    private volatile TxLog txLog = null;
    private boolean tmOk = false;
    private Throwable tmNotOkCause;

    private final KernelPanicEventGenerator kpe;

    private final AtomicInteger startedTxCount = new AtomicInteger( 0 );
    private final AtomicInteger comittedTxCount = new AtomicInteger( 0 );
    private final AtomicInteger rolledBackTxCount = new AtomicInteger( 0 );
    private int peakConcurrentTransactions = 0;

    private final StringLogger log;

    private final XaDataSourceManager xaDataSourceManager;
    private final FileSystemAbstraction fileSystem;
    private TxManager.TxManagerDataSourceRegistrationListener dataSourceRegistrationListener;

    private Throwable recoveryError;
    private final TransactionStateFactory stateFactory;

    public TxManager( File txLogDir,
                      XaDataSourceManager xaDataSourceManager,
                      KernelPanicEventGenerator kpe,
                      StringLogger log,
                      FileSystemAbstraction fileSystem,
                      TransactionStateFactory stateFactory
    )
    {
        this.txLogDir = txLogDir;
        this.xaDataSourceManager = xaDataSourceManager;
        this.fileSystem = fileSystem;
        this.log = log;
        this.kpe = kpe;
        this.stateFactory = stateFactory;
    }

    synchronized int getNextEventIdentifier()
    {
        return eventIdentifierCounter++;
    }

    private  E logAndReturn( String msg, E exception )
    {
        try
        {
            log.logMessage( msg, exception, true );
        }
        catch ( Throwable t )
        {
            // ignore
        }
        return exception;
    }

    private volatile boolean recovered = false;

    @Override
    public void init()
    {
    }

    @Override
    public synchronized void start()
            throws Throwable
    {
        txThreadMap = new ThreadLocalWithSize();
        openLog();
        findPendingDatasources();
        dataSourceRegistrationListener = new TxManagerDataSourceRegistrationListener();
        xaDataSourceManager.addDataSourceRegistrationListener( dataSourceRegistrationListener );
    }

    private void findPendingDatasources()
    {
        try
        {
            Iterable> danglingRecordList = txLog.getDanglingRecords();
            for ( List tx : danglingRecordList )
            {
                for ( TxLog.Record rec : tx )
                {
                    if ( rec.getType() == TxLog.BRANCH_ADD )
                    {
                        RecoveredBranchInfo branchId = new RecoveredBranchInfo( rec.getBranchId()) ;
                        if ( branches.containsKey( branchId ) )
                        {
                            continue;
                        }
                        branches.put( branchId, false );
                    }
                }
            }
        }
        catch ( IOException e )
        {
            log.logMessage( "Unable to recover pending branches", e );
            throw logAndReturn( "TM startup failure",
                    new TransactionFailureException( "Unable to start TM", e ) );
        }
    }

    @Override
    public synchronized void stop()
    {
        recovered = false;
        xaDataSourceManager.removeDataSourceRegistrationListener( dataSourceRegistrationListener );
        closeLog();
    }

    @Override
    public void shutdown()
            throws Throwable
    {
    }

    synchronized TxLog getTxLog() throws IOException
    {
        if ( txLog.getRecordCount() > maxTxLogRecordCount )
        {
            if ( txLog.getName().endsWith( txLog1FileName ) )
            {
                txLog.switchToLogFile( new File( txLogDir, txLog2FileName ));
                changeActiveLog( txLog2FileName );
            }
            else if ( txLog.getName().endsWith( txLog2FileName ) )
            {
                txLog.switchToLogFile( new File( txLogDir, txLog1FileName ));
                changeActiveLog( txLog1FileName );
            }
            else
            {
                setTmNotOk( new Exception( "Unknown active tx log file[" + txLog.getName()
                        + "], unable to switch." ) );
                final IOException ex = new IOException( "Unknown txLogFile[" + txLog.getName()
                        + "] not equals to either [" + txLog1FileName + "] or ["
                        + txLog2FileName + "]" );
                throw logAndReturn( "TM error accessing log file", ex );
            }
        }
        return txLog;
    }

    private void closeLog()
    {
        if ( txLog != null )
        {
            try
            {
                txLog.close();
                txLog = null;
                recovered = false;
            }
            catch ( IOException e )
            {
                log.logMessage( "Unable to close tx log[" + txLog.getName() + "]", e );
            }
        }
        log.logMessage( "TM shutting down", true );
    }

    private void changeActiveLog( String newFileName ) throws IOException
    {
        // change active log
        FileChannel fc = fileSystem.open( logSwitcherFileName, "rw" );
        ByteBuffer buf = ByteBuffer.wrap( UTF8.encode( newFileName ) );
        fc.truncate( 0 );
        fc.write( buf );
        fc.force( true );
        fc.close();
//        log.logMessage( "Active txlog set to " + newFileName, true );
    }

    synchronized void setTmNotOk( Throwable cause )
    {
        if ( !tmOk )
            return;
        
        tmOk = false;
        tmNotOkCause = cause;
        log.logMessage( "setting TM not OK", cause );
        kpe.generateEvent( ErrorState.TX_MANAGER_NOT_OK );
    }

    @Override
    public void begin() throws NotSupportedException, SystemException
    {
        begin( ForceMode.forced );
    }

    @Override
    public void begin( ForceMode forceMode ) throws NotSupportedException, SystemException
    {
        assertTmOk( "tx begin" );
        TransactionImpl tx = txThreadMap.get();
        if ( tx != null )
        {
            throw logAndReturn( "TM error tx begin", new NotSupportedException(
                    "Nested transactions not supported" ) );
        }
        tx = new TransactionImpl( this, forceMode, stateFactory, log );
        txThreadMap.set( tx );
        int concurrentTxCount = txThreadMap.size();
        if ( concurrentTxCount > peakConcurrentTransactions )
        {
            peakConcurrentTransactions = concurrentTxCount;
        }
        startedTxCount.incrementAndGet();
        // start record written on resource enlistment
    }

    private void assertTmOk( String source ) throws SystemException
    {
        if ( !tmOk )
        {
            SystemException ex = new SystemException( "TM has encountered some problem, "
                    + "please perform neccesary action (tx recovery/restart)" );
            if(tmNotOkCause != null)
            {
                ex = Exceptions.withCause( ex, tmNotOkCause );
            }

            throw ex;
        }
    }

    // called when a resource gets enlisted
    void writeStartRecord( byte globalId[] ) throws SystemException
    {
        try
        {
            getTxLog().txStart( globalId );
        }
        catch ( IOException e )
        {
            log.logMessage( "Error writing transaction log", e );
            setTmNotOk( e );
            throw logAndReturn( "TM error write start record", Exceptions.withCause( new SystemException( "TM " +
                    "encountered a problem, "
                    + " error writing transaction log," ), e ) );
        }
    }

    @Override
    public void commit() throws RollbackException, HeuristicMixedException,
            HeuristicRollbackException, IllegalStateException, SystemException
    {
        TransactionImpl tx = txThreadMap.get();
        if ( tx == null )
        {
            throw logAndReturn( "TM error tx commit", new IllegalStateException( "Not in transaction" ) );
        }

        boolean hasAnyLocks = false;
        boolean successful = false;
        try
        {
            assertTmOk( "tx commit" );
            Thread thread = Thread.currentThread();
            hasAnyLocks = tx.hasAnyLocks();
            if ( tx.getStatus() != Status.STATUS_ACTIVE
                    && tx.getStatus() != Status.STATUS_MARKED_ROLLBACK )
            {
                throw logAndReturn( "TM error tx commit", new IllegalStateException( "Tx status is: "
                        + getTxStatusAsString( tx.getStatus() ) ) );
            }


            tx.doBeforeCompletion();
            // delist resources?
            if ( tx.getStatus() == Status.STATUS_ACTIVE )
            {
                comittedTxCount.incrementAndGet();
                commit( thread, tx );
            }
            else if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK )
            {
                rolledBackTxCount.incrementAndGet();
                rollbackCommit( thread, tx );
            }
            else
            {
                throw logAndReturn( "TM error tx commit", new IllegalStateException( "Tx status is: "
                        + getTxStatusAsString( tx.getStatus() ) ) );
            }
            successful = true;
        }
        finally
        {
            txThreadMap.remove();
            if ( hasAnyLocks )
            {
                tx.finish( successful );
            }
        }
    }

    private void commit( Thread thread, TransactionImpl tx )
            throws SystemException, HeuristicMixedException,
            HeuristicRollbackException
    {
        // mark as commit in log done TxImpl.doCommit()
        Throwable commitFailureCause = null;
        int xaErrorCode = -1;
        if ( tx.getResourceCount() == 0 )
        {
            tx.setStatus( Status.STATUS_COMMITTED );
        }
        else
        {
            try
            {
                tx.doCommit();
            }
            catch ( XAException e )
            {
                // Behold, the error handling decision maker of great power.
                //
                // The thinking behind the code below is that there are certain types of errors that we understand,
                // and know that we can safely roll back after they occur. An example would be a user trying to delete
                // a node that still has relationships. For these errors, we keep a whitelist (the switch below),
                // and roll back when they occur.
                //
                // For *all* errors that we don't know exactly what they mean, we panic and run around in circles.
                // Other errors could involve out of disk space (can't recover) or out of memory (can't recover)
                // or anything else. The point is that there is no way for us to trust the state of the system any
                // more, so we set transaction manager to not ok and expect the user to fix the problem and do recovery.
                switch(e.errorCode)
                {
                    // These are error states that we can safely recover from

                    /*
                     * User tried to delete a node that still had relationships, or in some other way violated
                     * data model constraints.
                     */
                    case XAException.XA_RBINTEGRITY:

                    /*
                     *  A network error occurred.
                     */
                    case XAException.XA_HEURCOM:
                        xaErrorCode = e.errorCode;
                        commitFailureCause = e;
                        log.logMessage( "Commit failed, status=" + getTxStatusAsString( tx.getStatus() ) +
                                ", errorCode=" + xaErrorCode, e );
                        break;

                    // Error codes where we are not *certain* that we still know the state of the system
                    default:
                        setTmNotOk( e );
                        throw logAndReturn("TM error tx commit",new TransactionFailureException(
                                "commit threw exception", e ));
                }
            }
            catch ( Throwable t )
            {
                t.printStackTrace();
                log.logMessage( "Commit failed", t );

                setTmNotOk( t );
                // this should never be
                throw logAndReturn("TM error tx commit",new TransactionFailureException(
                        "commit threw exception but status is committed?", t ));
            }
        }
        if ( tx.getStatus() != Status.STATUS_COMMITTED )
        {
            try
            {
                tx.doRollback();
            }
            catch ( Throwable e )
            {
                log.logMessage( "Unable to rollback transaction. "
                        + "Some resources may be commited others not. "
                        + "Neo4j kernel should be SHUTDOWN for "
                        + "resource maintance and transaction recovery ---->", e );
                setTmNotOk( e );
                String commitError;
                if ( commitFailureCause != null )
                {
                    commitError = "error in commit: " + commitFailureCause;
                }
                else
                {
                    commitError = "error code in commit: " + xaErrorCode;
                }
                String rollbackErrorCode = "Unknown error code";
                if ( e instanceof XAException )
                {
                    rollbackErrorCode = Integer.toString( ((XAException) e).errorCode );
                }
                throw logAndReturn( "TM error tx commit", Exceptions.withCause( new HeuristicMixedException( "Unable " +
                        "to rollback ---> " + commitError
                        + " ---> error code for rollback: "
                        + rollbackErrorCode ), e ) );
            }
            tx.doAfterCompletion();
            try
            {
                if ( tx.isGlobalStartRecordWritten() )
                {
                    getTxLog().txDone( tx.getGlobalId() );
                }
            }
            catch ( IOException e )
            {
                log.logMessage( "Error writing transaction log", e );
                setTmNotOk( e );
                throw logAndReturn( "TM error tx commit", Exceptions.withCause( new SystemException( "TM encountered " +
                        "a problem, "
                        + " error writing transaction log" ), e ) );
            }
            tx.setStatus( Status.STATUS_NO_TRANSACTION );
            if ( commitFailureCause == null )
            {
                throw logAndReturn( "TM error tx commit", new HeuristicRollbackException(
                        "Failed to commit, transaction rolledback ---> "
                                + "error code was: " + xaErrorCode ) );
            }
            else
            {
                throw logAndReturn( "TM error tx commit", Exceptions.withCause( new HeuristicRollbackException(
                        "Failed to commit, transaction rolledback ---> " +
                                commitFailureCause ), commitFailureCause ) );
            }
        }
        tx.doAfterCompletion();
        try
        {
            if ( tx.isGlobalStartRecordWritten() )
            {
                getTxLog().txDone( tx.getGlobalId() );
            }
        }
        catch ( IOException e )
        {
            log.logMessage( "Error writing transaction log", e );
            setTmNotOk( e );
            throw logAndReturn( "TM error tx commit",
                    Exceptions.withCause( new SystemException( "TM encountered a problem, "
                            + " error writing transaction log" ), e ) );
        }
        tx.setStatus( Status.STATUS_NO_TRANSACTION );
    }

    private void rollbackCommit( Thread thread, TransactionImpl tx )
            throws HeuristicMixedException, RollbackException, SystemException
    {
        try
        {
            tx.doRollback();
        }
        catch ( XAException e )
        {
            log.logMessage( "Unable to rollback marked transaction. "
                    + "Some resources may be commited others not. "
                    + "Neo4j kernel should be SHUTDOWN for "
                    + "resource maintance and transaction recovery ---->", e );
            setTmNotOk( e );
            throw logAndReturn( "TM error tx rollback commit", Exceptions.withCause(
                    new HeuristicMixedException( "Unable to rollback " + " ---> error code for rollback: "
                            + e.errorCode ), e ) );
        }

        tx.doAfterCompletion();
        try
        {
            if ( tx.isGlobalStartRecordWritten() )
            {
                getTxLog().txDone( tx.getGlobalId() );
            }
        }
        catch ( IOException e )
        {
            log.logMessage( "Error writing transaction log", e );
            setTmNotOk( e );
            throw logAndReturn( "TM error tx rollback commit", Exceptions.withCause( new SystemException( "TM " +
                    "encountered a problem, "
                    + " error writing transaction log" ), e ) );
        }
        tx.setStatus( Status.STATUS_NO_TRANSACTION );
        RollbackException rollbackException = new RollbackException(
                "Failed to commit, transaction rolledback" );
        ExceptionCauseSetter.setCause( rollbackException, tx.getRollbackCause() );
        throw rollbackException;
    }

    @Override
    public void rollback() throws IllegalStateException, SystemException
    {
        TransactionImpl tx = txThreadMap.get();
        if ( tx == null )
        {
            throw logAndReturn( "TM error tx commit", new IllegalStateException( "Not in transaction" ) );
        }

        boolean hasAnyLocks = false;
        try
        {
            assertTmOk( "tx rollback" );
            hasAnyLocks = tx.hasAnyLocks();
            if ( tx.getStatus() == Status.STATUS_ACTIVE ||
                    tx.getStatus() == Status.STATUS_MARKED_ROLLBACK ||
                    tx.getStatus() == Status.STATUS_PREPARING )
            {
                tx.setStatus( Status.STATUS_MARKED_ROLLBACK );
                tx.doBeforeCompletion();
                // delist resources?
                try
                {
                    rolledBackTxCount.incrementAndGet();
                    tx.doRollback();
                }
                catch ( XAException e )
                {
                    log.logMessage( "Unable to rollback marked or active transaction. "
                            + "Some resources may be commited others not. "
                            + "Neo4j kernel should be SHUTDOWN for "
                            + "resource maintance and transaction recovery ---->", e );
                    setTmNotOk( e );
                    throw logAndReturn( "TM error tx rollback", Exceptions.withCause(
                            new SystemException( "Unable to rollback " + " ---> error code for rollback: "
                                    + e.errorCode ), e ) );
                }
                tx.doAfterCompletion();
                try
                {
                    if ( tx.isGlobalStartRecordWritten() )
                    {
                        getTxLog().txDone( tx.getGlobalId() );
                    }
                }
                catch ( IOException e )
                {
                    log.logMessage( "Error writing transaction log", e );
                    setTmNotOk( e );
                    throw logAndReturn( "TM error tx rollback", Exceptions.withCause(
                            new SystemException( "TM encountered a problem, "
                                    + " error writing transaction log" ), e ) );
                }
                tx.setStatus( Status.STATUS_NO_TRANSACTION );
            }
            else
            {
                throw new IllegalStateException( "Tx status is: "
                        + getTxStatusAsString( tx.getStatus() ) );
            }
        }
        finally
        {
            txThreadMap.remove();
            if ( hasAnyLocks )
            {
                tx.finish( false );
            }
        }
    }

    @Override
    public int getStatus()
    {
        TransactionImpl tx = txThreadMap.get();
        if ( tx != null )
        {
            return tx.getStatus();
        }
        return Status.STATUS_NO_TRANSACTION;
    }

    @Override
	public Transaction getTransaction() throws SystemException
    {
        assertTmOk( "get transaction" );
        return txThreadMap.get();
    }
    
    @Override
    public void resume( Transaction tx ) throws IllegalStateException,
            SystemException
    {
        assertTmOk( "tx resume" );
        if ( txThreadMap.get() != null )
        {
            throw new IllegalStateException( "Transaction already associated" );
        }
        if ( tx != null )
        {
            TransactionImpl txImpl = (TransactionImpl) tx;
            if ( txImpl.getStatus() != Status.STATUS_NO_TRANSACTION )
            {
                if ( txImpl.isActive() )
                {
                    throw new IllegalStateException( txImpl + " already active" );
                }
                txImpl.markAsActive();
                txThreadMap.set( txImpl );
            }
            // generate pro-active event resume
        }
    }

    @Override
    public Transaction suspend() throws SystemException
    {
        assertTmOk( "tx suspend" );
        // check for ACTIVE/MARKED_ROLLBACK?
        TransactionImpl tx = txThreadMap.get();
        if ( tx != null )
        {
            txThreadMap.remove();

            // generate pro-active event suspend
            tx.markAsSuspended();
        }
        return tx;
    }

    @Override
    public void setRollbackOnly() throws IllegalStateException, SystemException
    {
        assertTmOk( "tx set rollback only" );
        TransactionImpl tx = txThreadMap.get();
        if ( tx == null )
        {
            throw new IllegalStateException( "Not in transaction" );
        }
        tx.setRollbackOnly();
    }

    @Override
    public void setTransactionTimeout( int seconds ) throws SystemException
    {
        assertTmOk( "tx set timeout" );
        // ...
    }

    private void openLog()
    {
        logSwitcherFileName = new File( txLogDir, "active_tx_log");
        txLog1FileName = "tm_tx_log.1";
        txLog2FileName = "tm_tx_log.2";
        try
        {
            if ( fileSystem.fileExists( logSwitcherFileName ) )
            {
                FileChannel fc = fileSystem.open( logSwitcherFileName, "rw" );
                byte fileName[] = new byte[256];
                ByteBuffer buf = ByteBuffer.wrap( fileName );
                fc.read( buf );
                fc.close();
                File currentTxLog = new File( txLogDir, UTF8.decode( fileName ).trim());
                if ( !fileSystem.fileExists( currentTxLog ) )
                {
                    throw logAndReturn( "TM startup failure",
                            new TransactionFailureException(
                                    "Unable to start TM, " + "active tx log file[" +
                                            currentTxLog + "] not found." ) );
                }
                txLog = new TxLog( currentTxLog, fileSystem );
                log.logMessage( "TM opening log: " + currentTxLog, true );
            }
            else
            {
                if ( fileSystem.fileExists( new File( txLogDir, txLog1FileName ))
                        || fileSystem.fileExists( new File( txLogDir, txLog2FileName ) ))
                {
                    throw logAndReturn( "TM startup failure",
                            new TransactionFailureException(
                                    "Unable to start TM, "
                                            + "no active tx log file found but found either "
                                            + txLog1FileName + " or " + txLog2FileName
                                            + " file, please set one of them as active or "
                                            + "remove them." ) );
                }
                ByteBuffer buf = ByteBuffer.wrap( txLog1FileName
                        .getBytes( "UTF-8" ) );
                FileChannel fc = fileSystem.open( logSwitcherFileName, "rw" );
                fc.write( buf );
                txLog = new TxLog( new File( txLogDir, txLog1FileName), fileSystem );
                log.logMessage( "TM new log: " + txLog1FileName, true );
                fc.force( true );
                fc.close();
            }
        }
        catch ( IOException e )
        {
            log.logMessage( "Unable to start TM", e );
            throw logAndReturn( "TM startup failure",
                    new TransactionFailureException( "Unable to start TM", e ) );
        }
    }

    @Override
    public void doRecovery()
    {
        if ( txLog == null )
        {
            openLog();
        }
        if ( recovered )
        {
            return;
        }
        try
        {
            // Assuming here that the last datasource to register is the Neo one
            // Do recovery on start - all Resources should be registered by now
            Iterable> knownDanglingRecordList = txLog.getDanglingRecords();
            boolean danglingRecordsFound = knownDanglingRecordList.iterator().hasNext();
            if ( danglingRecordsFound )
            {
                log.info( "Unresolved transactions found in " + txLog.getName() + ", recovery started... " );
            }

            // Recover DataSources. Always call due to some internal state using it as a trigger.
            xaDataSourceManager.recover( knownDanglingRecordList.iterator() );

            if ( danglingRecordsFound )
            {
                log.logMessage( "Recovery completed, all transactions have been " +
                        "resolved to a consistent state." );
            }

            getTxLog().truncate();
            recovered = true;
            tmOk = true;
        }
        catch ( Throwable t )
        {
            setTmNotOk( t );

            recoveryError = t;
        }
    }

    byte[] getBranchId( XAResource xaRes )
    {
        if ( xaRes instanceof XaResource )
        {
            byte branchId[] = ((XaResource) xaRes).getBranchId();
            if ( branchId != null )
            {
                return branchId;
            }
        }
        return xaDataSourceManager.getBranchId( xaRes );
    }

    String getTxStatusAsString( int status )
    {
        switch ( status )
        {
            case Status.STATUS_ACTIVE:
                return "STATUS_ACTIVE";
            case Status.STATUS_NO_TRANSACTION:
                return "STATUS_NO_TRANSACTION";
            case Status.STATUS_PREPARING:
                return "STATUS_PREPARING";
            case Status.STATUS_PREPARED:
                return "STATUS_PREPARED";
            case Status.STATUS_COMMITTING:
                return "STATUS_COMMITING";
            case Status.STATUS_COMMITTED:
                return "STATUS_COMMITED";
            case Status.STATUS_ROLLING_BACK:
                return "STATUS_ROLLING_BACK";
            case Status.STATUS_ROLLEDBACK:
                return "STATUS_ROLLEDBACK";
            case Status.STATUS_UNKNOWN:
                return "STATUS_UNKNOWN";
            case Status.STATUS_MARKED_ROLLBACK:
                return "STATUS_MARKED_ROLLBACK";
            default:
                return "STATUS_UNKNOWN(" + status + ")";
        }
    }

    public synchronized void dumpTransactions()
    {
//        Iterator itr = txThreadMap.values().iterator();
//        if ( !itr.hasNext() )
//        {
//            System.out.println( "No uncompleted transactions" );
//            return;
//        }
//        System.out.println( "Uncompleted transactions found: " );
//        while ( itr.hasNext() )
//        {
//            System.out.println( itr.next() );
//        }
    }

    /**
     * @return The current transaction's event identifier or -1 if no
     *         transaction is currently running.
     */
    @Override
    public int getEventIdentifier()
    {
        TransactionImpl tx = null;
        try
        {
            tx = (TransactionImpl) getTransaction();
        }
        catch ( SystemException e )
        {
            throw new RuntimeException( e );
        }

        if ( tx != null )
        {
            return tx.getEventIdentifier();
        }
        return -1;
    }

    @Override
    public ForceMode getForceMode()
    {
        try
        {
            return ((TransactionImpl)getTransaction()).getForceMode();
        }
        catch ( SystemException e )
        {
            throw new RuntimeException( e );
        }
    }

    @Override
    public Throwable getRecoveryError()
    {
        return recoveryError;
    }

    public int getStartedTxCount()
    {
        return startedTxCount.get();
    }

    public int getCommittedTxCount()
    {
        return comittedTxCount.get();
    }

    public int getRolledbackTxCount()
    {
        return rolledBackTxCount.get();
    }

    public int getActiveTxCount()
    {
        return txThreadMap.size();
    }

    public int getPeakConcurrentTxCount()
    {
        return peakConcurrentTransactions;
    }

    @Override
    public TransactionState getTransactionState()
    {
        Transaction tx;
        try
        {
            tx = getTransaction();
        }
        catch ( SystemException e )
        {
            throw new RuntimeException( e );
        }
        return tx != null ? ((TransactionImpl)tx).getState() : TransactionState.NO_STATE;
    }
    
    private class TxManagerDataSourceRegistrationListener implements DataSourceRegistrationListener
    {
        @Override
        public void registeredDataSource( XaDataSource ds )
        {
            branches.put( new RecoveredBranchInfo( ds.getBranchId() ), true );
            boolean everythingRegistered = true;
            for ( boolean dsRegistered : branches.values() )
            {
                everythingRegistered &= dsRegistered;
            }
            if ( everythingRegistered )
            {
//                    openLog();
                doRecovery();
            }
        }

        @Override
        public void unregisteredDataSource( XaDataSource ds )
        {
            branches.put( new RecoveredBranchInfo( ds.getBranchId() ), false );
            boolean everythingUnregistered = true;
            for ( boolean dsRegistered : branches.values() )
            {
                everythingUnregistered &= !dsRegistered;
            }
            if ( everythingUnregistered )
            {
                closeLog();
            }
        }
    }

    /*
     * We use a hash map to store the branch ids. byte[] however does not offer a useful implementation of equals() or
     * hashCode(), so we need a wrapper that does that.
     */
    private static final class RecoveredBranchInfo
    {
        final byte[] branchId;

        private RecoveredBranchInfo( byte[] branchId )
        {
            this.branchId = branchId;
        }

        @Override
        public int hashCode()
        {
            return Arrays.hashCode( branchId );
        }

        @Override
        public boolean equals( Object obj )
        {
            if ( obj == null || obj.getClass() != RecoveredBranchInfo.class )
            {
                return false;
            }
            return Arrays.equals( branchId, ( ( RecoveredBranchInfo )obj ).branchId );
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy