/* * Copyright (c) 2002-2009 "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 Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.transaction; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; class TransactionImpl implements Transaction { private static Logger log = Logger.getLogger( TransactionImpl.class .getName() ); private static final int RS_ENLISTED = 0; private static final int RS_SUSPENDED = 1; private static final int RS_DELISTED = 2; private static final int RS_READONLY = 3; // set in prepare private final byte globalId[]; private int status = Status.STATUS_ACTIVE; private boolean active = true; private boolean globalStartRecordWritten = false; private final LinkedList<ResourceElement> resourceList = new LinkedList<ResourceElement>(); private List<Synchronization> syncHooks = new ArrayList<Synchronization>(); private final int eventIdentifier; private final TxManager txManager; TransactionImpl( TxManager txManager ) { this.txManager = txManager; globalId = XidImpl.getNewGlobalId(); eventIdentifier = txManager.getNextEventIdentifier(); } Integer getEventIdentifier() { return eventIdentifier; } byte[] getGlobalId() { return globalId; } public synchronized String toString() { StringBuffer txString = new StringBuffer( "Transaction(" + eventIdentifier + ")[" + txManager.getTxStatusAsString( status ) + ",Resources=" + resourceList.size() + "]" ); // Iterator<ResourceElement> itr = resourceList.iterator(); // while ( itr.hasNext() ) // { // txString.append( itr.next().toString() ); // if ( itr.hasNext() ) // { // txString.append( "," ); // } // } return txString.toString(); } public synchronized void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, IllegalStateException, SystemException { // make sure tx not suspended txManager.commit(); } boolean isGlobalStartRecordWritten() { return globalStartRecordWritten; } public synchronized void rollback() throws IllegalStateException, SystemException { // make sure tx not suspended txManager.rollback(); } public synchronized boolean enlistResource( XAResource xaRes ) throws RollbackException, IllegalStateException, SystemException { if ( xaRes == null ) { throw new IllegalArgumentException( "Null xa resource" ); } if ( status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING ) { try { if ( resourceList.size() == 0 ) { if ( !globalStartRecordWritten ) { txManager.writeStartRecord( globalId ); globalStartRecordWritten = true; } // byte branchId[] = txManager.getBranchId( xaRes ); Xid xid = new XidImpl( globalId, branchId ); resourceList.add( new ResourceElement( xid, xaRes ) ); xaRes.start( xid, XAResource.TMNOFLAGS ); try { txManager.getTxLog().addBranch( globalId, branchId ); } catch ( IOException e ) { e.printStackTrace(); log.severe( "Error writing transaction log" ); txManager.setTmNotOk(); throw new SystemException( "TM encountered a problem, " + " error writing transaction log," + e ); } return true; } Xid sameRmXid = null; Iterator<ResourceElement> itr = resourceList.iterator(); while ( itr.hasNext() ) { ResourceElement re = itr.next(); if ( sameRmXid == null && re.getResource().isSameRM( xaRes ) ) { sameRmXid = re.getXid(); } if ( xaRes == re.getResource() ) { if ( re.getStatus() == RS_SUSPENDED ) { xaRes.start( re.getXid(), XAResource.TMRESUME ); } else { // either enlisted or delisted // is TMJOIN correct then? xaRes.start( re.getXid(), XAResource.TMJOIN ); } re.setStatus( RS_ENLISTED ); return true; } } if ( sameRmXid != null ) // should we join? { resourceList.add( new ResourceElement( sameRmXid, xaRes ) ); xaRes.start( sameRmXid, XAResource.TMJOIN ); } else // new branch { // ResourceElement re = resourceList.getFirst(); byte branchId[] = txManager.getBranchId( xaRes ); Xid xid = new XidImpl( globalId, branchId ); resourceList.add( new ResourceElement( xid, xaRes ) ); xaRes.start( xid, XAResource.TMNOFLAGS ); try { txManager.getTxLog().addBranch( globalId, branchId ); } catch ( IOException e ) { e.printStackTrace(); log.severe( "Error writing transaction log" ); txManager.setTmNotOk(); throw new SystemException( "TM encountered a problem, " + " error writing transaction log," + e ); } } return true; } catch ( XAException e ) { e.printStackTrace(); log.severe( "Unable to enlist resource[" + xaRes + "]" ); status = Status.STATUS_MARKED_ROLLBACK; return false; } } else if ( status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK || status == Status.STATUS_MARKED_ROLLBACK ) { throw new RollbackException( "Tx status is: " + txManager.getTxStatusAsString( status ) ); } throw new IllegalStateException( "Tx status is: " + txManager.getTxStatusAsString( status ) ); } public synchronized boolean delistResource( XAResource xaRes, int flag ) throws IllegalStateException { if ( xaRes == null ) { throw new IllegalArgumentException( "Null xa resource" ); } if ( flag != XAResource.TMSUCCESS && flag != XAResource.TMSUSPEND && flag != XAResource.TMFAIL ) { throw new IllegalArgumentException( "Illegal flag: " + flag ); } ResourceElement re = null; Iterator<ResourceElement> itr = resourceList.iterator(); while ( itr.hasNext() ) { ResourceElement reMatch = itr.next(); if ( reMatch.getResource() == xaRes ) { re = reMatch; break; } } if ( re == null ) { return false; } if ( status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK ) { try { xaRes.end( re.getXid(), flag ); if ( flag == XAResource.TMSUSPEND || flag == XAResource.TMFAIL ) { re.setStatus( RS_SUSPENDED ); } else { re.setStatus( RS_DELISTED ); } return true; } catch ( XAException e ) { e.printStackTrace(); log.severe( "Unable to delist resource[" + xaRes + "]" ); status = Status.STATUS_MARKED_ROLLBACK; return false; } } throw new IllegalStateException( "Tx status is: " + txManager.getTxStatusAsString( status ) ); } // TODO: figure out if this needs syncrhonization or make status volatile public int getStatus() // throws SystemException { return status; } void setStatus( int status ) { this.status = status; } private boolean beforeCompletionRunning = false; private List<Synchronization> syncHooksAdded = new ArrayList<Synchronization>(); public synchronized void registerSynchronization( Synchronization s ) throws RollbackException, IllegalStateException { if ( s == null ) { throw new IllegalArgumentException( "Null parameter" ); } if ( status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING || status == Status.STATUS_MARKED_ROLLBACK ) { if ( !beforeCompletionRunning ) { syncHooks.add( s ); } else { // avoid CME if synchronization is added in before completion syncHooksAdded.add( s ); } } else if ( status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK ) { throw new RollbackException( "Tx status is: " + txManager.getTxStatusAsString( status ) ); } else { throw new IllegalStateException( "Tx status is: " + txManager.getTxStatusAsString( status ) ); } } synchronized void doBeforeCompletion() { beforeCompletionRunning = true; try { for ( Synchronization s : syncHooks ) { try { s.beforeCompletion(); } catch ( Throwable t ) { log.log( Level.WARNING, "Caught exception from tx syncronization[" + s + "] beforeCompletion()", t ); } } // execute any hooks added since we entered doBeforeCompletion while ( !syncHooksAdded.isEmpty() ) { List<Synchronization> addedHooks = syncHooksAdded; syncHooksAdded = new ArrayList<Synchronization>(); for ( Synchronization s : addedHooks ) { s.beforeCompletion(); syncHooks.add( s ); } } } finally { beforeCompletionRunning = false; } } synchronized void doAfterCompletion() { for ( Synchronization s : syncHooks ) { try { s.afterCompletion( status ); } catch ( Throwable t ) { log.log( Level.WARNING, "Caught exception from tx syncronization[" + s + "] afterCompletion()", t ); } } syncHooks = null; // help gc } public void setRollbackOnly() throws IllegalStateException { if ( status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING || status == Status.STATUS_PREPARED || status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_ROLLING_BACK ) { status = Status.STATUS_MARKED_ROLLBACK; } else { throw new IllegalStateException( "Tx status is: " + txManager.getTxStatusAsString( status ) ); } } public boolean equals( Object o ) { if ( !(o instanceof TransactionImpl) ) { return false; } TransactionImpl other = (TransactionImpl) o; return this.eventIdentifier == other.eventIdentifier; } private volatile int hashCode = 0; public int hashCode() { if ( hashCode == 0 ) { hashCode = 3217 * eventIdentifier; } return hashCode; } int getResourceCount() { return resourceList.size(); } private boolean isOnePhase() { if ( resourceList.size() == 0 ) { log.severe( "Detected zero resources in resourceList" ); return true; } // check for more than one unique xid Iterator<ResourceElement> itr = resourceList.iterator(); Xid xid = itr.next().getXid(); while ( itr.hasNext() ) { if ( !xid.equals( itr.next().getXid() ) ) { return false; } } return true; } void doCommit() throws XAException, SystemException { boolean onePhase = isOnePhase(); boolean readOnly = true; if ( !onePhase ) { // prepare status = Status.STATUS_PREPARING; LinkedList<Xid> preparedXids = new LinkedList<Xid>(); Iterator<ResourceElement> itr = resourceList.iterator(); while ( itr.hasNext() ) { ResourceElement re = itr.next(); if ( !preparedXids.contains( re.getXid() ) ) { preparedXids.add( re.getXid() ); int vote = re.getResource().prepare( re.getXid() ); if ( vote == XAResource.XA_OK ) { readOnly = false; } else if ( vote == XAResource.XA_RDONLY ) { re.setStatus( RS_READONLY ); } else { // rollback tx status = Status.STATUS_MARKED_ROLLBACK; return; } } else { // set it to readonly, only need to commit once re.setStatus( RS_READONLY ); } } status = Status.STATUS_PREPARED; } // commit if ( !onePhase && readOnly ) { status = Status.STATUS_COMMITTED; return; } if ( !onePhase ) { try { txManager.getTxLog().markAsCommitting( getGlobalId() ); } catch ( IOException e ) { e.printStackTrace(); log.severe( "Error writing transaction log" ); txManager.setTmNotOk(); throw new SystemException( "TM encountered a problem, " + " error writing transaction log," + e ); } } status = Status.STATUS_COMMITTING; Iterator<ResourceElement> itr = resourceList.iterator(); while ( itr.hasNext() ) { ResourceElement re = itr.next(); if ( re.getStatus() != RS_READONLY ) { re.getResource().commit( re.getXid(), onePhase ); } } status = Status.STATUS_COMMITTED; } void doRollback() throws XAException { status = Status.STATUS_ROLLING_BACK; LinkedList<Xid> rolledbackXids = new LinkedList<Xid>(); Iterator<ResourceElement> itr = resourceList.iterator(); while ( itr.hasNext() ) { ResourceElement re = itr.next(); if ( !rolledbackXids.contains( re.getXid() ) ) { rolledbackXids.add( re.getXid() ); re.getResource().rollback( re.getXid() ); } } status = Status.STATUS_ROLLEDBACK; } private static class ResourceElement { private Xid xid = null; private XAResource resource = null; private int status; ResourceElement( Xid xid, XAResource resource ) { this.xid = xid; this.resource = resource; status = RS_ENLISTED; } Xid getXid() { return xid; } XAResource getResource() { return resource; } int getStatus() { return status; } void setStatus( int status ) { this.status = status; } public String toString() { String statusString = null; switch ( status ) { case RS_ENLISTED: statusString = "ENLISTED"; break; case RS_DELISTED: statusString = "DELISTED"; break; case RS_SUSPENDED: statusString = "SUSPENDED"; break; case RS_READONLY: statusString = "READONLY"; break; default: statusString = "UNKNOWN"; } return "Xid[" + xid + "] XAResource[" + resource + "] Status[" + statusString + "]"; } } synchronized void markAsActive() { if ( active ) { throw new IllegalStateException( "Transaction[" + this + "] already active" ); } active = true; } synchronized void markAsSuspended() { if ( !active ) { throw new IllegalStateException( "Transaction[" + this + "] already suspended" ); } active = false; } }