Mercurial > hg > blitz_condensed
diff src/org/dancres/blitz/txn/TxnState.java @ 0:3dc0c5604566
Initial checkin of blitz 2.0 fcs - no installer yet.
author | Dan Creswell <dan.creswell@gmail.com> |
---|---|
date | Sat, 21 Mar 2009 11:00:06 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/dancres/blitz/txn/TxnState.java Sat Mar 21 11:00:06 2009 +0000 @@ -0,0 +1,295 @@ +package org.dancres.blitz.txn; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.logging.Level; + +import net.jini.core.transaction.TransactionException; +import net.jini.core.transaction.UnknownTransactionException; +import net.jini.core.transaction.server.TransactionConstants; + +import org.dancres.blitz.disk.DiskTxn; +import org.dancres.blitz.notify.EventQueue; +import org.dancres.blitz.notify.QueueEvent; +import org.dancres.blitz.task.Task; +import org.dancres.blitz.task.Tasks; + +/* + * We only need to post QueueEvent.TRANSACTION_ENDED during live work, not + * during recovery as it will only be relevant to active notifies. When + * we restart the only active notifies left would be those not interested in + * the event because they were registered with a null transaction. + * + * Given the above, we can post the event from doFinalize which will be + * called in TxnManager as part of "live" operations. This neatly ensures + * we will only do this work outside of recovery. + * + * We will post TRANSACTION_ENDED even for null transactions and for each + * such event scan the generators and kill any that are anchored against + * the transaction - this should be done via a new method to test this. + * + * EventGenerators should only profess interest if they aren't already tainted + * as they do with canSee etc. + * + * Note we needn't call kill we could just pass the event into all generators + * and have them taint and then kill themselves (assuming they no longer + * dispatch CleanTask). We could then strip out TxnNotify's but we would need + * to add support for TRANSACTION_ENDED to each canSee method. + */ + +/** + Contains all state associated with a particular transaction identified + by TxnId. + + @todo Deal with TRANSACTION_ENDED etc as above + */ +public class TxnState implements java.io.Serializable { + static final long serialVersionUID = 2855252304868267667L; + + private int theState = TransactionConstants.ACTIVE; + + private TxnId theId; + + private boolean nonDestructive; + + /* + Typical transaction might be take/write or a few ops more, so we + allocate accordingly + + @todo Decide if this is appropriate/worth doing + */ + private ArrayList theOperations = new ArrayList(5); + + TxnState(TxnId anId) { + theId = anId; + } + + TxnState(TxnId anId, boolean isIdentity) { + theId = anId; + nonDestructive = isIdentity; + } + + public TxnId getId() { + return theId; + } + + int getStatus() throws TransactionException { + synchronized(this) { + return theState; + } + } + + public void add(TxnOp anOp) throws TransactionException { + synchronized (this) { + if (theState != TransactionConstants.ACTIVE) { + TxnManager.theLogger.log(Level.SEVERE, + "Txn not in active state at addOp"); + throw new TransactionException("Transaction no longer active"); + } else { + theOperations.add(anOp); + } + } + } + + /** + * A move to voting state indicates this transaction is going to be resolved + * and thus cannot have any further actions assigned to it. Note that + * voting state will only be asserted if the transaction is currently + * active. Voting is a transient state and only exists temporarily whilst + * we move to a resolved state. It's only benefit is that it renders + * a transaction's state unchangeable and allows us to make binding + * decisions such as whether we need to log to disk. + */ + int vote() throws TransactionException { + int myState; + + synchronized (this) { + + if (theState == TransactionConstants.ACTIVE) { + theState = TransactionConstants.VOTING; + } + + myState = theState; + } + + return myState; + } + + /** + * @todo Decide if ths DiskTxn is required here - probably not? + */ + int prepare(boolean needsRestore) + throws UnknownTransactionException, IOException { + + if (needsRestore) { + /* + If we're doing restore, we're single threaded and don't need + the protection of the lock (getLock()) + */ + // DiskTxn myTxn = DiskTxn.newTxn(); + + for (int i = (theOperations.size() - 1); i > -1; i--) { + TxnOp myOp = (TxnOp) theOperations.get(i); + myOp.restore(this); + } + + // myTxn.commit(); + } + + int myStatus; + + synchronized(this) { + if (theState == TransactionConstants.VOTING) { + theState = TransactionConstants.PREPARED; + } + + /* + The state may not have been ACTIVE, we might already have + been prepared or even got to commited/aborted and we need to + reflect that in the status we return. + */ + myStatus = theState; + + } + + return myStatus; + } + + void commit() + throws UnknownTransactionException, IOException { + + synchronized(this) { + if (theState != TransactionConstants.PREPARED) { + TxnManager.theLogger.log(Level.SEVERE, + "Txn not prepared before commit"); + throw new UnknownTransactionException(); + } + + /* + * We can afford to set the state beforehand because if this doesn't + * work we have serious problems and it's very unlikely a second attempt + * will succeed/do any good. + */ + theState = TransactionConstants.COMMITTED; + + /* + * Having changed the state we can be sure no one else will do the + * same thing again and it's better to not hold the lock across the + * below which might require I/O + */ + } + + /* + * MUST BE DONE IN REVERSE ORDER - WE WANT A TAKE TO BE APPLIED + * BEFORE WE HIT THE PREVIOUS WRITE. THIS ALLOWS US TO GENERATE + * APPROPRIATE EVENTS FOR WRITES READY FOR NOTIFY - i.e. we + * want the disk system to be aware that something has been + * deleted so that we can test that state and abandon generating + * a write event in those cases. This saves adding code/structure + * to search other operations to verify whether a write generates + * an event or not. + */ + + // StringBuffer myTxn = new StringBuffer(theId + " "); + + for (int i = (theOperations.size() - 1); i > -1; i--) { + TxnOp myOp = (TxnOp) theOperations.get(i); + myOp.commit(this); + + // myTxn = myTxn.append(myOp.toString() + " "); + } + + // TxnManager.theLogger.log(Level.INFO, myTxn.toString()); + } + + void abort() + throws UnknownTransactionException, IOException { + + synchronized(this) { + /* + Transaction pinger may attempt abort on a just finalized transaction + cos the transaction manager has forgotten about it. We need to + handle that by refusing to abort when we're finalized (committed + or aborted). + */ + if ((theState == TransactionConstants.COMMITTED) || + (theState == TransactionConstants.ABORTED)) { + TxnManager.theLogger.log(Level.SEVERE, + "Txn already finalized"); + throw new UnknownTransactionException(); + } + + /* + * We can afford to set the state beforehand because if this doesn't + * work we have serious problems and it's very unlikely a second attempt + * will succeed/do any good. + */ + theState = TransactionConstants.ABORTED; + + /* + * Having changed the state we can be sure no one else will do the + * same thing again and it's better to not hold the lock across the + * below which might require I/O + */ + } + + Iterator myOps = theOperations.iterator(); + + // No events generated so we can use whatever order we like + while (myOps.hasNext()) { + TxnOp myOp = (TxnOp) myOps.next(); + myOp.abort(this); + } + } + + public boolean isIdentity() { + return nonDestructive; + } + + public boolean isNull() { + return theId.isNull(); + } + + public boolean hasNoOps() { + return (theOperations.size() == 0); + } + + public String toString() { + StringBuffer myOps = new StringBuffer(); + + for (int i = 0; i < theOperations.size(); i++) { + // For the first operation, we adopt the time of the + // enclosing command so drop the hyphen + if (i == 0) + myOps.append(" " + theOperations.get(i).toString() + " : " + + theId + "\n"); + else + myOps.append("- : " + theOperations.get(i).toString() + " : " + + theId + "\n"); + } + + return myOps.toString(); + } + + void doFinalize() { + /* + * Signal any associated listeners - we only want to do this during live + * operations + */ + QueueEvent myEvent = + new QueueEvent(QueueEvent.TRANSACTION_ENDED, + this, null); + EventQueue.get().add(myEvent); + } + + public void test() throws TransactionException { + synchronized (this) { + if (theState != TransactionConstants.ACTIVE) { + TxnManager.theLogger.log(Level.SEVERE, + "Txn not in active state at addOp"); + throw new TransactionException("Transaction no longer active"); + } + } + } +}