/*
* Copyright 2000-2008, Atomikos (http://www.atomikos.com)
*
* This code ("Atomikos TransactionsEssentials"), by itself,
* is being distributed under the
* Apache License, Version 2.0 ("License"), a copy of which may be found at
* http://www.atomikos.com/licenses/apache-license-2.0.txt .
* You may not use this file except in compliance with the License.
*
* While the License grants certain patent license rights,
* those patent license rights only extend to the use of
* Atomikos TransactionsEssentials by itself.
*
* This code (Atomikos TransactionsEssentials) contains certain interfaces
* in package (namespace) com.atomikos.icatch
* (including com.atomikos.icatch.Participant) which, if implemented, may
* infringe one or more patents held by Atomikos.
* It should be appreciated that you may NOT implement such interfaces;
* licensing to implement these interfaces must be obtained separately from Atomikos.
*
* 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.
*
*/
package com.atomikos.icatch.imp;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Stack;
import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.Extent;
import com.atomikos.icatch.HeurCommitException;
import com.atomikos.icatch.HeurHazardException;
import com.atomikos.icatch.HeurMixedException;
import com.atomikos.icatch.Participant;
import com.atomikos.icatch.RecoveryCoordinator;
import com.atomikos.icatch.RollbackException;
import com.atomikos.icatch.StringHeuristicMessage;
import com.atomikos.icatch.SubTxAwareParticipant;
import com.atomikos.icatch.Synchronization;
import com.atomikos.icatch.SysException;
import com.atomikos.icatch.TxState;
import com.atomikos.icatch.system.Configuration;
/**
*
*
* The state pattern applied to the CompositeTransaction classes.
*/
abstract class TransactionStateHandler implements SubTxAwareParticipant
{
private int subtxs_;
// REMOVED: one coordinator per subtransaction
// so coordinator can hold all participants
// private Stack participants_;
private Stack synchronizations_;
private List subtxawares_;
private CompositeTransactionImp ct_;
protected TransactionStateHandler ( CompositeTransactionImp ct )
{
ct_ = ct;
subtxs_ = 0;
subtxawares_ = new ArrayList ();
// participants_ = new Stack();
synchronizations_ = new Stack ();
}
protected TransactionStateHandler ( CompositeTransactionImp ct ,
TransactionStateHandler handler )
{
subtxs_ = handler.getSubTransactionCount ();
// participants_ = handler.getParticipants();
synchronizations_ = handler.getSynchronizations ();
subtxawares_ = handler.getSubtxawares ();
ct_ = ct;
}
private synchronized void localDecSubTxCount()
{
subtxs_--;
}
private synchronized void localIncSubTxCount()
{
subtxs_++;
}
private synchronized int localGetSubTxCount()
{
return subtxs_;
}
private synchronized void localPushSynchronization ( Synchronization sync )
{
synchronizations_.push ( sync );
}
private synchronized void localAddSubTxAwareParticipant ( SubTxAwareParticipant p )
{
subtxawares_.add ( p );
}
protected CompositeTransaction createSubTransaction ()
throws SysException, IllegalStateException
{
// argument is not null on test!
CompositeTransaction ct = null;
ct = ct_.getTransactionService ().createSubTransaction ( ct_ );
// we want to be notified of subtx commit for handling extents
ct.addSubTxAwareParticipant ( this );
localIncSubTxCount();
return ct;
}
// this method should NOT be synchronized, to avoid deadlocks in JBoss
// termination handling at remote servers!
protected RecoveryCoordinator addParticipant ( Participant participant )
throws SysException, java.lang.IllegalStateException
{
CoordinatorImp coord = ct_.getCoordinatorImp ();
try {
coord.addParticipant ( participant );
} catch ( RollbackException e ) {
throw new IllegalStateException ( "Transaction already rolled back" );
}
return coord;
}
protected void registerSynchronization ( Synchronization sync )
throws IllegalStateException, UnsupportedOperationException, SysException
{
if ( sync != null ) {
try {
ct_.getCoordinatorImp ().registerSynchronization ( sync );
} catch ( RollbackException e ) {
throw new IllegalStateException (
"Transaction already rolled back" );
}
localPushSynchronization ( sync );
}
}
protected void addSubTxAwareParticipant (
SubTxAwareParticipant subtxaware ) throws SysException,
java.lang.IllegalStateException
{
localAddSubTxAwareParticipant ( subtxaware );
}
private void rollback() throws IllegalStateException, SysException
{
Stack errors = new Stack ();
for ( int i = 0; i < subtxawares_.size (); i++ ) {
SubTxAwareParticipant subtxaware = (SubTxAwareParticipant) subtxawares_
.get ( i );
subtxaware.rolledback ( ct_ );
// NOTE: this can NOT be done by coordinator imp.,
// since that one will not know which tx is locally done!
}
// Added (bug discovered by MM):
// rollback extent participants too!
Enumeration enumm = null;
Extent extent = ct_.getExtent ();
if ( extent != null ) {
enumm = extent.getParticipants ().elements ();
while ( enumm.hasMoreElements () ) {
Participant part = (Participant) enumm.nextElement ();
// participants_.push ( part );
addParticipant ( part );
}
}
ct_.localSetTransactionStateHandler ( new TxTerminatedStateHandler (
ct_, this, false ) );
// rollback coordinator outside SYNCH block to avoid deadlocks
try {
ct_.getCoordinatorImp ().rollback ();
} catch ( HeurCommitException e ) {
errors.push ( e );
throw new SysException ( "Unexpected error in rollback", errors );
} catch ( HeurMixedException e ) {
errors.push ( e );
throw new SysException ( "Unexpected error in rollback", errors );
} catch ( HeurHazardException e ) {
errors.push ( e );
throw new SysException ( "Unexpected error in rollback", errors );
}
}
protected void rollbackWithStateCheck () throws java.lang.IllegalStateException,
SysException
{
//prevent concurrent commits - relevant if this is a timeout thread
ct_.localTestAndSetTransactionStateHandler ( this , new TxTerminatingStateHandler ( false , ct_ , this ) );
rollback();
}
// REMEMBER: don't synchronize the commit method, because it causes
// deadlocks
// (since this method also indirectly locks the coordinator and the FSM)
// This deadlock happens in particular when application commit interleaves
// with
// timeout-driven rollback (during preEnter, the rollback of this same
// handler
// is invoked)
protected void commit () throws SysException,
java.lang.IllegalStateException, RollbackException
{
Stack participants = null;
Stack synchronizations = null;
//prevent concurrent rollback due to timeout
ct_.localTestAndSetTransactionStateHandler ( this , new TxTerminatingStateHandler ( true , ct_ , this ) );
// first: check if local root; if so: add all local participants
// of all COMMITTED local subtxs to the coordinator.
// NOTE: this MUST be out of the synch block, since the coordinator
// is accessed in synch mode and hence can cause deadlocks.
// ALSO NOTE: this must be done BEFORE calling notifications
// to make sure that active recovery works for early prepares
if ( ct_.isLocalRoot () ) {
// add the tag as a summary heuristic to the coordinator.
// note: the same coordinator may have multiple tags
// from different local roots!
ct_.getCoordinatorImp ().addTag ( ct_.tag_ );
Enumeration enumm = ct_.getExtent ().getParticipants ().elements ();
while ( enumm.hasMoreElements () ) {
Participant part = (Participant) enumm.nextElement ();
addParticipant ( part );
}
}
// @todo move this to AFTER syncs were notified, to avoid pending
// thread associations?
if ( subtxs_ > 0 )
throw new IllegalStateException (
"active subtransactions exist" );
// BEFORE calling SubTxAwares, make sure that synchronizations
// are called. This is because the calling thread must still be
// associated with the tx at beforeCompletion, and the
// TM is listening as a SubTxAware!
// NOTE: doing this at the very beginning of commit
// also makes sure that the tx can still get new Participants
// from beforeCompletion work being done! This is required.
Synchronization sync = null;
Enumeration enumm = synchronizations_.elements ();
while ( enumm.hasMoreElements () ) {
sync = (Synchronization) enumm.nextElement ();
try {
sync.beforeCompletion ();
} catch ( RuntimeException error ) {
//see case 24246: rollback only
setRollbackOnly();
Configuration.logWarning ( "Unexpected error in beforeCompletion: " , error );
}
}
if ( ct_.getState().equals ( TxState.MARKED_ABORT ) ) {
//happens if synchronization has called setRollbackOnly
//-> rollback and throw error
rollback();
throw new RollbackException ( "The transaction was set to rollback only" );
}
// for loop to make sure that new registrations are possible
// during callback
for ( int i = 0; i < subtxawares_.size (); i++ ) {
SubTxAwareParticipant subtxaware = (SubTxAwareParticipant) subtxawares_
.get ( i );
subtxaware.committed ( ct_ );
// NOTE: this can NOT be done by coordinator imp.,
// since that one will not know which tx is locally done!
}
// change state handler to avoid other, concurrent modifications
// after we
// leave the synchronized block
ct_.localSetTransactionStateHandler ( new TxTerminatedStateHandler (
ct_, this, true ) );
}
protected void setRollbackOnly ()
{
StringHeuristicMessage msg = new StringHeuristicMessage (
"Transaction.setRollbackOnly was called." );
RollbackOnlyParticipant p = new RollbackOnlyParticipant ( msg );
try {
addParticipant ( p );
} catch ( IllegalStateException alreadyTerminated ) {
//happens in rollback after timeout - see case 27857
//ignore but log
Configuration.logDebug ( "Error during setRollbackOnly" , alreadyTerminated );
}
synchronized ( this ) {
ct_.localSetTransactionStateHandler ( new TxRollbackOnlyStateHandler ( ct_,
this ) );
}
}
public void committed ( CompositeTransaction subtx )
{
CompositeTransactionImp ct = (CompositeTransactionImp) subtx;
Extent toAdd = subtx.getTransactionControl ().getExtent ();
Extent target = ct_.getExtent ();
target.add ( toAdd );
// next: all LOCAL participants of the child subtx must be added to
// the 2PC set.
// Stack participants = ct.getParticipants ();
// addParticipants ( participants );
SubTransactionCoordinatorParticipant part = new SubTransactionCoordinatorParticipant (
ct.getCoordinatorImp () );
addParticipant ( part );
// decrement count of active subtxs
localDecSubTxCount();
}
public void rolledback ( CompositeTransaction subtx )
{
localDecSubTxCount();
}
protected abstract Object getState ();
/**
* @return List The subtx awares
*/
protected List getSubtxawares ()
{
return subtxawares_;
}
protected CompositeTransactionImp getCT ()
{
return ct_;
}
/**
* @return int The subtx count
*/
protected int getSubTransactionCount ()
{
return localGetSubTxCount();
}
/**
* @return Stack The synchronizations
*/
protected Stack getSynchronizations ()
{
return synchronizations_;
}
protected void addSynchronizations ( Stack synchronizations )
{
while ( !synchronizations.empty () ) {
Synchronization next = (Synchronization) synchronizations.pop ();
localPushSynchronization ( next );
}
}
}