view src/org/dancres/blitz/txn/TxnState.java @ 35:6f68e94c1fb8 default tip

Add CondensedStats monitoring utility, equivalent to vmstat
author Dominic Cleal <dominic-cleal@cdo2.com>
date Thu, 05 Aug 2010 11:07:25 +0100
parents 3dc0c5604566
children
line wrap: on
line source

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");
            }
        }
    }
}