long treeLsn,
BIN bin,
int index)
throws DatabaseException {
LN lnFromLog = info.getLN();
byte[] key = info.getKey();
DatabaseImpl db = bin.getDatabase();
boolean isTemporary = db.isTemporary();
/* Status variables are used to generate debug tracing info. */
boolean obsolete = false; // The LN is no longer in use.
boolean migrated = false; // The LN was in use and is migrated.
boolean lockDenied = false;// The LN lock was denied.
boolean completed = false; // This method completed.
BasicLocker locker = null;
try {
Tree tree = db.getTree();
assert tree != null;
/*
* If the tree and log LSNs are equal, then we can be fairly
* certain that the log entry is current; in that case, it is
* wasteful to lock the LN here if we will perform lazy migration
* -- it is better to lock only once during lazy migration. But if
* the tree and log LSNs differ, it is likely that another thread
* has updated or deleted the LN and the log LSN is now obsolete;
* in this case we can avoid dirtying the BIN by checking for
* obsoleteness here, which requires locking. The latter case can
* occur frequently if trackDetail is false.
*
* 1. If the LSN in the tree and in the log are the same, we will
* attempt to migrate it.
*
* 2. If the LSN in the tree is < the LSN in the log, the log entry
* is obsolete, because this LN has been rolled back to a previous
* version by a txn that aborted.
*
* 3. If the LSN in the tree is > the LSN in the log, the log entry
* is obsolete, because the LN was advanced forward by some
* now-committed txn.
*
* 4. If the LSN in the tree is a null LSN, the log entry is
* obsolete. A slot can only have a null LSN if the record has
* never been written to disk in a deferred write database, and
* in that case the log entry must be for a past, deleted version
* of that record.
*/
if (lnFromLog.isDeleted() &&
(treeLsn == logLsn) &&
fileLogVersion <= 2) {
/*
* SR 14583: After JE 2.0, deleted LNs are never found in the
* tree, since we can assume they're obsolete and correctly
* marked as such in the obsolete offset tracking. JE 1.7.1 and
* earlier did not use the pending deleted bit, so deleted LNs
* may still be reachable through their BIN parents.
*/
obsolete = true;
nLNsDeadThisRun++;
bin.setPendingDeleted(index);
} else if (treeLsn == DbLsn.NULL_LSN) {
/*
* Case 4: The LN in the tree is a never-written LN for a
* deferred-write db, so the LN in the file is obsolete.
*/
obsolete = true;
} else if (treeLsn != logLsn && isTemporary) {
/*
* Temporary databases are always non-transactional. If the
* tree and log LSNs are different then we know that the logLsn
* is obsolete. Even if the LN is locked, the tree cannot be
* restored to the logLsn because no abort is possible without
* a transaction. We should consider a similar optimization in
* the future for non-transactional durable databases.
*/
nLNsDeadThisRun++;
obsolete = true;
} else if ((treeLsn != logLsn || !cleaner.lazyMigration) &&
!isTemporary) {
/*
* Get a lock on the LN if the treeLsn and logLsn are different
* to determine definitively whether the logLsn is obsolete.
* We must also get a lock if we will migrate the LN now
* (lazyMigration is false and isTemporary is false).
*
* We can hold the latch on the BIN since we always attempt to
* acquire a non-blocking read lock.
*/
locker = BasicLocker.createBasicLocker(env, false /*noWait*/);
/* Don't allow this short-lived lock to be preempted/stolen. */
locker.setPreemptable(false);
LockResult lockRet = locker.nonBlockingLock
(treeLsn, LockType.READ, false /*jumpAheadOfWaiters*/, db);
if (lockRet.getLockGrant() == LockGrantType.DENIED) {
/*
* LN is currently locked by another Locker, so we can't
* assume anything about the value of the LSN in the bin.
*/
nLNsLockedThisRun++;
lockDenied = true;
} else if (treeLsn != logLsn) {
/* The LN is obsolete and can be purged. */
nLNsDeadThisRun++;
obsolete = true;
}
}
/*
* At this point either obsolete==true, lockDenied==true, or
* treeLsn==logLsn.
*/
if (!obsolete && !lockDenied) {
assert treeLsn == logLsn;
/*
* If lazyMigration is true, set the migrate flag and dirty
* the parent IN. The evictor or checkpointer will migrate the
* LN later. If lazyMigration is false, migrate the LN now.
*
* We have a lock on the LN if we are going to migrate it now,
* but not if we will set the migrate flag.
*
* When setting the migrate flag, also populate the target node
* so it does not have to be fetched when it is migrated, if
* the tree and log LSNs are equal and the target is not
* resident. We must call postFetchInit to initialize MapLNs
* that have not been fully initialized yet [#13191].
*
* For temporary databases, do not rely on the LN migration
* mechanism because temporary databases are not checkpointed
* or recovered. Instead, dirty the LN to ensure it is
* flushed before its parent is written. Because we do not
* attempt to lock temporary database LNs (see above) we know
* that if it is non-obsolete, the tree and log LSNs are equal.
* We will always restore the LN to the BIN slot here, and
* always log the dirty LN when logging the BIN.
*
* Also for temporary databases, make both the target LN and
* the BIN or IN parent dirty. Otherwise, when the BIN or IN is
* evicted in the future, it will be written to disk without
* flushing its dirty, migrated LNs. [#18227]
*/
if (bin.getTarget(index) == null) {
lnFromLog.postFetchInit(db, logLsn);
/* Ensure keys are transactionally correct. [#15704] */
bin.updateNode(index, lnFromLog, key /*lnSlotKey*/);
}
if (isTemporary) {
((LN) bin.getTarget(index)).setDirty();
bin.setDirty(true);
} else if (cleaner.lazyMigration) {
bin.setMigrate(index, true);
bin.setDirty(true);
} else {
LN targetLn = (LN) bin.getTarget(index);
assert targetLn != null;
long newLNLsn = targetLn.log
(env, db, bin.getKey(index), logLsn,
true /*backgroundIO*/,
Cleaner.getMigrationRepContext(targetLn));
bin.updateEntry(index, newLNLsn);
/* Evict LN if we populated it with the log LN. */