/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2005
*      Sleepycat Software.  All rights reserved.
*
* $Id: TxnManager.java,v 1.45 2004/12/22 14:11:37 linda Exp $
*/

package com.sleepycat.je.txn;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockStats;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.je.TransactionStats;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.latch.Latch;
import com.sleepycat.je.utilint.DbLsn;

/**
 * Class to manage transactions.  Basically a Set of all transactions with add
 * and remove methods and a latch around the set.
 */
public class TxnManager {

    /* 
     * All NullTxns share the same id so as not to eat from the id number
     * space.
     */
    static final long NULL_TXN_ID = -1; 
    private static final String DEBUG_NAME = TxnManager.class.getName();
    
    private LockManager lockManager;
    private EnvironmentImpl env;
    private Latch allTxnLatch;
    private Set allTxns;
    private long lastUsedTxnId;
    private int nActiveSerializable;
    
    /* Locker Stats */
    private int numCommits;
    private int numAborts;

    public TxnManager(EnvironmentImpl env) 
    	throws DatabaseException {
        if (env.getConfigManager().
            getBoolean(EnvironmentParams.ENV_FAIR_LATCHES)) {
            lockManager = new LatchedLockManager(env);
        } else {
            lockManager = new SyncedLockManager(env);
        }

        this.env = env;
        allTxns = new HashSet();
        allTxnLatch = new Latch(DEBUG_NAME, env);

        numCommits = 0;
        numAborts = 0;
        lastUsedTxnId = 0;
    }

    /**
     * Set the txn id sequence.
     */
    synchronized public void setLastTxnId(long lastId) {
        this.lastUsedTxnId = lastId;
    }

    /**
     * Get the last used id, for checkpoint info.
     */
    public synchronized long getLastTxnId() {
        return lastUsedTxnId;
    }

    /**
     * Get the next transaction id to use.
     */
    synchronized long incTxnId() {
        return ++lastUsedTxnId;
    }
    
    /**
     * Create a new transaction.
     * @param parent for nested transactions, not yet supported
     * @param txnConfig specifies txn attributes
     * @return the new txn
     */
    public Txn txnBegin(Transaction parent, TransactionConfig txnConfig) 
        throws DatabaseException {

        if (parent != null) {
            throw new DatabaseException
		("Nested transactions are not supported yet.");
        }
        
        return new Txn(env, txnConfig);
    }

    /**
     * Give transactions and environment access to lock manager.
     */
    public LockManager getLockManager() {
        return lockManager;
    }

    /**
     * Called when txn is created.
     */
    void registerTxn(Txn txn)
        throws DatabaseException {

        allTxnLatch.acquire();
        allTxns.add(txn);
        env.getMemoryBudget().updateCacheMemoryUsage(txn.getInMemorySize());
        if (txn.isSerializableIsolation()) {
            nActiveSerializable++;
        }
        allTxnLatch.release();
    }

    /**
     * Called when txn ends.
     */
    void unRegisterTxn(Txn txn, boolean isCommit) 
        throws DatabaseException {

        allTxnLatch.acquire();
        allTxns.remove(txn);
        env.getMemoryBudget().updateCacheMemoryUsage(0-txn.getInMemorySize());
        if (isCommit) {
            numCommits++;
        } else {
            numAborts++;
        }
        if (txn.isSerializableIsolation()) {
            nActiveSerializable--;
        }
        allTxnLatch.release();
    }

    /**
     * Returns whether there are any active serializable transactions,
     * excluding the transaction given (if non-null).  This is intentionally
     * returned without latching, since latching would not make the act of
     * reading an integer more atomic than it already is.
     */
    public boolean
        areOtherSerializableTransactionsActive(Locker excludeLocker) {
        int exclude =
            (excludeLocker != null &&
             excludeLocker.isSerializableIsolation()) ?
            1 : 0;
        return (nActiveSerializable - exclude > 0);
    }

    /**
     * Get the earliest LSN of all the active transactions, for checkpoint.
     */
    public long getFirstActiveLsn() 
        throws DatabaseException {

        /* 
         * Note that the latching hierarchy calls for getting allTxnLatch
         * first, then synchronizing on individual txns.
         */
        allTxnLatch.acquire();
        Iterator iter = allTxns.iterator();
        long firstActive = DbLsn.NULL_LSN;
        while(iter.hasNext()) {
            long txnFirstActive = ((Txn) iter.next()).getFirstActiveLsn();
            if (firstActive == DbLsn.NULL_LSN) {
                firstActive = txnFirstActive;
            } else if (txnFirstActive != DbLsn.NULL_LSN) {
                if (DbLsn.compareTo(txnFirstActive, firstActive) < 0) {
                    firstActive = txnFirstActive;
                }
            }
        }
        allTxnLatch.release();
        return firstActive;
    }

    /*
     * Statistics
     */

    /**
     * Collect transaction related stats.
     */
    public TransactionStats txnStat(StatsConfig config)
        throws DatabaseException {

        TransactionStats stats = new TransactionStats();
        allTxnLatch.acquire();
        stats.setNCommits(numCommits);
        stats.setNAborts(numAborts);
        stats.setNActive(allTxns.size());
        TransactionStats.Active[] activeSet =
	    new TransactionStats.Active[stats.getNActive()];
        stats.setActiveTxns(activeSet);
        Iterator iter = allTxns.iterator();
        int i = 0;
        while (iter.hasNext()) {
            Locker txn = (Locker) iter.next();
            activeSet[i] =
                new TransactionStats.Active(txn.toString(), txn.getId(), 0);
            i++;
        }
        if (config.getClear()) {
            numCommits = 0;
            numAborts = 0;
        }
        allTxnLatch.release();
        return stats;
    }

    /**
     * Collect lock related stats.
     */
    public LockStats lockStat(StatsConfig config) 
        throws DatabaseException {

        return lockManager.lockStat(config);
    }
}
