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.
long nodeId = lnFromLog.getNodeId();
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) {
/*
* 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).
*
* We can hold the latch on the BIN (and DIN) 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
(nodeId, LockType.READ, 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, and set the LSN to
* NULL_LSN to ensure that it is not tracked or otherwise
* referenced. 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 (isDupCountLN) {
ChildReference dclRef = parentDIN.getDupCountLNRef();
if (dclRef.getTarget() == null) {
lnFromLog.postFetchInit(db, logLsn);
parentDIN.updateDupCountLN(lnFromLog);
}
if (isTemporary) {
((LN) dclRef.getTarget()).setDirty();
dclRef.setLsn(DbLsn.NULL_LSN);
parentDIN.setDirty(true);
} else if (cleaner.lazyMigration) {
dclRef.setMigrate(true);
parentDIN.setDirty(true);
} else {
LN targetLn = (LN) dclRef.getTarget();
assert targetLn != null;
byte[] targetKey = parentDIN.getDupKey();
long newLNLsn = targetLn.log
(env, db, targetKey, logLsn, locker,
true /*backgroundIO*/,
ReplicationContext.NO_REPLICATE);
parentDIN.updateDupCountLNRef(newLNLsn);
/* Evict LN if we populated it with the log LN. */
if (lnFromLog == targetLn) {
parentDIN.updateDupCountLN(null);
}
}
} else {
if (bin.getTarget(index) == null) {
lnFromLog.postFetchInit(db, logLsn);
/* Ensure keys are transactionally correct. [#15704] */
byte[] lnSlotKey = bin.containsDuplicates() ?
dupKey : key;
bin.updateNode(index, lnFromLog, lnSlotKey);
}
if (isTemporary) {
((LN) bin.getTarget(index)).setDirty();
bin.clearLsn(index);
bin.setDirty(true);
} else if (cleaner.lazyMigration) {
bin.setMigrate(index, true);
bin.setDirty(true);
} else {
LN targetLn = (LN) bin.getTarget(index);
assert targetLn != null;
byte[] targetKey = cleaner.getLNMainKey(bin, index);
long newLNLsn = targetLn.log
(env, db, targetKey, logLsn, locker,
true /*backgroundIO*/,
ReplicationContext.NO_REPLICATE);
bin.updateEntry(index, newLNLsn);
/* Evict LN if we populated it with the log LN. */
if (lnFromLog == targetLn) {
bin.updateNode(index, null, null);
}
}
/*
* If the generation is zero, we fetched this BIN just for
* cleaning.
*/
if (PROHIBIT_DELTAS_WHEN_FETCHING &&
bin.getGeneration() == 0) {
bin.setProhibitNextDelta();
}
/*
* Update the generation so that the BIN is not evicted
* immediately. This allows the cleaner to fill in as many
* entries as possible before eviction, as to-be-cleaned
* files are processed.
*/
bin.setGeneration(CacheMode.DEFAULT);
}
nLNsMarkedThisRun++;
migrated = true;
}
completed = true;
} finally {
if (locker != null) {
locker.operationEnd();
}
/*
* If a write lock is held, it is likely that the log LSN will
* become obsolete. It is more efficient to process this via the