/**
* 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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.transaction;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
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 javax.transaction.xa.Xid;
import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.graphdb.event.ErrorState;
import org.neo4j.helpers.Exceptions;
import org.neo4j.helpers.UTF8;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.kernel.impl.core.KernelPanicEventGenerator;
import org.neo4j.kernel.impl.transaction.xaframework.XaDataSource;
import org.neo4j.kernel.impl.transaction.xaframework.XaResource;
import org.neo4j.kernel.impl.util.ArrayMap;
import org.neo4j.kernel.impl.util.StringLogger;
/**
* Public for testing purpose only. Use {@link TransactionFactory} to get a
* <CODE>UserTransaction</CODE>, don't use this class.
* <p>
*/
public class TxManager extends AbstractTransactionManager
{
private static Logger log = Logger.getLogger( TxManager.class.getName() );
private ArrayMap<Thread,TransactionImpl> txThreadMap;
private final String txLogDir;
private static String separator = File.separator;
private String logSwitcherFileName = "active_tx_log";
private String txLog1FileName = "tm_tx_log.1";
private String txLog2FileName = "tm_tx_log.2";
private final int maxTxLogRecordCount = 1000;
private int eventIdentifierCounter = 0;
private TxLog txLog = null;
private XaDataSourceManager xaDsManager = null;
private boolean tmOk = false;
private boolean blocked = false;
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 msgLog;
final TxFinishHook finishHook;
TxManager( String txLogDir, KernelPanicEventGenerator kpe, TxFinishHook finishHook )
{
this.txLogDir = txLogDir;
this.msgLog = StringLogger.getLogger( txLogDir );
this.kpe = kpe;
this.finishHook = finishHook;
}
synchronized int getNextEventIdentifier()
{
return eventIdentifierCounter++;
}
private <E extends Exception> E logAndReturn(String msg, E exception)
{
try
{
msgLog.logMessage(msg,exception,true);
} catch(Throwable t)
{
// ignore
}
return exception;
}
@Override
public void stop()
{
if ( txLog != null )
{
try
{
txLog.close();
}
catch ( IOException e )
{
log.log( Level.WARNING, "Unable to close tx log[" + txLog.getName() + "]", e );
}
}
msgLog.logMessage( "TM shutting down", true );
StringLogger.close( txLogDir );
}
@Override
public void init( XaDataSourceManager xaDsManagerToUse )
{
this.xaDsManager = xaDsManagerToUse;
txThreadMap = new ArrayMap<Thread,TransactionImpl>( 5, true, true );
logSwitcherFileName = txLogDir + separator + "active_tx_log";
txLog1FileName = "tm_tx_log.1";
txLog2FileName = "tm_tx_log.2";
try
{
if ( new File( logSwitcherFileName ).exists() )
{
FileChannel fc = new RandomAccessFile( logSwitcherFileName,
"rw" ).getChannel();
byte fileName[] = new byte[256];
ByteBuffer buf = ByteBuffer.wrap( fileName );
fc.read( buf );
fc.close();
String currentTxLog = txLogDir + separator
+ UTF8.decode( fileName ).trim();
if ( !new File( currentTxLog ).exists() )
{
throw logAndReturn("TM startup failure",
new TransactionFailureException(
"Unable to start TM, " + "active tx log file[" +
currentTxLog + "] not found."));
}
txLog = new TxLog( currentTxLog );
msgLog.logMessage( "TM opening log: " + currentTxLog, true );
}
else
{
if ( new File( txLogDir + separator + txLog1FileName ).exists()
|| new File( txLogDir + separator + txLog2FileName )
.exists() )
{
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 = new RandomAccessFile( logSwitcherFileName,
"rw" ).getChannel();
fc.write( buf );
txLog = new TxLog( txLogDir + separator + txLog1FileName );
msgLog.logMessage( "TM new log: " + txLog1FileName, true );
fc.force( true );
fc.close();
}
Iterator<List<TxLog.Record>> danglingRecordList =
txLog.getDanglingRecords();
if ( danglingRecordList.hasNext() )
{
log.info( "Unresolved transactions found, " +
"recovery started ..." );
recover( danglingRecordList );
log.info( "Recovery completed, all transactions have been " +
"resolved to a consistent state." );
msgLog.logMessage( "Recovery completed, all transactions have been " +
"resolved to a consistent state." );
}
getTxLog().truncate();
tmOk = true;
}
catch ( IOException e )
{
log.log(Level.SEVERE, "Unable to start TM", e);
throw logAndReturn("TM startup failure",
new TransactionFailureException("Unable to start TM", e));
}
}
synchronized TxLog getTxLog() throws IOException
{
if ( txLog.getRecordCount() > maxTxLogRecordCount )
{
if ( txLog.getName().endsWith( txLog1FileName ) )
{
txLog.switchToLogFile( txLogDir + separator + txLog2FileName );
changeActiveLog( txLog2FileName );
}
else if ( txLog.getName().endsWith( txLog2FileName ) )
{
txLog.switchToLogFile( txLogDir + separator + txLog1FileName );
changeActiveLog( txLog1FileName );
}
else {
setTmNotOk( new Exception( "Unknown active tx log file[" + txLog.getName()
+ "], unable to switch." ) );
log.severe("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 changeActiveLog( String newFileName ) throws IOException
{
// change active log
FileChannel fc = new RandomAccessFile( logSwitcherFileName, "rw" )
.getChannel();
ByteBuffer buf = ByteBuffer.wrap( UTF8.encode( newFileName ) );
fc.truncate( 0 );
fc.write( buf );
fc.force( true );
fc.close();
}
void setTmNotOk( Throwable cause )
{
tmOk = false;
msgLog.logMessage( "setting TM not OK", cause );
kpe.generateEvent( ErrorState.TX_MANAGER_NOT_OK );
}
@Override
public void attemptWaitForTxCompletionAndBlockFutureTransactions( long maxWaitTimeMillis )
{
msgLog.logMessage( "TxManager is blocking new transactions and waiting for active to fail..." );
blocked = true;
List<Transaction> failedTransactions = new ArrayList<Transaction>();
synchronized ( txThreadMap )
{
for ( Transaction tx : txThreadMap.values() )
{
try
{
int status = tx.getStatus();
if ( status != Status.STATUS_COMMITTING && status != Status.STATUS_ROLLING_BACK )
{ // Set it to rollback only if it's not committing or rolling back
tx.setRollbackOnly();
}
}
catch ( IllegalStateException e )
{ // OK
failedTransactions.add( tx );
}
catch ( SystemException e )
{ // OK
failedTransactions.add( tx );
}
}
}
msgLog.logMessage( "TxManager blocked transactions" + ((failedTransactions.isEmpty() ? "" :
", but failed for: " + failedTransactions.toString())) );
long endTime = System.currentTimeMillis()+maxWaitTimeMillis;
while ( txThreadMap.size() > 0 && System.currentTimeMillis() < endTime ) Thread.yield();
}
private void recover( Iterator<List<TxLog.Record>> danglingRecordList )
{
msgLog.logMessage( "TM non resolved transactions found in " + txLog.getName(), true );
try
{
// contains NonCompletedTransaction that needs to be committed
List<NonCompletedTransaction> commitList =
new ArrayList<NonCompletedTransaction>();
// contains Xids that should be rolledback
List<Xid> rollbackList = new LinkedList<Xid>();
// key = Resource(branchId) value = XAResource
Map<Resource,XAResource> resourceMap =
new HashMap<Resource,XAResource>();
buildRecoveryInfo( commitList, rollbackList, resourceMap,
danglingRecordList );
// invoke recover on all xa resources found
Iterator<Resource> resourceItr = resourceMap.keySet().iterator();
List<Xid> recoveredXidsList = new LinkedList<Xid>();
while ( resourceItr.hasNext() )
{
XAResource xaRes = resourceMap.get( resourceItr.next() );
Xid xids[] = xaRes.recover( XAResource.TMNOFLAGS );
for ( int i = 0; i < xids.length; i++ )
{
if ( XidImpl.isThisTm( xids[i].getGlobalTransactionId() ) )
{
// linear search
if ( rollbackList.contains( xids[i] ) )
{
log.fine( "Found pre commit " + xids[i]
+ " rolling back ... " );
msgLog.logMessage( "TM: Found pre commit " + xids[i] + " rolling back ... ", true );
rollbackList.remove( xids[i] );
xaRes.rollback( xids[i] );
}
else
{
recoveredXidsList.add( xids[i] );
}
}
else
{
log.warning( "Unknown xid: " + xids[i] );
}
}
}
// sort the commit list after sequence number
Collections.sort( commitList,
new Comparator<NonCompletedTransaction>()
{
public int compare( NonCompletedTransaction r1,
NonCompletedTransaction r2 )
{
return r1.getSequenceNumber() - r2.getSequenceNumber();
}
} );
// go through and commit
Iterator<NonCompletedTransaction> commitItr = commitList.iterator();
while ( commitItr.hasNext() )
{
NonCompletedTransaction nct = commitItr.next();
int seq = nct.getSequenceNumber();
Xid xids[] = nct.getXids();
log.fine( "Marked as commit tx-seq[" + seq +
"] branch length: " + xids.length );
for ( Xid xid : xids )
{
if ( !recoveredXidsList.contains( xid ) )
{
log.fine( "Tx-seq[" + seq + "][" + xid +
"] not found in recovered xid list, "
+ "assuming already committed" );
continue;
}
recoveredXidsList.remove( xid );
Resource resource = new Resource( xid.getBranchQualifier() );
if ( !resourceMap.containsKey( resource ) ) {
final TransactionFailureException ex = new TransactionFailureException(
"Couldn't find XAResource for " + xid);
throw logAndReturn("TM: recovery error", ex);
}
log.fine( "Commiting tx seq[" + seq + "][" + xid + "] ... " );
msgLog.logMessage( "TM: Committing tx " + xid, true );
resourceMap.get( resource ).commit( xid, false );
}
}
// rollback the rest
Iterator<Xid> rollbackItr = recoveredXidsList.iterator();
while ( rollbackItr.hasNext() )
{
Xid xid = rollbackItr.next();
Resource resource = new Resource( xid.getBranchQualifier() );
if ( !resourceMap.containsKey( resource ) ) {
final TransactionFailureException ex = new TransactionFailureException(
"Couldn't find XAResource for " + xid);
throw logAndReturn("TM: recovery error", ex);
}
log.fine( "Rollback " + xid + " ... " );
msgLog.logMessage( "TM: no match found for " + xid + " removing", true );
resourceMap.get( resource ).rollback( xid );
}
if ( rollbackList.size() > 0 )
{
log.fine( "TxLog contained unresolved "
+ "xids that needed rollback. They couldn't be matched to "
+ "any of the XAResources recover list. " + "Assuming "
+ rollbackList.size()
+ " transactions already rolled back." );
msgLog.logMessage( "TM: no match found for in total " + rollbackList.size() +
" transaction that should have been rolled back", true );
}
// Rotate the logs of the participated data sources, making sure that
// done-records are written so that even if the tm log gets truncated,
// which it will be after this recovery, that transaction information
// doesn't get lost.
for ( XAResource participant : MapUtil.reverse( resourceMap ).keySet() )
{
xaResourceToDataSource( participant ).rotateLogicalLog();
}
}
catch ( IOException e )
{
throw logAndReturn("TM: recovery failed",new TransactionFailureException("Recovery failed.", e));
}
catch ( XAException e )
{
throw logAndReturn("TM: recovery failed", new TransactionFailureException( "Recovery failed.", e ));
}
}
private XaDataSource xaResourceToDataSource( XAResource participant )
{
byte[] participantBranchId = xaDsManager.getBranchId( participant );
for ( XaDataSource dataSource : xaDsManager.getAllRegisteredDataSources() )
{
if ( Arrays.equals( participantBranchId, dataSource.getBranchId() ) )
{
return dataSource;
}
}
throw logAndReturn("TM recovery data source not found",
new TransactionFailureException("Data source for recovery participant " + participant +
", " + Arrays.toString(participantBranchId) + " couldn't be found"));
}
private void buildRecoveryInfo( List<NonCompletedTransaction> commitList,
List<Xid> rollbackList, Map<Resource,XAResource> resourceMap,
Iterator<List<TxLog.Record>> danglingRecordList )
{
while ( danglingRecordList.hasNext() )
{
Iterator<TxLog.Record> dListItr =
danglingRecordList.next().iterator();
TxLog.Record startRecord = dListItr.next();
if ( startRecord.getType() != TxLog.TX_START )
{
throw logAndReturn("TM error building recovery info",
new TransactionFailureException(
"First record not a start record, type="
+ startRecord.getType() ));
}
// get branches & commit status
HashSet<Resource> branchSet = new HashSet<Resource>();
int markedCommit = -1;
while ( dListItr.hasNext() )
{
TxLog.Record record = dListItr.next();
if ( record.getType() == TxLog.BRANCH_ADD )
{
if ( markedCommit != -1 )
{
throw logAndReturn("TM error building recovery info", new TransactionFailureException(
"Already marked commit " + startRecord ));
}
branchSet.add( new Resource( record.getBranchId() ) );
}
else if ( record.getType() == TxLog.MARK_COMMIT )
{
if ( markedCommit != -1 )
{
throw logAndReturn("TM error building recovery info",new TransactionFailureException(
"Already marked commit " + startRecord ));
}
markedCommit = record.getSequenceNumber();
}
else
{
throw logAndReturn("TM error building recovery info",new TransactionFailureException(
"Illegal record type[" + record.getType() + "]" ));
}
}
Iterator<Resource> resourceItr = branchSet.iterator();
List<Xid> xids = new LinkedList<Xid>();
while ( resourceItr.hasNext() )
{
Resource resource = resourceItr.next();
if ( !resourceMap.containsKey( resource ) )
{
resourceMap.put( resource, getXaResource(
resource.getResourceId() ) );
}
xids.add( new XidImpl( startRecord.getGlobalId(),
resource.getResourceId() ) );
}
if ( markedCommit != -1 ) // this xid needs to be committed
{
commitList.add(
new NonCompletedTransaction( markedCommit, xids ) );
}
else
{
rollbackList.addAll( xids );
}
}
}
private static class NonCompletedTransaction
{
private int seqNr = -1;
private List<Xid> xidList = null;
NonCompletedTransaction( int seqNr, List<Xid> xidList )
{
this.seqNr = seqNr;
this.xidList = xidList;
}
int getSequenceNumber()
{
return seqNr;
}
Xid[] getXids()
{
return xidList.toArray( new XidImpl[xidList.size()] );
}
@Override
public String toString()
{
return "NonCompletedTx[" + seqNr + "," + xidList + "]";
}
}
private static class Resource
{
private byte resourceId[] = null;
Resource( byte resourceId[] )
{
if ( resourceId == null || resourceId.length == 0 )
{
throw new IllegalArgumentException( "Illegal resourceId" );
}
this.resourceId = resourceId;
}
byte[] getResourceId()
{
return resourceId;
}
@Override
public boolean equals( Object o )
{
if ( !(o instanceof Resource) )
{
return false;
}
byte otherResourceId[] = ((Resource) o).getResourceId();
if ( resourceId.length != otherResourceId.length )
{
return false;
}
for ( int i = 0; i < resourceId.length; i++ )
{
if ( resourceId[i] != otherResourceId[i] )
{
return false;
}
}
return true;
}
private volatile int hashCode = 0;
@Override
public int hashCode()
{
if ( hashCode == 0 )
{
int calcHash = 0;
for ( int i = 0; i < resourceId.length; i++ )
{
calcHash += resourceId[i] << i * 8;
}
hashCode = 3217 * calcHash;
}
return hashCode;
}
}
public void begin() throws NotSupportedException, SystemException
{
if ( blocked )
{
throw new SystemException( "TxManager is preventing new transactions from starting " +
"due a shutdown is imminent" );
}
assertTmOk( "tx begin" );
Thread thread = Thread.currentThread();
TransactionImpl tx = txThreadMap.get( thread );
if ( tx != null )
{
throw logAndReturn("TM error tx begin",new NotSupportedException(
"Nested transactions not supported" ));
}
tx = new TransactionImpl( this );
txThreadMap.put( thread, 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 )
{
throw new SystemException( "TM has encountered some problem, "
+ "please perform neccesary action (tx recovery/restart)" );
}
}
// called when a resource gets enlisted
void writeStartRecord( byte globalId[] ) throws SystemException
{
try
{
getTxLog().txStart( globalId );
}
catch ( IOException e )
{
log.log( Level.SEVERE, "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 ));
}
}
public void commit() throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, IllegalStateException, SystemException
{
assertTmOk( "tx commit" );
Thread thread = Thread.currentThread();
TransactionImpl tx = txThreadMap.get( thread );
if ( tx == null )
{
throw logAndReturn("TM error tx commit", new IllegalStateException( "Not in transaction" ));
}
boolean hasAnyLocks = false;
boolean successful = false;
try
{
hasAnyLocks = finishHook.hasAnyLocks( tx );
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
{
if ( hasAnyLocks )
{
finishHook.finishTransaction( tx.getEventIdentifier(), 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 )
{
xaErrorCode = e.errorCode;
log.log( Level.SEVERE, "Commit failed, status=" + getTxStatusAsString( tx.getStatus() ) +
", errorCode=" + xaErrorCode, e );
if ( tx.getStatus() == Status.STATUS_COMMITTED )
{
// this should never be
setTmNotOk( e );
throw logAndReturn("TM error tx commit",new TransactionFailureException(
"commit threw exception but status is committed?", e ));
}
}
catch ( Throwable t )
{
log.log( Level.SEVERE, "Commit failed", t );
commitFailureCause = t;
}
}
if ( tx.getStatus() != Status.STATUS_COMMITTED )
{
try
{
tx.doRollback();
}
catch ( Throwable e )
{
log.log( Level.SEVERE, "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 = "Uknown 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();
txThreadMap.remove( thread );
try
{
if ( tx.isGlobalStartRecordWritten() )
{
getTxLog().txDone( tx.getGlobalId() );
}
}
catch ( IOException e )
{
log.log( Level.SEVERE, "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();
txThreadMap.remove( thread );
try
{
if ( tx.isGlobalStartRecordWritten() )
{
getTxLog().txDone( tx.getGlobalId() );
}
}
catch ( IOException e )
{
log.log( Level.SEVERE, "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.log( Level.SEVERE, "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();
txThreadMap.remove( thread );
try
{
if ( tx.isGlobalStartRecordWritten() )
{
getTxLog().txDone( tx.getGlobalId() );
}
}
catch ( IOException e )
{
log.log( Level.SEVERE, "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 );
throw new RollbackException(
"Failed to commit, transaction rolledback" );
}
public void rollback() throws IllegalStateException, SystemException
{
assertTmOk( "tx rollback" );
Thread thread = Thread.currentThread();
TransactionImpl tx = txThreadMap.get( thread );
if ( tx == null )
{
throw new IllegalStateException( "Not in transaction" );
}
boolean hasAnyLocks = false;
try
{
hasAnyLocks = finishHook.hasAnyLocks( tx );
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.log( Level.SEVERE, "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();
txThreadMap.remove( thread );
try
{
if ( tx.isGlobalStartRecordWritten() )
{
getTxLog().txDone( tx.getGlobalId() );
}
}
catch ( IOException e )
{
log.log( Level.SEVERE, "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
{
if ( hasAnyLocks )
{
finishHook.finishTransaction( tx.getEventIdentifier(), false );
}
}
}
public int getStatus()
{
Thread thread = Thread.currentThread();
TransactionImpl tx = txThreadMap.get( thread );
if ( tx != null )
{
return tx.getStatus();
}
return Status.STATUS_NO_TRANSACTION;
}
public Transaction getTransaction()
{
return txThreadMap.get( Thread.currentThread() );
}
public void resume( Transaction tx ) throws IllegalStateException,
SystemException
{
assertTmOk( "tx resume" );
Thread thread = Thread.currentThread();
if ( txThreadMap.get( thread ) != 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.put( thread, txImpl );
}
// generate pro-active event resume
}
}
public Transaction suspend() throws SystemException
{
assertTmOk( "tx suspend" );
// check for ACTIVE/MARKED_ROLLBACK?
TransactionImpl tx = txThreadMap.remove( Thread.currentThread() );
if ( tx != null )
{
// generate pro-active event suspend
tx.markAsSuspended();
}
return tx;
}
public void setRollbackOnly() throws IllegalStateException, SystemException
{
assertTmOk( "tx set rollback only" );
Thread thread = Thread.currentThread();
TransactionImpl tx = txThreadMap.get( thread );
if ( tx == null )
{
throw new IllegalStateException( "Not in transaction" );
}
tx.setRollbackOnly();
}
public void setTransactionTimeout( int seconds ) throws SystemException
{
assertTmOk( "tx set timeout" );
// ...
}
byte[] getBranchId( XAResource xaRes )
{
if ( xaRes instanceof XaResource )
{
byte branchId[] = ((XaResource) xaRes).getBranchId();
if ( branchId != null )
{
return branchId;
}
}
return xaDsManager.getBranchId( xaRes );
}
XAResource getXaResource( byte branchId[] )
{
return xaDsManager.getXaResource( branchId );
}
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<TransactionImpl> 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() );
}
}
public int getEventIdentifier()
{
TransactionImpl tx = (TransactionImpl) getTransaction();
if ( tx != null )
{
return tx.getEventIdentifier();
}
return -1;
}
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;
}
}