/*
* If wasCleaned is false we don't count statistics unless we migrate
* the LN. This avoids double counting.
*/
BasicLocker locker = null;
LN ln = null;
try {
/*
* Fetch the node, if necessary. If it was not resident and it is
* an evictable LN, we will clear it after we migrate it.
*/
if (!bin.isEntryKnownDeleted(index)) {
ln = (LN) bin.getTarget(index);
if (ln == null) {
/* If fetchTarget returns null, a deleted LN was cleaned.*/
ln = (LN) bin.fetchTarget(index);
clearTarget = !db.getId().equals(DbTree.ID_DB_ID);
}
}
/* Don't migrate knownDeleted or deleted cleaned LNs. */
if (ln == null) {
if (wasCleaned) {
nLNsDead++;
}
obsolete = true;
completed = true;
return;
}
/*
* Get a non-blocking read lock on the LN. A pending node is
* already locked, but that node ID may be different than the
* current LN's node if a slot is reused. We must lock the current
* node to guard against aborts.
*/
if (lockedPendingNodeId != ln.getNodeId()) {
locker = new BasicLocker(env);
LockResult lockRet = locker.nonBlockingLock
(ln.getNodeId(), 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.
*/
if (wasCleaned) {
nLNsLocked++;
}
lockDenied = true;
completed = true;
return;
}
}
/* Don't migrate deleted LNs. */
if (ln.isDeleted()) {
bin.setKnownDeletedLeaveTarget(index);
if (wasCleaned) {
nLNsDead++;
}
obsolete = true;
completed = true;
return;
}
/*
* Once we have a lock, check whether the current LSN needs to be
* migrated. There is no need to migrate it if the LSN no longer
* qualifies for cleaning. The LSN could have been changed by an
* update or delete after we set the MIGRATE flag.
*
* Note that we do not perform this optimization if the MIGRATE
* flag is not set, i.e, for clustering and proactive migration of
* resident LNs. For these cases, we checked the conditions for
* migration immediately before calling this method. Although the
* condition could change after locking, the window is small and
* a second check is not worthwhile.
*/
if (bin.getMigrate(index)) {
Long fileNum = new Long(DbLsn.getFileNumber(lsn));
if (!fileSelector.isFileCleaningInProgress(fileNum)) {
obsolete = true;
completed = true;
if (wasCleaned) {
nLNsDead++;
}
return;
}
}
/* Migrate the LN. */
byte[] key = getLNMainKey(bin, index);
long newLNLsn = ln.log(env, db.getId(), key, lsn, locker);
bin.updateEntry(index, newLNLsn);
nLNsMigrated++;
migrated = true;
completed = true;
return;
} finally {
if (isPending) {
if (completed && !lockDenied) {
fileSelector.removePendingLN(lockedPendingNodeId);
}
} else {
/*
* If a to-be-migrated LN was not processed successfully, we
* must guarantee that the file will not be deleted and that we
* will retry the LN later. The retry information must be
* complete or we may delete a file later without processing
* all of its LNs.
*/
if (bin.getMigrate(index) && (!completed || lockDenied)) {
byte[] key = getLNMainKey(bin, index);
byte[] dupKey = getLNDupKey(bin, index, ln);
fileSelector.addPendingLN(ln, db.getId(), key, dupKey);
/* Wake up the cleaner thread to process pending LNs. */
if (!areThreadsRunning()) {
env.getUtilizationTracker().activateCleaner();
}
/*
* If we need to retry, don't clear the target since we
* would only have to fetch it again soon.
*/
clearTarget = false;
}
}
/*
* Always clear the migrate flag. If the LN could not be locked
* and the migrate flag was set, the LN will have been added to the
* pending LN set above.
*/
bin.setMigrate(index, false);
/*
* If the node was originally non-resident, clear it now so that we
* don't create more work for the evictor and reduce the cache
* memory available to the application.
*/
if (clearTarget) {
bin.updateEntry(index, null);
}
if (locker != null) {
locker.operationEnd();
}
trace(detailedTraceLevel, cleanAction, ln, lsn,
completed, obsolete, migrated);
}