All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.je.txn.TxnChain Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.txn;

import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.EnvironmentFailureReason;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.TTL;
import com.sleepycat.je.log.ErasedException;
import com.sleepycat.je.log.LogManager;
import com.sleepycat.je.log.WholeEntry;
import com.sleepycat.je.log.entry.LNLogEntry;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.VLSN;

/**
 * {@literal
 * TxnChain supports "txn rollback", which undoes the write operations for a
 * given txn to an arbitrary point. Txn rollback (and TxnChain construction)
 * is done in 2 occasions:
 * 1. During normal processing, when an ongoing txn must be rolled-back due to
 *    a syncup operation (see rep/txn/ReplayTxn.java).
 * 2. During recovery, to process a "rollback period" (see RollbackTracker.java)
 *
 * In the JE log, the logrecs that make up a txn are chained, but each logrec
 * contains undo info that refers to the pre-txn version of the associated
 * record, which may not be the immediately previous version, if the txn writes
 * the same record multiple times. For example, a log looks like this:
 *
 * lsn       key   data         abortlsn
 * 100       A      10          null_lsn (first instance of record A)
 * 150       B      100         null_lsn (first instance of record B)
 *  .....  txn begins .....
 * 200       A      20          100
 * 300       A    deleted       100
 * 400       B     200          150
 * 500       A      30          100
 * 600       C      10          null_lsn
 *
 * When reading the log, we can find all the records in the transaction. This
 * chain exists:
 * 500->400->300->200->null_lsn
 *
 * To rollback to an arbitrary entry in the transaction, we need a chain of all
 * the records that occupied a given BIN slot during the transaction.
 * chain. The key, data, and comparators are used to determine which records
 * hash to the same slot, mimicking the btree itself.
 *
 *
 *   300      400      500     600
 *    |        |        |       |
 *   \ /      \ /      \ /     \ /
 *    
 *   200      150      300    null_lsn   revertToLsn
 *                             true       revertKD
 * }
 */
public class TxnChain {

    private final EnvironmentImpl envImpl;

    /*
     * Null if we are in recovery. Otherwise, it points to the same map as the
     * undoDatabases field of the ReplayTxn.
     */
    private final Map undoDatabases;

    /*
     * Set of LSNs that will not be undone (i.e. the preserved portion of the
     * txn's log chain).
     */
    private final Set remainingLockedNodes;

    /*
     * For each logrec that will be undone, revertList contains a RevertInfo
     * obj, which refers to the record version to revert to. The list is 
     * ordered in reverse LSN order.
     */
    private final LinkedList revertList;

    /* The last applied VLSN in this txn, after rollback has occurred. */
    private VLSN lastValidVLSN;

    /*
     * Find the previous version for all entries in this transaction. Used by
     * recovery. This differs from the constructor used by syncup rollback
     * which is instigated by the txn. In this case, there is no cache of
     * DatabaseImpls.
     */
    public TxnChain(
        long lastLoggedLsn,
        long txnId,
        long matchpoint,
        EnvironmentImpl envImpl)  {

        this(lastLoggedLsn, txnId, matchpoint, null, envImpl);
    }

    /*
     * Find the previous version for all entries in this transaction.
     * DatabaseImpls used during txn chain creation are taken from the
     * transaction's undoDatabases cache.
     */
    public TxnChain(
        long lastLoggedLsn,
        long txnId,
        long matchpoint,
        Map undoDatabases,
        EnvironmentImpl envImpl)
        throws DatabaseException {

        LogManager logManager = envImpl.getLogManager();

        this.envImpl = envImpl;
        this.undoDatabases = undoDatabases;

        remainingLockedNodes = new HashSet();

        /*
         * A map that stores for each record R the revert info for the
         * latest R-logrec seen during the backwards traversal of the
         * txn chain done below.
         */
        TreeMap recordsMap =
            new TreeMap();

        revertList = new LinkedList();

        /*
         * Traverse this txn's entire logrec chain and record revert info
         * for each logrec in the chain. Start the traversal with the last
         * logrec generated by this txn and move backwards.
         */
        long currLsn = lastLoggedLsn;

        try {
            lastValidVLSN = VLSN.NULL_VLSN;

            while (currLsn != DbLsn.NULL_LSN) {

                WholeEntry wholeEntry =
                    logManager.getLogEntryAllowInvisible(currLsn);

                LNLogEntry currLogrec =
                    (LNLogEntry) wholeEntry.getEntry();

                DatabaseImpl dbImpl = getDatabaseImpl(currLogrec.getDbId());

                if (dbImpl == null) {

                    if (undoDatabases != null) {
                        throw EnvironmentFailureException.unexpectedState(
                            envImpl, // fatal error, this is a corruption
                            "DB missing during non-recovery rollback, dbId=" +
                            currLogrec.getDbId() + " txnId=" + txnId);
                    }

                    /*
                     * For recovery rollback, simply skip this entry when the
                     * DB has been deleted.  This has no impact on the chain
                     * for other LNs.  This LN will not be processed by
                     * recovery rollback, since RollbackTracker.rollback
                     * ignores LNs in deleted DBs.  [#22071] [#22052]
                     */
                    currLsn = currLogrec.getUserTxn().getLastLsn();
                    continue;
                }

                currLogrec.postFetchInit(dbImpl);

                try {
                    /*
                     * Let L be the current logrec, and let R and T be the
                     * record and txn associated with L. If T wrote R again
                     * after L, let Ln be the 1st R-logrec by T after L. 
                     */
                    CompareSlot recId = new CompareSlot(dbImpl, currLogrec);

                    RevertInfo ri = recordsMap.get(recId);

                    /*
                     * If Ln exists, update the RevertInfo created earlier for
                     * Ln so that it now refers to the L version of R.
                      */
                    if (ri != null) {
                        ri.revertLsn = currLsn;
                        ri.revertKD = false;
                        ri.revertPD = currLogrec.isDeleted();

                        ri.revertKey = 
                            (dbImpl.allowsKeyUpdates() ?
                             currLogrec.getKey() : null);

                        ri.revertData =
                            (currLogrec.isEmbeddedLN() ?
                             currLogrec.getData() : null);

                        ri.revertVLSN =
                            (currLogrec.isEmbeddedLN() ?
                             currLogrec.getLN().getVLSNSequence() :
                             VLSN.NULL_VLSN_SEQUENCE);

                        ri.revertExpiration = currLogrec.getExpiration();

                        ri.revertExpirationInHours =
                            currLogrec.isExpirationInHours();
                    }

                    /*
                     * If L will be rolled back, assume that it is the 1st
                     * R-logrec by T and thus set its revert info to refer
                     * to the pre-T version of R. 
                     */
                    if (DbLsn.compareTo(currLsn, matchpoint) > 0) {

                        ri = new RevertInfo(
                            currLogrec.getAbortLsn(),
                            currLogrec.getAbortKnownDeleted(),
                            currLogrec.getAbortKey(),
                            currLogrec.getAbortData(),
                            currLogrec.getAbortVLSN(),
                            currLogrec.getAbortExpiration(),
                            currLogrec.isAbortExpirationInHours());

                        revertList.add(ri);
                        recordsMap.put(recId, ri);

                    } else {

                        /*
                         * We are done with record R, so remove it from the
                         * map, if it is still there.
                         */
                        if (ri != null) {
                            recordsMap.remove(recId);
                        }

                        remainingLockedNodes.add(currLsn);

                        if (lastValidVLSN != null &&
                            lastValidVLSN.isNull() &&
                            wholeEntry.getHeader().getVLSN() != null &&
                            !wholeEntry.getHeader().getVLSN().isNull()) {

                            lastValidVLSN = wholeEntry.getHeader().getVLSN();
                        }
                    }

                    /* Move on to the previous logrec for this txn. */
                    currLsn = currLogrec.getUserTxn().getLastLsn();

                } finally {
                    releaseDatabaseImpl(dbImpl);
                }
            }
        } catch (FileNotFoundException|ErasedException e) {
            throw EnvironmentFailureException.promote(
                envImpl, EnvironmentFailureReason.LOG_INTEGRITY,
                "Problem finding intermediates for txn " + txnId +
                " at lsn " + DbLsn.getNoFormatString(currLsn), e);
        }
    }

    /**
     * Hide the details of whether we are getting a databaseImpl from the txn's
     * cache, or whether we're fetching it from the dbMapTree at recovery or 
     * during master->replica transition.
     */
    private DatabaseImpl getDatabaseImpl(DatabaseId dbId) {
        if (undoDatabases != null) {
            return undoDatabases.get(dbId);
        }

        return envImpl.getDbTree().getDb(dbId);
    }

    /** Only needed if we are in recovery, and fetched the DatabaseImpl. */
    private void releaseDatabaseImpl(DatabaseImpl dbImpl) {
        if (undoDatabases == null) {
            envImpl.getDbTree().releaseDb(dbImpl);
        }
    }

    /**
     * Returns LSNs for all nodes that should remain locked by the txn.  Note
     * that when multiple versions of a record were locked by the txn, the LSNs
     * of all versions are returned.  Only the latest version will actually be
     * locked.
     */
    public Set getRemainingLockedNodes() {
        return remainingLockedNodes;
    }

    /**
     * Return information about the next item on the transaction chain and
     * remove it from the chain.
     */
    public RevertInfo pop() {
        return revertList.remove();
    }

    public VLSN getLastValidVLSN() {
        return lastValidVLSN;
    }

    @Override
    public String toString() {
        return revertList.toString();
    }

    public static class RevertInfo {

        public long revertLsn;
        public boolean revertKD;
        public boolean revertPD;
        public byte[] revertKey;
        public byte[] revertData;
        public long revertVLSN;
        public int revertExpiration;
        public boolean revertExpirationInHours;

        RevertInfo(
            long revertLsn,
            boolean revertKD,
            byte[] revertKey,
            byte[] revertData,
            long revertVLSN,
            int revertExpiration,
            boolean revertExpirationInHours) {

            this.revertLsn = revertLsn;
            this.revertKD = revertKD;
            this.revertPD = false;
            this.revertKey = revertKey;
            this.revertData = revertData;
            this.revertVLSN = revertVLSN;
            this.revertExpiration = revertExpiration;
            this.revertExpirationInHours = revertExpirationInHours;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("revertLsn=");
            sb.append(DbLsn.getNoFormatString(revertLsn));
            sb.append(" revertKD=").append(revertKD);
            sb.append(" revertPD=").append(revertPD);
            if (revertKey != null) {
                sb.append(" revertKey=");
                sb.append(Key.getNoFormatString(revertKey));
            }
            if (revertData != null) {
                sb.append(" revertData=");
                sb.append(Key.getNoFormatString(revertData));
            }
            sb.append(" revertVLSN=").append(revertVLSN);
            sb.append(" revertExpires=");
            sb.append(TTL.formatExpiration(
                revertExpiration, revertExpirationInHours));
            return sb.toString();
        }
    }

    /**
     * Compare two keys using the appropriate comparator. Keys from different
     * databases should never be equal.
     */
    public static class CompareSlot implements Comparable {

        private final DatabaseImpl dbImpl;
        private final byte[] key;

        public CompareSlot(DatabaseImpl dbImpl, LNLogEntry undoEntry) {
            this(dbImpl, undoEntry.getKey());
        }

        private CompareSlot(DatabaseImpl dbImpl, byte[] key) {
            this.dbImpl = dbImpl;
            this.key = key;
        }

        public int compareTo(CompareSlot other) {
            int dbCompare = dbImpl.getId().compareTo(other.dbImpl.getId());
            if (dbCompare != 0) {
                /* LNs are from different databases. */
                return dbCompare;
            }

            /* Compare keys. */
            return Key.compareKeys(key, other.key, dbImpl.getKeyComparator());
        }

        @Override
        public boolean equals(Object other) {
            if (!(other instanceof CompareSlot)) {
                return false;
            }
            return compareTo((CompareSlot) other) == 0;
        }

        @Override
        public int hashCode() {

            /*
             * Disallow use of HashSet/HashMap/etc.  TreeSet/TreeMap/etc should
             * be used instead when a CompareSlot is used as a key.
             *
             * Because a comparator may be configured that compares only a part
             * of the key, a hash code cannot take into account the key or
             * data, because hashCode() must return the same value for two
             * objects whenever equals() returns true.  We could hash the DB ID
             * alone, but that would not produce an efficient hash table.
             */
            throw EnvironmentFailureException.unexpectedState
                ("Hashing not supported");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy