Package com.sleepycat.je.cleaner

Source Code of com.sleepycat.je.cleaner.Cleaner

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002, 2011 Oracle and/or its affiliates.  All rights reserved.
*
*/

package com.sleepycat.je.cleaner;

import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_BIN_DELTAS_CLEANED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_BIN_DELTAS_DEAD;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_BIN_DELTAS_MIGRATED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_BIN_DELTAS_OBSOLETE;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_CORRECTED_AVG_LN_SIZE;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_CLUSTER_LNS_PROCESSED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_DELETIONS;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_ENTRIES_READ;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_ESTIMATED_AVG_LN_SIZE;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_INS_CLEANED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_INS_DEAD;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_INS_MIGRATED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_INS_OBSOLETE;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_LNQUEUE_HITS;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_LNS_CLEANED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_LNS_DEAD;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_LNS_LOCKED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_LNS_MARKED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_LNS_MIGRATED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_LNS_OBSOLETE;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_MARKED_LNS_PROCESSED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_PENDING_LNS_LOCKED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_PENDING_LNS_PROCESSED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_PROBE_RUNS;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_REPEAT_ITERATOR_READS;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_RUNS;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_TOTAL_LOG_SIZE;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.CLEANER_TO_BE_CLEANED_LNS_PROCESSED;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.GROUP_DESC;
import static com.sleepycat.je.cleaner.CleanerStatDefinition.GROUP_NAME;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sleepycat.je.CacheMode;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.EnvironmentMutableConfig;
import com.sleepycat.je.ExceptionListener;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.cleaner.FileSelector.CheckpointStartCleanerState;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.CursorImpl;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.DbTree;
import com.sleepycat.je.dbi.EnvConfigObserver;
import com.sleepycat.je.dbi.EnvironmentFailureReason;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.FileManager;
import com.sleepycat.je.log.ReplicationContext;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.tree.Node;
import com.sleepycat.je.tree.Tree;
import com.sleepycat.je.tree.TreeLocation;
import com.sleepycat.je.txn.BasicLocker;
import com.sleepycat.je.txn.LockGrantType;
import com.sleepycat.je.txn.LockResult;
import com.sleepycat.je.txn.LockType;
import com.sleepycat.je.utilint.DaemonRunner;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.ExceptionListenerUser;
import com.sleepycat.je.utilint.FloatStat;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.LongStat;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.VLSN;

/**
* The Cleaner is responsible for effectively garbage collecting the JE log.
* It selects the least utilized log file for cleaning (see FileSelector),
* reads through the log file (FileProcessor) and determines whether each entry
* is obsolete (no longer relavent) or active (referenced by the Btree).
* Entries that are active are migrated (copied) to the end of the log, and
* finally the cleaned file is deleted.
*
* The migration of active entries is a multi-step process that can be
* configured to operate in different ways.  Eviction and checkpointing, as
* well as the cleaner threads (FileProcessor instances) are participants in
* this process.  Migration may be immediate, lazy, or proactive.
*
* Active INs are always migrated lazily, which means that they are marked
* dirty by the FileProcessor, and then logged later by an eviction or
* checkpoint.  Active LNs are either migrated lazily or immediately depending
* on the EnvironmentConfig.CLEANER_LAZY_MIGRATION setting.  If they are
* migrated lazily, the migrate flag is set in the LN slot by the
* FileProcessor, and the LN is logged later by an eviction or checkpoint.
*
* When the FileProcessor is finished with a file, all lazy migration for that
* file is normally completed by the end of the next checkpoint, if not sooner
* via eviction.  The checkpoint/recovery mechanism will ensure that obsolete
* entries will not be referenced by the Btree.  At the end of the checkpoint,
* it is therefore safe to delete the log file.
*
* There is one exception to the above paragraph.  When attempting to migrate
* an LN, if the LN cannot be locked then we must retry the migration at a
* later time.  Also, if a database removal is in progress, we consider all
* entries in the database obsolete but cannot delete the log file until
* database removal is complete.  Such "pending" LNs and databases are queued
* and processed periodically during file processing and at the start of a
* checkpoint; see processPending().  In this case, we may have to wait for
* more than one checkpoint to occur before the log file can be deleted.  See
* FileSelector and the use of the pendingLNs and pendingDBs collections.
*
* The last type of migration, proactive migration, is migration of LNs by the
* evictor or checkpointer for files that are to-be-cleaned, i.e., part of the
* cleaner's backlog.  The idea is to prevent the backlog from growing too
* large (and potentially filling the disk) by doing more cleaner work during
* eviction, which will throttle the application threads.  By default,
* proactive migration is disabled, but it can enabled using
* EnvironmentConfig.CLEANER_BACKGROUND_PROACTIVE_MIGRATION and
* CLEANER_FOREGROUND_PROACTIVE_MIGRATION.
*/
public class Cleaner implements DaemonRunner,
                                EnvConfigObserver,
                                ExceptionListenerUser {
    /* From cleaner */
    static final String CLEAN_IN = "CleanIN:";
    static final String CLEAN_LN = "CleanLN:";
    static final String CLEAN_MIGRATE_LN = "CleanMigrateLN:";
    static final String CLEAN_PENDING_LN = "CleanPendingLN:";

    /**
     * Whether to update the IN generation count during searches.  This is
     * currently disabled because 1) we update the generation of the BIN when
     * we set a MIGRATE flag and 2) if the BIN is not evicted its parents will
     * not be, so not updating the generation during the search has no benefit.
     * By not updating the generation during searches for which we do NOT set
     * the MIGRATE flag, we avoid holding INs in the cache that are not needed
     * for lazy migration.  However, we do very few searches for obsolete LNs
     * because the obsolete tracking info prevents this, so the benefit of not
     * updating the generation during searches is questionable.  In other
     * words, changing this setting will have little effect.
     */
    static final CacheMode UPDATE_GENERATION = CacheMode.UNCHANGED;

    /**
     * Whether the cleaner should participate in critical eviction.  Ideally
     * the cleaner would not participate in eviction, since that would reduce
     * the cost of cleaning.  However, the cleaner can add large numbers of
     * nodes to the cache.  By not participating in eviction, other threads
     * could be kept in a constant state of eviction and would effectively
     * starve.  Therefore, this setting is currently enabled.
     */
    static final boolean DO_CRITICAL_EVICTION = true;

    /*
     * Cumulative counters.  Updates to these counters occur in multiple
     * threads, including FileProcessor threads,  and are not synchronized.
     * This could produce errors in counting, but avoids contention around stat
     * updates.
     */
    StatGroup stats;
    LongStat nCleanerRuns;
    LongStat nCleanerProbeRuns;
    LongStat nCleanerDeletions;
    LongStat nINsObsolete;
    LongStat nINsCleaned;
    LongStat nINsDead;
    LongStat nINsMigrated;
    LongStat nBINDeltasObsolete;
    LongStat nBINDeltasCleaned;
    LongStat nBINDeltasDead;
    LongStat nBINDeltasMigrated;
    LongStat nLNsObsolete;
    LongStat nLNsCleaned;
    LongStat nLNsDead;
    LongStat nLNsLocked;
    LongStat nLNsMigrated;
    LongStat nLNsMarked;
    LongStat nLNQueueHits;
    LongStat nPendingLNsProcessed;
    LongStat nMarkedLNsProcessed;
    LongStat nToBeCleanedLNsProcessed;
    LongStat nClusterLNsProcessed;
    LongStat nPendingLNsLocked;
    LongStat nEntriesRead;
    LongStat nRepeatIteratorReads;
    LongStat totalLogSize;
    FloatStat estimatedAvgLNSize;
    FloatStat correctedAvgLNSize;

    /*
     * Configuration parameters are non-private for use by FileProcessor,
     * UtilizationTracker, or UtilizationCalculator.
     */
    long lockTimeout;
    int readBufferSize;
    int lookAheadCacheSize;
    long nDeadlockRetries;
    boolean expunge;
    boolean clusterResident;
    boolean clusterAll;
    int maxBatchFiles;
    long cleanerBytesInterval;
    boolean trackDetail;
    boolean fetchObsoleteSize;
    boolean lazyMigration;
    private boolean foregroundProactiveMigration;
    private boolean backgroundProactiveMigration;
    private final boolean rmwFixEnabled;
    int minUtilization;
    int minFileUtilization;
    int minAge;

    /**
     * All files that are to-be-cleaned.  Used to perform proactive migration.
     * Is read-only after assignment, so no synchronization is needed.
     */
    private Set<Long> toBeCleanedFiles = Collections.emptySet();

    /**
     * All files that are below the minUtilization threshold.  Used to perform
     * clustering migration.  Is read-only after assignment, so no
     * synchronization is needed.
     */
    private Set<Long> lowUtilizationFiles = Collections.emptySet();

    private final String name;
    private final EnvironmentImpl env;
    private final UtilizationProfile profile;
    private final UtilizationTracker tracker;
    private final UtilizationCalculator calculator;
    private final FileSelector fileSelector;
    private FileProcessor[] threads;

    /*
     * Log file deletion must check for ongoing backups and other procedures
     * that rely on a set log files remaining stable (no deletions).  Multiple
     * ranges of file numbers may be protected from deletion, where each range
     * is from a given file number to the end of the log.
     *
     * protectedFileRanges is a list that contains the starting file number for
     * each protected range.  All files from the mininum of these values to the
     * end of the log are protected from deletion.  This field is accessed only
     * while synchronizing on protectedFileRanges.
     */
    private final List<Long> protectedFileRanges;
    private long lastCleanerBarrierStartFile; /* For debugging. */
    private final Logger logger;
    TestHook fileChosenHook;

    public Cleaner(EnvironmentImpl env, String name)
        throws DatabaseException {

        this.env = env;
        this.name = name;

        /* Initiate the stats definitions. */
        stats = new StatGroup(GROUP_NAME, GROUP_DESC);
        nCleanerRuns = new LongStat(stats, CLEANER_RUNS);
        nCleanerProbeRuns = new LongStat(stats, CLEANER_PROBE_RUNS);
        nCleanerDeletions = new LongStat(stats, CLEANER_DELETIONS);
        nINsObsolete = new LongStat(stats, CLEANER_INS_OBSOLETE);
        nINsCleaned = new LongStat(stats, CLEANER_INS_CLEANED);
        nINsDead = new LongStat(stats, CLEANER_INS_DEAD);
        nINsMigrated = new LongStat(stats, CLEANER_INS_MIGRATED);
        nBINDeltasObsolete = new LongStat(stats, CLEANER_BIN_DELTAS_OBSOLETE);
        nBINDeltasCleaned = new LongStat(stats, CLEANER_BIN_DELTAS_CLEANED);
        nBINDeltasDead = new LongStat(stats, CLEANER_BIN_DELTAS_DEAD);
        nBINDeltasMigrated = new LongStat(stats, CLEANER_BIN_DELTAS_MIGRATED);
        nLNsObsolete = new LongStat(stats, CLEANER_LNS_OBSOLETE);
        nLNsCleaned = new LongStat(stats, CLEANER_LNS_CLEANED);
        nLNsDead = new LongStat(stats, CLEANER_LNS_DEAD);
        nLNsLocked = new LongStat(stats, CLEANER_LNS_LOCKED);
        nLNsMigrated = new LongStat(stats, CLEANER_LNS_MIGRATED);
        nLNsMarked = new LongStat(stats, CLEANER_LNS_MARKED);
        nLNQueueHits = new LongStat(stats, CLEANER_LNQUEUE_HITS);
        nPendingLNsProcessed =
            new LongStat(stats, CLEANER_PENDING_LNS_PROCESSED);
        nMarkedLNsProcessed = new LongStat(stats, CLEANER_MARKED_LNS_PROCESSED);
        nToBeCleanedLNsProcessed =
            new LongStat(stats, CLEANER_TO_BE_CLEANED_LNS_PROCESSED);
        nClusterLNsProcessed =
            new LongStat(stats, CLEANER_CLUSTER_LNS_PROCESSED);
        nPendingLNsLocked = new LongStat(stats, CLEANER_PENDING_LNS_LOCKED);
        nEntriesRead = new LongStat(stats, CLEANER_ENTRIES_READ);
        nRepeatIteratorReads =
            new LongStat(stats, CLEANER_REPEAT_ITERATOR_READS);
        totalLogSize = new LongStat(stats, CLEANER_TOTAL_LOG_SIZE);
        estimatedAvgLNSize =
            new FloatStat(stats, CLEANER_ESTIMATED_AVG_LN_SIZE);
        correctedAvgLNSize =
            new FloatStat(stats, CLEANER_CORRECTED_AVG_LN_SIZE);

        tracker = new UtilizationTracker(env, this);
        profile = new UtilizationProfile(env, tracker);
        calculator = new UtilizationCalculator(env, this);
        fileSelector = new FileSelector();
        threads = new FileProcessor[0];
        protectedFileRanges = new LinkedList<Long>();
        logger = LoggerUtils.getLogger(getClass());

        /*
         * The trackDetail property is immutable because of the complexity (if
         * it were mutable) in determining whether to update the memory budget
         * and perform eviction.
         */
        trackDetail = env.getConfigManager().getBoolean
            (EnvironmentParams.CLEANER_TRACK_DETAIL);

        rmwFixEnabled = env.getConfigManager().getBoolean
            (EnvironmentParams.CLEANER_RMW_FIX);

        /* Initialize mutable properties and register for notifications. */
        envConfigUpdate(env.getConfigManager(), null);
        env.addConfigObserver(this);
        env.registerExceptionListenerUser(this);
    }

    /**
     * Process notifications of mutable property changes.
     *
     * @throws IllegalArgumentException via Environment ctor and
     * setMutableConfig.
     */
    public void envConfigUpdate(DbConfigManager cm,
                                EnvironmentMutableConfig ignore)
        throws DatabaseException {

        lockTimeout = cm.getDuration(EnvironmentParams.CLEANER_LOCK_TIMEOUT);

        readBufferSize = cm.getInt(EnvironmentParams.CLEANER_READ_SIZE);
        if (readBufferSize <= 0) {
            readBufferSize = cm.getInt
                (EnvironmentParams.LOG_ITERATOR_READ_SIZE);
        }

        lookAheadCacheSize = cm.getInt
            (EnvironmentParams.CLEANER_LOOK_AHEAD_CACHE_SIZE);

        foregroundProactiveMigration = cm.getBoolean
            (EnvironmentParams.CLEANER_FOREGROUND_PROACTIVE_MIGRATION);

        backgroundProactiveMigration = cm.getBoolean
            (EnvironmentParams.CLEANER_BACKGROUND_PROACTIVE_MIGRATION);

        nDeadlockRetries = cm.getInt(EnvironmentParams.CLEANER_DEADLOCK_RETRY);

        expunge = cm.getBoolean(EnvironmentParams.CLEANER_REMOVE);

        clusterResident = cm.getBoolean(EnvironmentParams.CLEANER_CLUSTER);

        clusterAll = cm.getBoolean(EnvironmentParams.CLEANER_CLUSTER_ALL);

        maxBatchFiles = cm.getInt(EnvironmentParams.CLEANER_MAX_BATCH_FILES);

        if (clusterResident && clusterAll) {
            throw new IllegalArgumentException
                ("Both " + EnvironmentParams.CLEANER_CLUSTER +
                 " and " + EnvironmentParams.CLEANER_CLUSTER_ALL +
                 " may not be set to true.");
        }

        int nThreads = cm.getInt(EnvironmentParams.CLEANER_THREADS);
        assert nThreads > 0;

        if (nThreads != threads.length) {

            /* Shutdown threads when reducing their number. */
            for (int i = nThreads; i < threads.length; i += 1) {
                if (threads[i] != null) {
                    threads[i].shutdown();
                    threads[i] = null;
                }
            }

            /* Copy existing threads that are still used. */
            FileProcessor[] newThreads = new FileProcessor[nThreads];
            for (int i = 0; i < nThreads && i < threads.length; i += 1) {
                newThreads[i] = threads[i];
            }

            /* Don't lose track of new threads if an exception occurs. */
            threads = newThreads;

            /* Start new threads when increasing their number. */
            for (int i = 0; i < nThreads; i += 1) {
                if (threads[i] == null) {
                    threads[i] = new FileProcessor
                        (name + '-' + (i + 1),
                         env, this, profile, calculator, fileSelector);
                }
            }
        }

        cleanerBytesInterval = cm.getLong
            (EnvironmentParams.CLEANER_BYTES_INTERVAL);
        if (cleanerBytesInterval == 0) {
            cleanerBytesInterval = cm.getLong
                (EnvironmentParams.LOG_FILE_MAX) / 4;
        }

        fetchObsoleteSize = cm.getBoolean
            (EnvironmentParams.CLEANER_FETCH_OBSOLETE_SIZE);

        /*
         * In addition to honoring the CLEANER_LAZY_MIGRATION, lazy migration
         * of LNs is disabled if CHECKPOINTER_HIGH_PRIORITY is true.  LN
         * migration slows down the checkpoint and so LNs are migrated by
         * FileProcessor when high priority checkpoints are configured.
         */
        lazyMigration =
            cm.getBoolean(EnvironmentParams.CLEANER_LAZY_MIGRATION) &&
            !cm.getBoolean(EnvironmentParams.CHECKPOINTER_HIGH_PRIORITY);

        minAge = cm.getInt(EnvironmentParams.CLEANER_MIN_AGE);
        minUtilization = cm.getInt(EnvironmentParams.CLEANER_MIN_UTILIZATION);
        minFileUtilization = cm.getInt
            (EnvironmentParams.CLEANER_MIN_FILE_UTILIZATION);
    }

    public UtilizationTracker getUtilizationTracker() {
        return tracker;
    }

    public UtilizationProfile getUtilizationProfile() {
        return profile;
    }

    public UtilizationCalculator getUtilizationCalculator() {
        return calculator;
    }

    public FileSelector getFileSelector() {
        return fileSelector;
    }

    public boolean getFetchObsoleteSize() {
        return fetchObsoleteSize;
    }

    /**
     * @see EnvironmentParams#CLEANER_RMW_FIX
     * @see FileSummaryLN#postFetchInit
     */
    public boolean isRMWFixEnabled() {
        return rmwFixEnabled;
    }

    /* For unit testing only. */
    public void setFileChosenHook(TestHook hook) {
        fileChosenHook = hook;
    }

    public CleanerLogSummary getLogSummary() {
        return calculator.getLogSummary();
    }

    public void setLogSummary(CleanerLogSummary logSummary) {
        calculator.setLogSummary(logSummary);
    }

    /*
     * Delegate the run/pause/wakeup/shutdown DaemonRunner operations.  We
     * always check for null to account for the possibility of exceptions
     * during thread creation.  Cleaner daemon can't ever be run if No Locking
     * mode is enabled.
     */
    public void runOrPause(boolean run) {
        if (!env.isNoLocking()) {
            for (FileProcessor processor : threads) {
                if (processor != null) {
                    processor.runOrPause(run);
                }
            }
        }
    }

    public void wakeup() {
        for (FileProcessor thread : threads) {
            if (thread != null) {
                thread.wakeup();
            }
        }
    }

    public void requestShutdown() {
        for (FileProcessor thread : threads) {
            if (thread != null) {
                thread.requestShutdown();
            }
        }
    }

    public void shutdown() {
        for (int i = 0; i < threads.length; i += 1) {
            if (threads[i] != null) {
                threads[i].shutdown();
                threads[i].clearEnv();
                threads[i] = null;
            }
        }
    }

    public int getNWakeupRequests() {
        int count = 0;
        for (FileProcessor thread : threads) {
            if (thread != null) {
                count += thread.getNWakeupRequests();
            }
        }
        return count;
    }

    private boolean areThreadsRunning() {
        for (FileProcessor thread : threads) {
            if (thread != null) {
                return thread.isRunning();
            }
        }
        return false;
    }

    /**
     * @see ExceptionListenerUser#setExceptionListener(ExceptionListener)
     */
    public void setExceptionListener(ExceptionListener exceptionListener) {
        for (FileProcessor thread : threads) {
            if (thread != null) {
                thread.setExceptionListener(exceptionListener);
            }
        }
    }

    /**
     * Cleans selected files and returns the number of files cleaned.  This
     * method is not invoked by a deamon thread, it is programatically.
     *
     * @param cleanMultipleFiles is true to clean until we're under budget,
     * or false to clean at most one file.
     *
     * @param forceCleaning is true to clean even if we're not under the
     * utilization threshold.
     *
     * @return the number of files cleaned, not including files cleaned
     * unsuccessfully.
     */
    public int doClean(boolean cleanMultipleFiles, boolean forceCleaning)
        throws DatabaseException {

        FileProcessor processor = new FileProcessor
            ("", env, this, profile, calculator, fileSelector);
        return processor.doClean
            (false /*invokedFromDaemon*/, cleanMultipleFiles, forceCleaning);
    }

    /**
     * Load stats.
     */
    public StatGroup loadStats(StatsConfig config) {

        if (!config.getFast()) {
            totalLogSize.set(profile.getTotalLogSize());
        }

        correctedAvgLNSize.set(calculator.getCorrectedAvgLNSize());
        estimatedAvgLNSize.set(calculator.getEstimatedAvgLNSize());

        StatGroup copyStats = stats.cloneGroup(config.getClear());
        /* Add the FileSelector's stats to the cleaner stat group. */
        copyStats.addAll(fileSelector.loadStats());

        return copyStats;
    }

    /**
     * Deletes all files that are safe-to-delete and which are not protected by
     * a DbBackup or replication. Files are deleted only if there are no
     * read-only processes.
     *
     * Log file deletion is coordinated by the use of three mechanisms:
     *
     * 1) To guard against read/only processes, the would-be deleter tries to
     * get an exclusive lock on the environment. This will not be possible if a
     * read/only process exists.  File locks must be used for inter-process
     * coordination. But note that file locks are not supported intra-process.
     *
     * 2) Synchronization on the protectedFileRanges field.  Elements are added
     * to and removed from the protectedFileRanges collection by DbBackup.
     * More than one backup may be occuring at once, hence a collection of
     * protectedFileRanges is maintained, and the files protected are the range
     * starting with the minimum value returned by the objects in that
     * collection.
     *
     * 3) In a replicated environment, files are protected from deletion by the
     * CBVLSN (CleanerBarrier VLSN). No file greater or equal to the CBVLSN
     * file may be deleted.
     *
     * For case (2) and (3), all coordinated activities -- replication, backup
     * and file deletion -- can only be carried out by a read-write process, so
     * we know that all activities are occurring in the same process because
     * there can only be one JE read-write process per environment.
     *
     * This method is synchronized to prevent multiple threads from requesting
     * the environment lock or deleting the same files.
     */
    synchronized void deleteSafeToDeleteFiles()
        throws DatabaseException {

        /* Fail loudly if the environment is invalid. */
        env.checkIfInvalid();

        /* Fail silently if the environment is not open. */
        if (env.mayNotWrite()) {
            return;
        }

        SortedSet<Long> safeToDeleteFiles =
            fileSelector.copySafeToDeleteFiles();
        if (safeToDeleteFiles == null) {
            return; /* Nothing to do. */
        }

        /*
         * Get the protected file range from HA for the CBVLSN.  This value
         * cannot move backward, only forward over time.  We can safely use
         * this value as a pessimistic minimum without synchronization.
         * [#16820]
         */
        long minProtectedFile = env.getCleanerBarrierStartFile();
        if (minProtectedFile == -1) {

            /*
             * The replicated node is not available, so the cleaner barrier can
             * not be read. Don't delete any files.
             */
            return;
        }
        if (lastCleanerBarrierStartFile > minProtectedFile) {
            throw EnvironmentFailureException.unexpectedState
                ("Cleaner barrier file has moved backward, prevValue=" +
                 lastCleanerBarrierStartFile + " newValue=" +
                 minProtectedFile);
        }
        lastCleanerBarrierStartFile = minProtectedFile;

        SortedSet<Long> unprotectedFiles =
            safeToDeleteFiles.headSet(minProtectedFile);
        if (unprotectedFiles.isEmpty()) {
            /* Leave a clue for analyzing log file deletion problems. */
            LoggerUtils.traceAndLog(logger, env, Level.WARNING,
                                    "Cleaner has " + safeToDeleteFiles.size() +
                                    " files not deleted because they are " +
                                    "protected by replication.");
            return; /* Nothing to do. */
        }

        /*
         * Truncate the entries in the VLSNIndex that reference VLSNs in the
         * files to be deleted.  [#16566]
         *
         * This is done prior to deleting the files to ensure that the
         * replicator removes the files from the VLSNIndex.  If we were to
         * truncate after deleting a file, we may crash before the truncation
         * and would have to "replay" the truncation later in
         * UtilizationProfile.populateCache.  This would be more complex and
         * the lastVLSN for the files would not be available.
         *
         * OTOH, if we crash after the truncation and before deleting a file,
         * it is very likely that we will re-clean the zero utilization file
         * and delete it later.  This will only cause a redundant truncation.
         *
         * This is done before locking the environment to minimize the interval
         * during which the environment is locked and read-only processes are
         * blocked.  We may unnecessarily truncate the VLSNIndex if we can't
         * lock the environment, but that is a lesser priority.
         *
         * We intentionally do not honor the protected file ranges specified by
         * DbBackups when truncating, because the VLSNIndex is protected only
         * by the CBVLSN.  Luckily, this also means we do not need to
         * synchronize on protectedFileRanges while truncating, and DbBackups
         * will not be blocked by this potentially expensive operation.
         */
        Long[] unprotectedFilesArray = unprotectedFiles.toArray(new Long[0]);
        for (int i = unprotectedFilesArray.length - 1; i >= 0; i -= 1) {
            Long fileNum = unprotectedFilesArray[i];

            /*
             * Truncate VLSNIndex for the highest numbered file with a VLSN. We
             * search from high to low because some files may not contain a
             * VLSN. If the truncate does have to do work, the VLSNIndex will
             * ensure that the change is fsynced to disk. [#20702]
             */
            VLSN lastVlsn = fileSelector.getLastVLSN(fileNum);
            if ((lastVlsn != null) && !lastVlsn.isNull()) {
                env.vlsnHeadTruncate(lastVlsn, fileNum);
                break;
            }
        }

        /*
         * If we can't get an exclusive lock, then there are other processes
         * with the environment open read-only and we can't delete any files.
         */
        final FileManager fileManager = env.getFileManager();
        if (!fileManager.lockEnvironment(false, true)) {
            LoggerUtils.traceAndLog(logger, env, Level.WARNING,
                                    "Cleaner has " + safeToDeleteFiles.size() +
                                    " files not deleted because of read-only" +
                                    " processes.");
            return;
        }

        /* Be sure to release the environment lock in the finally block. */
        try {
            /* Synchronize while deleting files to block DbBackup.start. */
            synchronized (protectedFileRanges) {

                /* Intersect the protected ranges for active DbBackups. */
                if (!protectedFileRanges.isEmpty()) {
                    unprotectedFiles = unprotectedFiles.headSet
                        (Collections.min(protectedFileRanges));
                }

                /* Delete the unprotected files. */
                for (final Iterator<Long> iter = unprotectedFiles.iterator();
                     iter.hasNext();) {
                    final Long fileNum = iter.next();
                    final boolean deleted;
                    try {
                        if (expunge) {
                            deleted = fileManager.deleteFile(fileNum);
                        } else {
                            deleted = fileManager.renameFile
                                (fileNum, FileManager.DEL_SUFFIX);
                        }
                    } catch (IOException e) {
                        throw new EnvironmentFailureException
                            (env, EnvironmentFailureReason.LOG_WRITE,
                             "Unable to delete or rename " + fileNum, e);
                    }
                    if (deleted) {

                        /*
                         * Deletion was successful.  Log a trace message for
                         * debugging of log cleaning behavior.
                         */
                        LoggerUtils.traceAndLog(logger, env, Level.FINE,
                                                "Cleaner deleted file 0x" +
                                                Long.toHexString(fileNum));
                    } else if (!fileManager.isFileValid(fileNum)) {

                        /*
                         * Somehow the file was previously deleted.  This could
                         * indicate an internal state error, and therefore we
                         * output a trace message.  But we should not
                         * repeatedly attempt to delete it, so we do remove it
                         * from the profile below.
                         */
                        LoggerUtils.traceAndLog
                            (logger, env, Level.SEVERE,
                             "Cleaner deleteSafeToDeleteFiles Log file 0x" +
                             Long.toHexString(fileNum) + " was previously " +
                             (expunge ? "deleted" : "renamed") + ".  State: " +
                             fileSelector);
                    } else {

                        /*
                         * We will retry the deletion later if file still
                         * exists.  The deletion could have failed on Windows
                         * if the file was recently closed.  Remove the file
                         * from unprotectedFiles. That way, we won't remove it
                         * from the FileSelector's safe-to-delete set or the UP
                         * below, and we will retry the file deletion later.
                         */
                        iter.remove();

                        LoggerUtils.traceAndLog
                            (logger, env, Level.WARNING,
                             "Cleaner deleteSafeToDeleteFiles Log file 0x" +
                             Long.toHexString(fileNum) + " could not be " +
                             (expunge ? "deleted" : "renamed") + ". This " +
                             "operation will be retried at the next " +
                             "checkpoint. State: " + fileSelector);
                    }
                }
            }
        } finally {
            fileManager.releaseExclusiveLock();
        }

        /*
         * Now unprotectedFiles contains only the files we deleted above.  We
         * can update the UP (and FileSelector) here outside of the
         * synchronization block and without the environment locked.  That way,
         * DbBackups and read-only processes will not be blocked by the
         * expensive UP operation.
         *
         * We do not retry if an error occurs deleting the UP database entries
         * below.  Retrying (when file deletion fails) is intended only to
         * solve a problem on Windows where deleting a log file isn't always
         * possible immediately after closing it.
         *
         * Remove the file from the UP before removing it from the
         * FileSelector's safe-to-delete set.  If we remove in the reverse
         * order, it may be selected for cleaning.  Always remove the file from
         * the safe-to-delete set (in a finally block) so that we don't attempt
         * to delete the file again.
         */
        profile.removePerDbMetadata
            (unprotectedFiles,
             fileSelector.getCleanedDatabases(unprotectedFiles));
        for (Long fileNum : unprotectedFiles) {
            try {
                profile.removePerFileMetadata(fileNum);
            } finally {
                fileSelector.removeDeletedFile
                    (fileNum, env.getMemoryBudget());
            }
            nCleanerDeletions.increment();
        }

        /* Leave a clue for analyzing log file deletion problems. */
        if (safeToDeleteFiles.size() > unprotectedFiles.size()) {
            LoggerUtils.traceAndLog
                (logger, env, Level.WARNING,
                 "Cleaner has " +
                 (safeToDeleteFiles.size() - unprotectedFiles.size()) +
                 " files not deleted because they are protected by DbBackup " +
                 "or replication.");
        }
    }

    /**
     * Adds a range of log files to be protected from deletion during a backup
     * or similar procedures where log files must not be deleted.
     *
     * <p>This method is called automatically by the {@link
     * com.sleepycat.je.util.DbBackup} utility and is provided here as a
     * separate API for advanced applications that may implement a custom
     * backup procedure.</p>
     *
     * <p><em>WARNING:</em> After calling this method, deletion of log files in
     * the file range by the JE log cleaner will be disabled until {@link
     * #removeProtectedFileRange} is called.  To prevent unbounded growth of
     * disk usage, be sure to call {@link #removeProtectedFileRange} to
     * re-enable log file deletion.</p>
     *
     * @param firstProtectedFile the number of the first file to be protected.
     * The protected range is from this file number to the last (highest
     * numbered) file in the log.
     *
     * @since 4.0
     */
    public void addProtectedFileRange(long firstProtectedFile) {
        synchronized (protectedFileRanges) {
            protectedFileRanges.add(firstProtectedFile);
        }
    }

    /**
     * Removes a range of log files to be protected after calling {@link
     * #addProtectedFileRange}.
     *
     * @param firstProtectedFile the value previously passed to {@link
     * #addProtectedFileRange}.
     *
     * @throws EnvironmentFailureException if {@code firstProtectedFile} is not
     * currently the start of a protected range.
     *
     * @since 4.0
     */
    public void removeProtectedFileRange(long firstProtectedFile) {
        synchronized (protectedFileRanges) {
            if (!protectedFileRanges.remove(firstProtectedFile)) {
                throw EnvironmentFailureException.unexpectedState
                    ("File range starting with 0x" +
                     Long.toHexString(firstProtectedFile) +
                     " is not currently protected");
            }
        }
    }

    /**
     * Returns a copy of the cleaned and processed files at the time a
     * checkpoint starts.
     *
     * <p>If non-null is returned, the checkpoint should flush an extra level,
     * and addCheckpointedFiles() should be called when the checkpoint is
     * complete.</p>
     */
    public CheckpointStartCleanerState getFilesAtCheckpointStart()
        throws DatabaseException {

        /* Pending LNs can prevent file deletion. */
        processPending();

        return fileSelector.getFilesAtCheckpointStart();
    }

    /**
     * When a checkpoint is complete, update the files that were returned at
     * the beginning of the checkpoint.
     */
    public void updateFilesAtCheckpointEnd(CheckpointStartCleanerState info)
        throws DatabaseException {

        fileSelector.updateFilesAtCheckpointEnd(info);
        deleteSafeToDeleteFiles();
    }

    /**
     * Update the lowUtilizationFiles and toBeCleanedFiles fields with new
     * read-only collections.
     */
    public void updateReadOnlyFileCollections() {
        toBeCleanedFiles = fileSelector.getToBeCleanedFiles();
        lowUtilizationFiles = fileSelector.getLowUtilizationFiles();
    }

    /**
     * If any LNs are pending, process them.  This method should be called
     * often enough to prevent the pending LN set from growing too large.
     */
    void processPending()
        throws DatabaseException {

        DbTree dbMapTree = env.getDbTree();

        Map<Long, LNInfo> pendingLNs = fileSelector.getPendingLNs();
        if (pendingLNs != null) {
            TreeLocation location = new TreeLocation();

            for (Map.Entry<Long, LNInfo> entry : pendingLNs.entrySet()) {
                long originalLsn = entry.getKey();
                LNInfo info = entry.getValue();
                DatabaseId dbId = info.getDbId();
                DatabaseImpl db = dbMapTree.getDb(dbId, lockTimeout);
                try {
                    byte[] key = info.getKey();
                    LN ln = info.getLN();

                    /* Evict before processing each entry. */
                    if (DO_CRITICAL_EVICTION) {
                        env.daemonEviction(true /*backgroundIO*/);
                    }

                    processPendingLN(originalLsn, ln, db, key, location);
                } finally {
                    dbMapTree.releaseDb(db);
                }

                /* Sleep if background read/write limit was exceeded. */
                env.sleepAfterBackgroundIO();
            }
        }

        DatabaseId[] pendingDBs = fileSelector.getPendingDBs();
        if (pendingDBs != null) {
            for (DatabaseId dbId : pendingDBs) {
                DatabaseImpl db = dbMapTree.getDb(dbId, lockTimeout);
                try {
                    if (db == null || db.isDeleteFinished()) {
                        fileSelector.removePendingDB(dbId);
                    }
                } finally {
                    dbMapTree.releaseDb(db);
                }
            }
        }
    }

    /**
     * Processes a pending LN, getting the lock first to ensure that the
     * overhead of retries is mimimal.
     */
    private void processPendingLN(long originalLsn,
                                  LN ln,
                                  DatabaseImpl db,
                                  byte[] key,
                                  TreeLocation location)
        throws DatabaseException {

        boolean parentFound = false// We found the parent BIN.
        boolean processedHere = true; // The LN was cleaned here.
        boolean lockDenied = false;   // The LN lock was denied.
        boolean obsolete = false;     // The LN is no longer in use.
        boolean completed = false;    // This method completed.

        BasicLocker locker = null;
        BIN bin = null;
        try {
            nPendingLNsProcessed.increment();

            /*
             * If the DB is gone, this LN is obsolete.  If delete cleanup is in
             * progress, put the DB into the DB pending set; this LN will be
             * declared deleted after the delete cleanup is finished.
             */
            if (db == null || db.isDeleted()) {
                addPendingDB(db);
                nLNsDead.increment();
                obsolete = true;
                completed = true;
                return;
            }

            Tree tree = db.getTree();
            assert tree != null;

            /*
             * Get a non-blocking read lock on the original log LSN.  If this
             * fails, then the original LSN is still write-locked.  We may have
             * to lock again, if the LSN has changed in the BIN, but this
             * initial check prevents a Btree lookup in some cases.
             */
            locker = BasicLocker.createBasicLocker(env, false /*noWait*/);
            /* Don't allow this short-lived lock to be preempted/stolen. */
            locker.setPreemptable(false);
            LockResult lockRet =
                locker.nonBlockingLock(originalLsn, LockType.READ,
                                       false /*jumpAheadOfWaiters*/, db);
            if (lockRet.getLockGrant() == LockGrantType.DENIED) {
                /* Try again later. */
                nPendingLNsLocked.increment();
                lockDenied = true;
                completed = true;
                return;
            }

            /*
             * Search down to the bottom most level for the parent of this LN.
             */
            parentFound = tree.getParentBINForChildLN
                (location, key, false /*splitsAllowed*/,
                 true /*findDeletedEntries*/, UPDATE_GENERATION);
            bin = location.bin;
            int index = location.index;

            if (!parentFound) {
                nLNsDead.increment();
                obsolete = true;
                completed = true;
                return;
            }

            /* Migrate an LN. */
            processedHere = false;
            migrateLN
                (db, bin.getLsn(index), bin, index,
                 true,           // wasCleaned
                 true,           // isPending
                 originalLsn,
                 true,           // backgroundIO
                 CLEAN_PENDING_LN);
            completed = true;
        } catch (DatabaseException DBE) {
            DBE.printStackTrace();
            LoggerUtils.traceAndLogException
                (env, "com.sleepycat.je.cleaner.Cleaner",
                 "processLN", "Exception thrown: ", DBE);
            throw DBE;
        } finally {
            if (bin != null) {
                bin.releaseLatch();
            }

            if (locker != null) {
                locker.operationEnd();
            }

            /*
             * If migrateLN was not called above, remove the pending LN and
             * perform tracing in this method.
             */
            if (processedHere) {
                if (completed && !lockDenied) {
                    fileSelector.removePendingLN(originalLsn);
                }
                logFine(CLEAN_PENDING_LN, ln, DbLsn.NULL_LSN,
                        completed, obsolete, false /*migrated*/);
            }
        }
    }

    /**
     * Returns whether the given BIN entry may be stripped by the evictor.
     * True is always returned if the BIN is not dirty.  False is returned if
     * the BIN is dirty and the entry will be migrated soon.
     *
     * Note that the BIN may or may not be latched when this method is called.
     * Returning the wrong answer is OK in that case (it will be called again
     * later when latched), but an exception should not occur.
     */
    public boolean isEvictable(final BIN bin, final int index) {

        if (bin.getDirty()) {

            if (bin.getMigrate(index)) {
                return false;
            }

            final long lsn = bin.getLsn(index);
            if (lsn == DbLsn.NULL_LSN) {

                /*
                 * LN is resident but never logged, no cleaning restrictions
                 * apply.
                 */
                return true;
            }

            final Long fileNum = Long.valueOf(DbLsn.getFileNumber(lsn));

            /*
             * Assume foreground eviction for now.  If we resurrect the
             * background eviction thread, the backgroundIO parameter should be
             * passed down and used here.
             */
            if (foregroundProactiveMigration &&
                toBeCleanedFiles.contains(fileNum)) {
                return false;
            }

            if ((clusterAll || clusterResident) &&
                lowUtilizationFiles.contains(fileNum)) {
                return false;
            }
        }

        return true;
    }

    /**
     * This method should be called just before logging a BIN.  LNs will be
     * migrated if the MIGRATE flag is set, or if they are in a file to be
     * cleaned, or if the LNs qualify according to the rules for cluster and
     * clusterAll.
     *
     * <p>On return this method guarantees that no MIGRATE flag will be set on
     * any child entry.  If this method is *not* called before logging a BIN,
     * then the addPendingLN method must be called.</p>
     *
     * @param bin is the latched BIN.  The latch will not be released by this
     * method.
     */
    public void lazyMigrateLNs(final BIN bin, final boolean backgroundIO)
        throws DatabaseException {

        DatabaseImpl db = bin.getDatabase();

        /*
         * For non-resident LNs, sort them by LSN before migrating them.
         * Fetching in LSN order reduces physical disk I/O.
         */
        Integer[] sortedIndices = null;
        int nSortedIndices = 0;
        int nEntries = bin.getNEntries();

        for (int index = 0; index < nEntries; index += 1) {

            boolean migrateFlag = bin.getMigrate(index);
            boolean isResident = (bin.getTarget(index) != null);
            long childLsn = bin.getLsn(index);

            if (childLsn != DbLsn.NULL_LSN) {
                /* LSN could be NULL_LSN if deferred-write mode */

                if (shouldMigrateLN
                    (migrateFlag, isResident, backgroundIO, childLsn)) {

                     if (isResident) {
                         migrateLN
                         (db, childLsn, bin, index,
                          migrateFlag, // wasCleaned
                          false,       // isPending
                          0,           // lockedPendingLsn
                          backgroundIO,
                          CLEAN_MIGRATE_LN);
                     } else {
                         if (sortedIndices == null) {
                             sortedIndices = new Integer[nEntries];
                         }
                         sortedIndices[nSortedIndices++] =
                             Integer.valueOf(index);
                     }
                }
            }
        }

        if (sortedIndices != null) {
            Arrays.sort(sortedIndices,
                        0,
                        nSortedIndices,
                        new Comparator<Integer>() {
                public int compare( Integer int1, Integer int2) {
                    return DbLsn.compareTo(bin.getLsn(int1), bin.getLsn(int2));
                }
            });
            for (int i = 0; i < nSortedIndices; i += 1) {
                int index = sortedIndices[i].intValue();
                long childLsn = bin.getLsn(index);
                boolean migrateFlag = bin.getMigrate(index);
                migrateLN
                    (db, childLsn, bin, index,
                     migrateFlag, // wasCleaned
                     false,       // isPending
                     0,           // lockedPendingLsn
                     backgroundIO,
                     CLEAN_MIGRATE_LN);
            }
        }
    }

    /**
     * Returns whether an LN entry should be migrated.  Updates stats.
     *
     * @param migrateFlag is whether the MIGRATE flag is set on the entry.
     *
     * @param isResident is whether the LN is currently resident.
     *
     * @param childLsn is the LSN of the LN.
     *
     * @return whether to migrate the LN.
     */
    private boolean shouldMigrateLN(final boolean migrateFlag,
                                    final boolean isResident,
                                    final boolean backgroundIO,
                                    final long childLsn) {
        if (migrateFlag) {

            /*
             * Always try to migrate if the MIGRATE flag is set, since the LN
             * has been processed.  If we did not migrate it, we would have to
             * add it to pending LN set.
             */
            nMarkedLNsProcessed.increment();
            return true;
        }

        if (env.isClosing()) {

            /*
             * Do nothing if the environment is shutting down and the
             * MIGRATE flag is not set.  Proactive migration during
             * shutdown is counterproductive -- it prevents a short final
             * checkpoint, and it does not allow more files to be deleted.
             */
            return false;
        }

        final Long fileNum = Long.valueOf(DbLsn.getFileNumber(childLsn));

        if ((isResident || (backgroundIO ?
                            backgroundProactiveMigration :
                            foregroundProactiveMigration)) &&
            toBeCleanedFiles.contains(fileNum)) {
            /* Migrate because it will be cleaned soon. */
            nToBeCleanedLNsProcessed.increment();
            return true;
        }

        if ((clusterAll || (clusterResident && isResident)) &&
            lowUtilizationFiles.contains(fileNum)) {
            /* Migrate for clustering. */
            nClusterLNsProcessed.increment();
            return true;
        }

        return false;
    }

    /**
     * Migrate an LN in the given BIN entry, if it is not obsolete.  The BIN is
     * latched on entry to this method and is left latched when it returns.
     */
    private void migrateLN(DatabaseImpl db,
                           long lsn,
                           BIN bin,
                           int index,
                           boolean wasCleaned,
                           boolean isPending,
                           long lockedPendingLsn,
                           boolean backgroundIO,
                           String cleanAction)
        throws DatabaseException {

        /* 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.
        boolean clearTarget = false; // Node was non-resident when called.

        /*
         * 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 {
            if (lsn == DbLsn.NULL_LSN) {
                /* This node was never written, no need to migrate. */
                completed = true;
                return;
            }

            /*
             * 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.increment();
                }
                obsolete = true;
                completed = true;
                return;
            }

            /*
             * Get a non-blocking read lock on the LN.  A pending node is
             * already locked, but the original pending LSN may have changed.
             * We must lock the current LSN to guard against aborts.
             */
            if (lockedPendingLsn != lsn) {
                locker = BasicLocker.createBasicLocker(env, false /*noWait*/);
                /* Don't allow this short-lived lock to be preempted/stolen. */
                locker.setPreemptable(false);
                LockResult lockRet = locker.nonBlockingLock
                    (lsn, 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.
                     */
                    if (wasCleaned) {
                        nLNsLocked.increment();
                    }
                    lockDenied = true;
                    completed = true;
                    return;
                }
            }

            /* Don't migrate deleted LNs.  */
            if (ln.isDeleted()) {
                bin.setKnownDeleted(index);
                if (wasCleaned) {
                    nLNsDead.increment();
                }
                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 = Long.valueOf(DbLsn.getFileNumber(lsn));
                if (!fileSelector.isFileCleaningInProgress(fileNum)) {
                    obsolete = true;
                    completed = true;
                    if (wasCleaned) {
                        nLNsDead.increment();
                    }
                    return;
                }
            }

            /*
             * Migrate the LN.
             *
             * Do not pass a locker, because there is no need to lock the new
             * LSN, as done for user operations.  Another locker cannot attempt
             * to lock the new LSN until we're done, because we release the
             * lock before we release the BIN latch.
             */
            long newLNLsn = ln.log(env, db, bin.getKey(index), lsn,
                                   backgroundIO,
                                   getMigrationRepContext(ln));
            bin.updateEntry(index, newLNLsn);
            nLNsMigrated.increment();
            /* Lock new LSN on behalf of existing lockers. */
            CursorImpl.lockAfterLsnChange
                (db, lsn, newLNLsn, locker /*excludeLocker*/);
            migrated = true;
            completed = true;
            return;
        } finally {
            if (isPending) {
                if (completed && !lockDenied) {
                    fileSelector.removePendingLN(lockedPendingLsn);
                }
            } 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.
                 *
                 * Note that the LN may be null if fetchTarget threw an
                 * exception above. [#16039]
                 */
                if (bin.getMigrate(index) &&
                    (!completed || lockDenied) &&
                    (ln != null)) {

                    fileSelector.addPendingLN(lsn, ln, db.getId(),
                                              bin.getKey(index));

                    /* 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.updateNode(index, null /*node*/, null /*lnSlotKey*/);
            }

            if (locker != null) {
                locker.operationEnd();
            }

            logFine(cleanAction, ln, lsn, completed, obsolete, migrated);
        }
    }

    /**
     * Returns the ReplicationContext to use for migrating the given LN.  If
     * VLSNs are preserved in this Environment then the VLSN is logically part
     * of the data record, and LN.getVLSNSequence will return the VLSN, which
     * should be included in the migrated LN.
     */
    static ReplicationContext getMigrationRepContext(LN ln) {
        long vlsnSeq = ln.getVLSNSequence();
        if (vlsnSeq <= 0) {
            return ReplicationContext.NO_REPLICATE;
        }
        return new ReplicationContext(new VLSN(vlsnSeq),
                                      false /*inReplicationStream*/);
    }

    /**
     * Adds the DB ID to the pending DB set if it is being deleted but deletion
     * is not yet complete.
     */
    void addPendingDB(DatabaseImpl db) {
        if (db != null && db.isDeleted() && !db.isDeleteFinished()) {
            DatabaseId id = db.getId();
            if (fileSelector.addPendingDB(id)) {
                LoggerUtils.logMsg(logger, env, Level.FINE,
                                   "CleanAddPendingDB " + id);
            }
        }
    }

    /**
     * Send trace messages to the java.util.logger. Don't rely on the logger
     * alone to conditionalize whether we send this message, we don't even want
     * to construct the message if the level is not enabled.
     */
    void logFine(String action,
               Node node,
               long logLsn,
               boolean completed,
               boolean obsolete,
               boolean dirtiedMigrated) {

        if (logger.isLoggable(Level.FINE)) {
            StringBuilder sb = new StringBuilder();
            sb.append(action);
            if (node instanceof IN) {
                sb.append(" node=");
                sb.append(((IN) node).getNodeId());
            }
            sb.append(" logLsn=");
            sb.append(DbLsn.getNoFormatString(logLsn));
            sb.append(" complete=").append(completed);
            sb.append(" obsolete=").append(obsolete);
            sb.append(" dirtiedOrMigrated=").append(dirtiedMigrated);

            LoggerUtils.logMsg(logger, env, Level.FINE, sb.toString());
        }
    }

    /**
     * Release resources and update memory budget. Should only be called
     * when this environment is closed and will never be accessed again.
     */
    public void close() {
        profile.close();
        tracker.close();
        fileSelector.close(env.getMemoryBudget());
    }
}
TOP

Related Classes of com.sleepycat.je.cleaner.Cleaner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.