com.sleepycat.je.recovery.RollbackTracker 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.recovery;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.cleaner.RecoveryUtilizationTracker;
import com.sleepycat.je.dbi.DbTree;
import com.sleepycat.je.dbi.EnvironmentFailureReason;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.FileManager;
import com.sleepycat.je.log.LNFileReader;
import com.sleepycat.je.tree.TreeLocation;
import com.sleepycat.je.txn.RollbackEnd;
import com.sleepycat.je.txn.RollbackStart;
import com.sleepycat.je.txn.TxnChain;
import com.sleepycat.je.txn.TxnManager;
import com.sleepycat.je.txn.UndoReader;
import com.sleepycat.je.txn.TxnChain.RevertInfo;
import com.sleepycat.je.utilint.DbLsn;
/**
* {@literal
* RollbackTracker is used to detect rollback periods in the log that are the
* result of HA replica syncups. These rollback periods affect how LNs should
* be processed at recovery. Rollbacks differ from aborts in that a rollback
* returns a LN to its previous version, whether intra or inter-txnal, while an
* abort always returns an LN to its pre-txn version.
*
* What is a Rollback Period?
* --------------------------
* The rollback represents the logical truncation of the log. Any transactional
* LNs in that rollback period should be undone, even if they are ultimately
* part of a committed transaction. See the wiki page on Syncup Recovery for
* the full design. See com.sleepycat.je.rep.impl.node.Replay.rollback for the
* steps taken at the time of the rollback.
*
* A RollbackStart record is logged at the start of any rollback, and a
* RollbackEnd is logged at the completion of a rollback. RollbackStarts refer
* to a matchpoint and the area between the matchpoint and the RollbackStart is
* the rollback period.The RollbackTracker peruses RollbackStarts and Ends and
* generates a map of the rollback periods.
*
* RollbackStarts and their starting Matchpoints can be nested or can be
* distinct, but several invariants are in place and can be enforced. For
* example:
*
* LSN
* ---
* 100 txnA commit
*
* 200 txnB abort
* 250 LN for txnC
* 300 txnC abort
* ..
* 400 RollbackStart A (starting matchpoint = 200)
* 500 RollbackEnd A
* ...
* 600 RollbackStart B (starting matchpoint = 200)
* 700 RollbackStart C (starting matchpoint = 100)
* 800 RollbackEnd C
* 900 txnD abort
* 1000 RollbackStart D (starting matchpoint = 900)
*
* This log creates four rollback periods
* 1) LSN 100 -> 700 (defined by RollbackStart C). This has two rollback
* periods nested within.
* 2) LSN 200 -> 400, (defined by RollbackStart A) nested within B
* 3) LSN 200 -> 600, (defined by RollbackStart B) nested within C
* 4) LSN 1000 -> 900 (defined by RolbackStart D)
* - There can be no commits or aborts within a rollback period, because we
* shouldn't have executed a soft recovery that undid a commit or abort. in
* the rollback period.
*
* - There can be no LN_TXs between a RollbackStart and its matching
* RollbackEnd (should be no LN write operations happening during the syncup.)
* However, there might be INs written by a checkpoint, and eviction.
*
* - The recovery period should never see a RollbackEnd without its matching
* RollbackStart record, though it is possible to see a RollbackStart that has
* no RollbackEnd.
*
* - There can never be any overlapping, or intersection of periods, because a
* rollback period is supposed to be like a truncation of the log. Since that
* log is "gone", a subsequent rollback shouldn't find a matchpoint inside
* another rollback period.
*
* - A child period must be wholly contained between the parent's matchpoint
* and RollbackStart. This is simply due to the way rollbacks occur. A parent
* rollback has a Matchpoint <= the child's Matchpoint or it wouldn't be
* nested. The parent's RollbackStart > the child's RollbackEnd, since the
* parent occurs after the child in time.
*
* The Rollback tracker keeps a list of all the rollback periods. Some are
* distinct, some are nested.
*
* Recovery processing and rollback periods
* ----------------------------------------
* The actions taken at a rollback may not have been made persistent to the
* log, so at recovery, we literally mimic and replay these two steps: (a) make
* sure invisible log entries have their invisible bit on and (b) make sure all
* INs reflect the correct LNs. All use of the rollback periods and tracker
* take place on the backwards scans. The RollbackStart and End entries are
* read during the first recovery undo pass When a rollback period is found, a
* transaction chain is constructed for each transaction that was active in the
* period, to support a repeat of the actions taken originally.
*
* The first undo pass, for the mapping tree, has to construct a map of
* recovery periods. Since the mapping tree only has MapLNs, and we never write
* any txnal MapLNs, that first pass does not encounter any txnal LNs. The
* next two undo passes consult the rollback period map to determine if an LN
* needs to be rolledback, or just treated like other LNs.
*
* Rollback periods that precede the checkpoint start can be ignored, because
* we can be assured that all the INs and LNs modified by that rollback were
* made persistent by the checkpoint. Ignoring such periods is required, and
* is not just an optimization, because it guarantees that we will not need to
* create a transaction chain that needs to traverse the log beyond the first
* active lsn. A rollback period precedes the checkpoint if its RollbackEnd is
* before the checkpoint start.
*
* When a rollback period overlaps CkptStart and we recover, we are guaranteed
* that the undo passes will process all LNs in the rollback period, because
* they are >= to the firstActiveLEnd of the checkpoint.
*
* The lastActiveLSN for the checkpoint will be <= the LSN of the first LN of
* any transaction that is being rolled back at the time of CkptStart, since
* these transactions were still active at that time.
*
* No file containing a transaction rolled back in the recovery interval, or a
* file containing the abortLSN of such a transaction, will be deleted by the
* cleaner. An active transaction prevents cleaning of its first logged entry
* and beyond. The LN of the abortLSN will be locked, which prevents it from
* being cleaned.
*
* All the work lies on the undo side. Recovery redo only needs to ignore
* invisible log entries, because we know that the undo pass applied the
* invisible bit where needed. Note that the undo pass must be sure to write
* the invisible bits after the pass, before redo attempts to read the log.
*
* Each rollback LN_TX belongs to a single rollback period. When periods are
* nested, the LN_TX belongs to the closest rollback period that encompasses
* it.
* Using the example above,
* a LN at lsn 350 belongs to rollback period A
* a LN at lsn 550 belongs to rollback period B
* a LN at lsn 650 belongs to rollback period C
* It uses its rollback period's txn chain to find its previous version.
* }
*/
public class RollbackTracker {
private final EnvironmentImpl envImpl;
private long checkpointStart;
/* for assertions. */
private boolean firstUndoPass;
/*
* List of lsns that were made invisible and need to be fsynced, from
* this recovery.
*
* singlePassLsns are collected for a single recovery pass. After that
* pass, the lsns must be written to the log, so that the next redo
* recovery pass properly skips over invisible lsns, but it need not
* do a fsync. After each pass, the file numbers involved are added to
* recoveryFilesToSync.
*
* After recovery is finished, all file that have re-flipped invisible bits
* are fsynced. Hopefully, the OS may have fsynced some, and waiting until
* the end to fsync will be an optimization.
*/
private final Set recoveryFilesToSync;
private List singlePassInvisibleLsns;
/*
* Used only for the first construction pass. This is the rollback
* period that we have just found.
*/
private RollbackPeriod underConstructionPeriod;
/* Top level list of rollback periods */
private final List periodList;
RollbackTracker(EnvironmentImpl envImpl) {
this.envImpl = envImpl;
periodList = new ArrayList();
checkpointStart = DbLsn.NULL_LSN;
recoveryFilesToSync = new HashSet();
singlePassInvisibleLsns = new ArrayList();
}
/**
* Construction Pass: A RollbackEnd is seen, make new period.
*/
void register(RollbackEnd rollbackEnd, long rollbackEndLSN) {
assertFirstPass(rollbackEndLSN);
if ((underConstructionPeriod != null) &&
(underConstructionPeriod.makeNestedPeriod(rollbackEnd,
rollbackEndLSN))) {
return;
}
underConstructionPeriod = new RollbackPeriod(this,
rollbackEnd,
rollbackEndLSN);
periodList.add(underConstructionPeriod);
}
/**
* Construction Pass: A RollbackStart is seen. Might be the matching
* one for the current period, or it might be a new period.
*/
void register(RollbackStart rollbackStart, long rollbackStartLSN) {
assertFirstPass(rollbackStartLSN);
/* There's no rollback period going on, start a new one. */
if ((underConstructionPeriod != null) &&
(underConstructionPeriod.makeNestedPeriod(rollbackStart,
rollbackStartLSN))) {
return;
}
underConstructionPeriod = new RollbackPeriod(this,
rollbackStart,
rollbackStartLSN);
periodList.add(underConstructionPeriod);
}
/**
* A TxnCommit showed up on the construction pass. If it's a replicated
* txn, check if it's in a valid place. It should not be within the
* rollback period.
*
* Omit commits for internal, non-replicated transactions from this check.
*/
void checkCommit(long commitLSN, long txnId) {
assertFirstPass(commitLSN);
if (!TxnManager.isReplicatedTxn(txnId)) {
return;
}
if (underConstructionPeriod == null) {
return;
}
if (underConstructionPeriod.contains(commitLSN)) {
underConstructionPeriod.fail("Commit at " +
DbLsn.getNoFormatString(commitLSN) +
" is within rollback period.");
}
}
/*
* Set the checkpoint start before we begin marking rollback periods, so we
* know that we can ignore periods that are before the checkpoint start.
*/
void setCheckpointStart(long lsn) {
checkpointStart = lsn;
}
long getCheckpointStart() {
return checkpointStart;
}
EnvironmentImpl getEnvImpl() {
return envImpl;
}
/* For unit tests */
List getPeriodList() {
return periodList;
}
void setFirstPass(boolean firstUndoPass) {
this.firstUndoPass = firstUndoPass;
}
/**
* A Scanner is a cursor over the tracker's rollback periods.
*/
Scanner getScanner() {
if (firstUndoPass) {
/*
* The RollbackTracker is being built, and we need a special
* scanner that can use the rollback period map while it is in an
* incomplete state. This is only needed for JE log versions that
* use MapLN_TXNAL, which are 2.0 and earlier.
*/
return new UnderConstructionScanner();
}
return new BackwardScanner();
}
/**
* Flip the invisible bit for each lsn in rollbackLsns. Collect the
* corresponding unique set of file numbers and add them to fileNums.
*/
private static void setInvisible(EnvironmentImpl envImpl,
List rollbackLsns,
Set filesToFsync) {
if (rollbackLsns.size() == 0) {
return;
}
/*
* Sort so that the entries are made invisible in disk order for better
* efficiency.
*/
FileManager fileManager = envImpl.getFileManager();
Collections.sort(rollbackLsns);
List perFileLsns = new ArrayList();
long currentFileNum = -1;
for (Long lsn : rollbackLsns) {
/* See if we have moved to a new file. */
if (DbLsn.getFileNumber(lsn) != currentFileNum) {
/*
* We've moved on to a new file. Make the previous set of
* lsns invisible.
*/
fileManager.makeInvisible(currentFileNum, perFileLsns);
currentFileNum = DbLsn.getFileNumber(lsn);
filesToFsync.add(currentFileNum);
/* make a new set to house the lsns for the next file. */
perFileLsns = new ArrayList();
}
perFileLsns.add(lsn);
}
/* Take care of the last set. */
fileManager.makeInvisible(currentFileNum, perFileLsns);
}
/*
* Flip the invisible bit for the rollback set of lsns, in lsn order.
* Fsync the set of files represented in this collection of lsns. Used by
* syncup rollback.
*/
public static void makeInvisible(EnvironmentImpl targetEnvImpl,
List rollbackLsns) {
Set fsyncFiles = new HashSet();
setInvisible(targetEnvImpl, rollbackLsns, fsyncFiles);
targetEnvImpl.getFileManager().force(fsyncFiles);
}
/**
* At the end of a recovery pass, write out all invisible bits, save
* a set of file numbers to fsync, and reinitialize the per-pass list
* for the next round.
*/
void singlePassSetInvisible() {
if (envImpl.isReadOnly()) {
return;
}
setInvisible(envImpl,
singlePassInvisibleLsns,
recoveryFilesToSync);
singlePassInvisibleLsns = new ArrayList();
}
void recoveryEndFsyncInvisible() {
if (envImpl.isReadOnly()) {
return;
}
envImpl.getFileManager().force(recoveryFilesToSync);
}
/**
* Count an LN obsolete that is being made invisble by rollback.
*
* Use inexact counting. Since invisible entries are not processed by the
* cleaner, recording the obsolete offset would be a waste of resources.
* Since we don't count offsets, we don't need to worry about duplicate
* offsets.
*
* Some entries may be double counted if they were previously counted
* obsolete, for example, when multiple versions of an LN were logged.
* This is tolerated for an exceptional situation like rollback.
*/
private void countObsolete(long undoLsn,
UndoReader undo,
RecoveryUtilizationTracker tracker) {
tracker.countObsoleteUnconditional
(undoLsn,
null /*type*/,
undo.logEntrySize,
false /*countExact*/);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (RollbackPeriod period : periodList) {
sb.append(period).append("\n");
}
return sb.toString();
}
private void assertFirstPass(long logLSN) {
if (!firstUndoPass) {
throw new EnvironmentFailureException
(envImpl,
EnvironmentFailureReason.UNEXPECTED_STATE,
"Saw entry at " + DbLsn.getNoFormatString(logLSN) +
"Should only be building the tracker on the first pass");
}
}
/**
* A Scanner is to process LNs during a recovery pass. It determines
* whether this log entry is within the rollback period, and should be
* accordingly undone or ignored. It serves as a sort of cursor or iterator
* that works with the rollback tracker.
*/
abstract class Scanner {
/*
* The target period is the one which houses the LNs that will be
* rolled back.
*/
RollbackPeriod target;
/**
* Return true if this transactional log entry is something that should
* be rolled back in this rollback period. The Scanner's position can
* be changed by this call. Update the target field if necessary.
*/
abstract boolean positionAndCheck(long lsn, long txnId);
/**
* Rollback the filereader's current LN_TX. This assumes that the the
* caller has ascertained that the LN is contained within this rollback
* period.
*/
public void rollback(Long txnId,
LNFileReader reader,
RecoveryUtilizationTracker tracker) {
/*
* If this period is before the checkpoint start, we need not
* repeat the partial rollback.
*/
if (target.beforeCheckpointStart()) {
return;
}
long undoLsn = reader.getLastLsn();
DbTree dbTree = envImpl.getDbTree();
UndoReader undo = UndoReader.createForRecovery(reader, dbTree);
if (undo == null) {
/* Database of LN has been deleted. [#22052] */
return;
}
/* Get the TxnChain for this log entry. */
TxnChain chain = target.getChain(txnId, undoLsn, envImpl);
try {
RevertInfo revertTo = chain.pop();
/*
* When we undo this log entry, we've logically truncated it
* from the log. Remove it from the btree and mark it obsolete.
*/
RecoveryManager.rollbackUndo(
envImpl.getLogger(), Level.FINER, new TreeLocation(),
undo.db, undo.logEntry, undoLsn, revertTo);
if (!target.hasRollbackEnd()) {
/*
* We're not positive that the fsync of the invisible log
* entries happened. Make it invisible again.
*/
if (!reader.isInvisible()) {
singlePassInvisibleLsns.add(undoLsn);
}
}
} finally {
dbTree.releaseDb(undo.db);
}
countObsolete(undoLsn, undo, tracker);
}
/* For unit tests */
boolean needsRollback() {
if (target == null) {
return false;
}
return !target.beforeCheckpointStart();
}
}
class UnderConstructionScanner extends Scanner {
@Override
public boolean positionAndCheck(long lsn, long txnId) {
if (underConstructionPeriod == null) {
return false;
}
assert underConstructionPeriod.notInRollbackStartAndEnd
(lsn, txnId) :
underConstructionPeriod.bracketFailure(lsn);
target = underConstructionPeriod.getScannerTarget(lsn);
if ((target != null) && (target.containsLN(lsn, txnId))) {
return true;
}
return false;
}
}
/**
* In a backward scanner, the currentPeriod field is always pointing to the
* period that contains this lsn. If the lsn is not in a period, the
* currentPeriod is the period that is just before this lsn. If there is no
* period before this lsn, the currentPeriod field is null.
*/
class BackwardScanner extends Scanner {
private final Iterator iter;
/*
* The current period is the period where the scanner is currently
* posed. It is one of the top level periods in the scanner. When
* rollback periods are nested, currentPeriod may not equal target.
*/
private RollbackPeriod currentPeriod;
BackwardScanner() {
this.iter = periodList.iterator();
if (iter.hasNext()) {
currentPeriod = iter.next();
currentPeriod.initChildIter();
} else {
currentPeriod = null;
}
}
@Override
public boolean positionAndCheck(long lsn, long txnId) {
if (currentPeriod == null) {
return false;
}
if (currentPeriod.follows(lsn)) {
/*
* We've passed out of the currentPeriod. Look for a new one
* that might cover this lsn.
*/
if (iter.hasNext()) {
currentPeriod = iter.next();
currentPeriod.initChildIter();
} else {
currentPeriod = null;
return false;
}
}
assert currentPeriod.notInRollbackStartAndEnd(lsn, txnId) :
currentPeriod.bracketFailure(lsn);
if (currentPeriod.contains(lsn)) {
/*
* Make the stack of periods point to the one that contains
* this lsn, or which precedes this lsn.
*/
currentPeriod.positionChildren(lsn);
/*
* See if any period contains this lsn. There might not be a
* target if the lsn was aborted or committed already at the
* time of rollback.
*/
target = currentPeriod.findTarget(lsn, txnId);
return (target != null);
}
return false;
}
}
/**
* A RollbackPeriod describes a section of the log that is logically
* truncated.
*/
static class RollbackPeriod {
private final RollbackTracker tracker;
private final long matchpointLSN; // start of period
private final long rollbackStartLSN; // end of period
private final long rollbackEndLSN; // for debugging and sanity checks
/*
* lsn of the checkpoint start, to determine if this rollback period
* needs to be used.
*/
private final boolean beforeCheckpointStart;
/*
* The transactions that were rolled back for this rollback period,
* which were logged in the RollbackStart entry.
*/
private Set activeTxnIds;
/*
* The txn chain constructed to support rollback to an earlier version.
*/
private final Map txnChainMap;
/* Nested rollbacks. */
private final List children;
private RollbackPeriod currentChild = null;
private Iterator childIter;
RollbackPeriod(RollbackTracker tracker,
RollbackEnd rollbackEnd,
long rollbackEndLSN) {
this(tracker,
rollbackEnd.getMatchpoint(),
rollbackEnd.getRollbackStart(),
rollbackEndLSN,
tracker.getCheckpointStart(),
null); // activeTxnIds
}
RollbackPeriod(RollbackTracker tracker,
RollbackStart rollbackStart,
long rollbackStartLSN) {
this(tracker,
rollbackStart.getMatchpoint(),
rollbackStartLSN,
DbLsn.NULL_LSN, // rollbackendLSN;
tracker.getCheckpointStart(),
rollbackStart.getActiveTxnIds());
}
/* For unit testing only. */
RollbackPeriod(long matchpointLSN,
long rollbackStartLSN,
long rollbackEndLSN,
long checkpointStart) {
this(null, matchpointLSN, rollbackStartLSN, rollbackEndLSN,
checkpointStart, null /*activeTxnIds*/);
}
private RollbackPeriod(RollbackTracker tracker,
long matchpointLSN,
long rollbackStartLSN,
long rollbackEndLSN,
long checkpointStart,
Set activeTxnIds) {
this.tracker = tracker;
this.matchpointLSN = matchpointLSN;
this.rollbackStartLSN = rollbackStartLSN;
this.rollbackEndLSN = rollbackEndLSN;
this.beforeCheckpointStart = calcBeforeCheckpoint(checkpointStart);
txnChainMap = new HashMap();
children = new ArrayList();
this.activeTxnIds = activeTxnIds;
}
private boolean calcBeforeCheckpoint(long checkpointStart) {
return ((checkpointStart != DbLsn.NULL_LSN) &&
(rollbackEndLSN != DbLsn.NULL_LSN) &&
(DbLsn.compareTo(rollbackEndLSN, checkpointStart) < 0));
}
/**
* A new RollbackEnd has been seen.
*
* @return true if the RollbackEnd belongs to a period nested within
* the current period. Return false if the RollbackEnd belongs to new,
* distinct, different period, and the current period is closed.
*/
boolean makeNestedPeriod(RollbackEnd foundRBEnd, long foundLSN) {
RollbackPeriod target = getNewPeriodTarget(foundRBEnd, foundLSN);
if (target != null) {
target.makeChild(foundRBEnd, foundLSN);
return true;
}
return false;
}
/**
* A new RollbackStart has been seen.
*
* @return true if the RollbackStart belongs to a period nested within
* the current period, or if it is the current period. Return false if
* the RollbackStart belongs to new, distinct, different period, and
* this current period is closed.
*/
boolean makeNestedPeriod(RollbackStart foundRBStart, long foundLSN) {
RollbackPeriod target = getNewPeriodTarget(foundRBStart, foundLSN);
if (target != null) {
if (target.isMatchingRollbackStart(foundLSN)) {
assert target.activeTxnIds == null;
target.activeTxnIds = foundRBStart.getActiveTxnIds();
} else {
target.makeChild(foundRBStart, foundLSN);
}
/*
* Retrun true to let the caller know that it doesn't have to
* make a new Rollback period. Either the RBStart did not
* initiate a new period, or we made a nested child.
*/
return true;
}
/* This period is closed. */
return false;
}
private boolean contained(RollbackEnd foundRBEnd, long foundLSN) {
/*
* This RollbackEnd must either
* 1 - precede this period, in which case this period is closed, or
* 2 - be wholly contained within this period.
*/
/* case 1 */
if (DbLsn.compareTo(foundLSN, matchpointLSN) < 0) {
/* The found rollback end precedes this period. */
return false;
}
if (DbLsn.compareTo(foundLSN, rollbackStartLSN) >= 0) {
fail("Should not be two RollbackEnds in a row. " +
"New RollbackEnd at " +
DbLsn.getNoFormatString(foundLSN) +
" " + foundRBEnd);
}
/*
* Check for compliance to the rule that this RollbackEnd does not
* intersect this rollback period.
*/
if (!((DbLsn.compareTo(foundRBEnd.getMatchpoint(),
matchpointLSN) >= 0) &&
(DbLsn.compareTo(foundRBEnd.getRollbackStart(),
rollbackStartLSN) < 0))) {
fail("RollbackEnd intersects current rollback period " +
foundRBEnd + " at " + DbLsn.getNoFormatString(foundLSN));
}
/* case 2 */
return true;
}
/**
* @return true if the current rollback period is still open
*/
private boolean contained(RollbackStart foundRBStart, long foundLSN) {
/*
* This RollbackStart must:
* 1 - precede the current period, indicating the end of this
* period.
* 2 - is the rolblack start that belongs to this period.
* 3 - be wholly contained within this period.
*/
/* case 1 */
if (DbLsn.compareTo(foundLSN, matchpointLSN) < 0) {
/* The found rollback start precedes this period. */
return false;
}
if (isMatchingRollbackStart(foundLSN)) {
return true;
}
/* Check for compliance with case 3. */
if (!((DbLsn.compareTo(foundRBStart.getMatchpoint(),
matchpointLSN) >= 0) &&
(DbLsn.compareTo(foundLSN, rollbackStartLSN) < 0))) {
fail("RollbackStart intersects current rollback period " +
foundRBStart + " at " +
DbLsn.getNoFormatString(foundLSN));
}
/* case 3. */
return true;
}
/**
* @return true if this RollbackStart entry is the one that is the
* RollbackStart for this open period.
*/
private boolean isMatchingRollbackStart(long foundLSN) {
return (DbLsn.compareTo(foundLSN, rollbackStartLSN) == 0);
}
private void makeChild(RollbackEnd foundRBEnd, long foundLSN) {
currentChild = new RollbackPeriod(tracker,
foundRBEnd,
foundLSN);
children.add(currentChild);
}
private void makeChild(RollbackStart foundRBStart, long foundLSN) {
currentChild = new RollbackPeriod(tracker,
foundRBStart,
foundLSN);
children.add(currentChild);
}
/**
* Return the period that should own this foundRBEnd. That may be
* either a nested period, or this period.
*/
RollbackPeriod getNewPeriodTarget(RollbackEnd foundRBEnd,
long foundLSN) {
if (currentChild != null) {
final RollbackPeriod target =
currentChild.getNewPeriodTarget(foundRBEnd, foundLSN);
if (target != null) {
return target;
}
}
if (contained(foundRBEnd, foundLSN)) {
return this;
}
return null;
}
/**
* Return the period that should own this foundRBStart. That may be
* either a nested period, or this period.
*/
RollbackPeriod getNewPeriodTarget(RollbackStart foundRBStart,
long foundLSN) {
if (currentChild != null) {
final RollbackPeriod target =
currentChild.getNewPeriodTarget(foundRBStart, foundLSN);
if (target != null) {
return target;
}
}
if (contained(foundRBStart, foundLSN)) {
return this;
}
return null;
}
RollbackPeriod getScannerTarget(long lsn) {
if (currentChild != null) {
RollbackPeriod target = currentChild.getScannerTarget(lsn);
if (target != null) {
return target;
}
}
if (DbLsn.compareTo(lsn, matchpointLSN) > 0) {
return this;
}
return null;
}
void initChildIter() {
childIter = children.iterator();
if (childIter.hasNext()) {
currentChild = childIter.next();
currentChild.initChildIter();
} else {
currentChild = null;
}
}
void fail(String errorMessage) {
throw new EnvironmentFailureException
(tracker.getEnvImpl(),
EnvironmentFailureReason.LOG_INTEGRITY,
errorMessage + "\ntracker contents=" + tracker);
}
/**
* This log entry belongs to this rollback period if it lies between
* the matchpoint and the RollbackStart. We don't use RollbackEnd,
* because there may not be a RollbackEnd. Also, by definition,
* anything whose rollback fate is define by this period must have
* happened before the RollbackStart.
*/
boolean contains(long lsn) {
return (DbLsn.compareTo(matchpointLSN, lsn) < 0) &&
(DbLsn.compareTo(rollbackStartLSN, lsn) > 0);
}
boolean containsLN(long lsn, long txnId) {
return contains(lsn) && activeTxnIds.contains(txnId);
}
void positionChildren(long lsn) {
if (currentChild == null)
return;
if (currentChild.follows(lsn)) {
if (childIter.hasNext()) {
currentChild = childIter.next();
currentChild.initChildIter();
} else {
currentChild = null;
return;
}
}
currentChild.positionChildren(lsn);
}
RollbackPeriod findTarget(long lsn, long txnId) {
if (currentChild != null) {
final RollbackPeriod candidate =
currentChild.findTarget(lsn, txnId);
if (candidate != null) {
return candidate;
}
}
if (containsLN(lsn, txnId)) {
return this;
}
return null;
}
/**
* There should not be any txnal LNs between a rollback start and
* rollback end log entry.
*/
boolean notInRollbackStartAndEnd(long lsn, long txnId) {
if (!TxnManager.isReplicatedTxn(txnId)) {
/* Don't bother checking a non-replicated txn. */
return true;
}
if (rollbackEndLSN == DbLsn.NULL_LSN)
return true;
return (!((DbLsn.compareTo(rollbackStartLSN, lsn) < 0) &&
(DbLsn.compareTo(rollbackEndLSN, lsn) > 0)));
}
String bracketFailure(long lsn) {
return lsn + " [" + DbLsn.getNoFormatString(lsn) +
"] should not be within rollbackStart " + rollbackStartLSN +
" [" + DbLsn.getNoFormatString(rollbackStartLSN) +
"] and rollbackEnd " + rollbackEndLSN + " [" +
DbLsn.getNoFormatString(rollbackEndLSN) + "]";
}
/**
* @return true if this rollback period is after, and does not contain
* the lsn.
*/
boolean follows(long lsn) {
return DbLsn.compareTo(matchpointLSN, lsn) > 0;
}
/**
* @return true if this rollback period is before, and does not contain
* the lsn.
*/
boolean precedes(long lsn) {
return DbLsn.compareTo(rollbackStartLSN, lsn) < 0;
}
TxnChain getChain(long txnId, long undoLsn, EnvironmentImpl envImpl) {
TxnChain chain = txnChainMap.get(txnId);
if (chain == null) {
chain = new TxnChain(undoLsn,
txnId,
matchpointLSN,
envImpl);
txnChainMap.put(txnId, chain);
}
return chain;
}
boolean hasRollbackEnd() {
return rollbackEndLSN != DbLsn.NULL_LSN;
}
@Override
public String toString() {
return "matchpoint=" + matchpointLSN + " [" +
DbLsn.getNoFormatString(matchpointLSN) +
"] rollbackStart=" + rollbackStartLSN + " [" +
DbLsn.getNoFormatString(rollbackStartLSN) +
"] rollbackEnd=" + rollbackEndLSN + " [" +
DbLsn.getNoFormatString(rollbackEndLSN) + "]";
}
@Override
public boolean equals(Object other) {
if (!(other instanceof RollbackPeriod)) {
return false;
}
RollbackPeriod otherPeriod = (RollbackPeriod) other;
return ((matchpointLSN == otherPeriod.matchpointLSN) &&
(rollbackStartLSN == otherPeriod.rollbackStartLSN) &&
(rollbackEndLSN == otherPeriod.rollbackEndLSN));
}
boolean beforeCheckpointStart() {
return beforeCheckpointStart;
}
@Override
public int hashCode() {
final int prime = 31;
int result = (prime + (int) matchpointLSN);
result = (result * prime)+ (int) rollbackStartLSN;
result = (result * prime) + (int) rollbackEndLSN;
return result;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy