Package com.sleepycat.je.evictor

Source Code of com.sleepycat.je.evictor.RootEvictor

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

package com.sleepycat.je.evictor;

import static com.sleepycat.je.evictor.EvictorStatDefinition.AVG_BATCH_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_EVICTION_TYPE_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_FETCH;
import static com.sleepycat.je.evictor.EvictorStatDefinition.BIN_FETCH_MISS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.CACHED_IN_COMPACT_KEY;
import static com.sleepycat.je.evictor.EvictorStatDefinition.CACHED_IN_NO_TARGET;
import static com.sleepycat.je.evictor.EvictorStatDefinition.CACHED_IN_SPARSE_TARGET;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_BINS_STRIPPED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_EVICT_PASSES;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_EVICTED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_NODES_SCANNED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.EVICTOR_ROOT_NODES_EVICTED;
import static com.sleepycat.je.evictor.EvictorStatDefinition.GROUP_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.GROUP_NAME;
import static com.sleepycat.je.evictor.EvictorStatDefinition.LN_FETCH;
import static com.sleepycat.je.evictor.EvictorStatDefinition.LN_FETCH_MISS;
import static com.sleepycat.je.evictor.EvictorStatDefinition.NUM_BATCHES_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.THREAD_UNAVAILABLE;
import static com.sleepycat.je.evictor.EvictorStatDefinition.UPPER_IN_EVICTION_TYPE_DESC;
import static com.sleepycat.je.evictor.EvictorStatDefinition.UPPER_IN_FETCH;
import static com.sleepycat.je.evictor.EvictorStatDefinition.UPPER_IN_FETCH_MISS;

import java.util.EnumSet;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
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.StatsConfig;
import com.sleepycat.je.config.EnvironmentParams;
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.EnvironmentImpl;
import com.sleepycat.je.dbi.INList;
import com.sleepycat.je.evictor.TargetSelector.ScanInfo;
import com.sleepycat.je.evictor.TargetSelector.SetupInfo;
import com.sleepycat.je.recovery.Checkpointer;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.ChildReference;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.Node;
import com.sleepycat.je.tree.SearchResult;
import com.sleepycat.je.tree.Tree;
import com.sleepycat.je.tree.WithRootLatched;
import com.sleepycat.je.utilint.AtomicLongStat;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.LongStat;
import com.sleepycat.je.utilint.StatDefinition;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.StoppableThreadFactory;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.TestHookExecute;

/**
* The Evictor is responsible for maintaining the JE cache. Since object sizes
* are not directly manipulated in a Java application, the cache is actually a
* collection of in-memory btree nodes, implemented by
* com.sleepycat.je.dbi.INList. Nodes are selected from the INList for removal,
* which is done by detaching them from the in-memory tree, and by removing
* them from the INList. Once all references to them are removed, they can be
* GC'd by the JVM.
*
* There are three main components.
*
* Arbiter: queries the memory budget to decide whether eviction is needed
* TargetSelector : chooses a target node
* Evictor: does the work of detaching the node.
*
* The TargetSelector and Evictor classes are subclassed to provide
* private/shared cache implementations. A shared cache is used by multiple
* environments within a single JVM, and is seen logically as a single INList
* collection, although it is implemented by an umbrella over multiple INLists.
*
* The Evictor owns a thread pool which is available to handle eviction tasks.
* Eviction is carried out by three types of threads:
* 1. The application thread, in the course of doing critical eviction
* 2. Daemon threads, such as the cleaner or INCompressor, in the course of
*    doing their respective duties
* 3. Eviction pool threads
*
* We prefer that the eviction pool threads do as much of the eviction as
* possible, and that the application threads do as little, because eviction
* adds latency to the perceived application response time. To date, it has
* been impossible to completely remove eviction responsiblities from the
* application threads, because the process doesn't have sufficient feedback,
* and can incur an OutOfMemoryException.
*
* The eviction pool is a standard java.util.concurrent thread pool, and can
* be mutably configured in terms of core threads, max threads, and keepalive
* times.
*
* Since three types of threads can concurrently do eviction, it's important
* that eviction is both thread safe and as parallel as possible.  Memory
* thresholds are generally accounted for in an unsynchronized fashion, and are
* seen as advisory. The only point of true synchronization is around the
* selection of a node for eviction. The act of eviction itself can be done
* concurrently.
*
* The eviction method is not reentrant, and a simple concurrent hash map
* of threads is used to prevent recursive calls.
*/
public abstract class Evictor implements EnvConfigObserver {
   
    /*
     * If new eviction source enums are added, a new stat is created, and
     * EnvironmentStats must be updated to add a getter method.
     *
     * CRITICAL eviction is called by operations executed app or daemon
     * threads which detect that the cache has reached its limits
     * CACHE_MODE eviction is called by operations that use a specific
     * Cursor.
     * EVICTORThread is the eviction pool
     * MANUAL is the call to Environment.evictMemory, called by recovery or
     *   application code.
     */
    public enum EvictionSource {
        /* Using ordinal for array values! */
        EVICTORTHREAD, MANUAL, CRITICAL, CACHEMODE, DAEMON;

        public StatDefinition getBINStatDef() {
            return new StatDefinition("nBINsEvicted" + toString(),
                                      BIN_EVICTION_TYPE_DESC);
        }

        public StatDefinition getUpperINStatDef() {
            return new StatDefinition("nUpperINsEvicted" + toString(),
                                      UPPER_IN_EVICTION_TYPE_DESC);
        }

        public StatDefinition getNumBatchesStatDef() {
            return new StatDefinition("nBatches" + toString(),
                                      NUM_BATCHES_DESC);
        }

        public StatDefinition getAvgBatchStatDef() {
            return new StatDefinition("avgBatch" + toString(),
                                      AVG_BATCH_DESC);
        }
    }

    final EnvironmentImpl envImpl;
    private final TargetSelector selector;
    private final Arbiter arbiter;

    /* The thread pool used to manage the background evictor threads. */
    private final ThreadPoolExecutor evictionPool;
    private int terminateMillis;

    /*
     * runEvictor is needed as a distinct flag, rather than setting maxThreads
     * to 0, because the ThreadPoolExecutor does not permit  maxThreads to be 0.
     */
    private boolean runEvictor;

    /*
     * Whether to allow deltas when logging a BIN.
     */
    private boolean allowBinDeltas;

    /* Prevent endless eviction loops under extreme resource constraints. */
    private static final int MAX_BATCHES_PER_RUN = 100;

    /*
     * Stats
     */
    private final StatGroup stats;

    /* Number of passes made to the evictor. */
    private final LongStat nEvictPasses;
    /* Number of nodes scanned in order to select the eviction set */
    private final LongStat nNodesScanned;

    /*
     * Number of nodes evicted on this run. This could be understated, as a
     * whole subtree may have gone out with a single node.
     */
    private final LongStat nNodesEvicted;
    /* Number of closed database root nodes evicted on this run. */
    private final LongStat nRootNodesEvicted;
    /* Number of BINs stripped. */
    private final LongStat nBINsStripped;

    /*
     * Tree related cache hit/miss stats. A subset of the cache misses recorded
     * by the log manager, in that these only record tree node hits and misses.
     * Recorded by IN.fetchTarget, but grouped with evictor stats. Use
     * AtomicLongStat for multithreading safety.
     */
    private final AtomicLongStat nLNFetch;
    private final AtomicLongStat nBINFetch;
    private final AtomicLongStat nUpperINFetch;
    private final AtomicLongStat nLNFetchMiss;
    private final AtomicLongStat nBINFetchMiss;
    private final AtomicLongStat nUpperINFetchMiss;

    private final AtomicLongStat nThreadUnavailable;
   
     /* Stats for IN compact array representations currently in cache. */
    private final AtomicLong nINSparseTarget;
    private final AtomicLong nINNoTarget;
    private final AtomicLong nINCompactKey;

    /*
     * Array of stats is indexed into by the EvictionSource ordinal value.
     * A EnumMap could have been an alternative, but would be heavier weight.
     */
    private final AtomicLongStat[] binEvictSources;
    private final AtomicLongStat[] inEvictSources;
    private final LongStat[] batchesPerSource;
    private final LongStat[] avgBatchPerSource;
    private final AtomicLong[] numBatchTargets;
    private final AtomicLong[] numBatches;

    /* Debugging and unit test support. */
    private TestHook<Object> preEvictINHook;
    private TestHook<IN> evictProfile;

    /* Eviction calls cannot be recursive. */
    private final ReentrancyGuard reentrancyGuard;

    /* Flag to help shutdown launched eviction tasks. */
    private final AtomicBoolean shutdownRequested;

    private final Logger logger;

    Evictor(EnvironmentImpl envImpl)
        throws DatabaseException {

        this.envImpl = envImpl;

        /* Do the stats definitions. */
        stats = new StatGroup(GROUP_NAME, GROUP_DESC);
        nEvictPasses = new LongStat(stats, EVICTOR_EVICT_PASSES);
        nNodesScanned = new LongStat(stats, EVICTOR_NODES_SCANNED);
        nNodesEvicted = new LongStat(stats, EVICTOR_NODES_EVICTED);
        nRootNodesEvicted = new LongStat(stats, EVICTOR_ROOT_NODES_EVICTED);
        nBINsStripped = new LongStat(stats, EVICTOR_BINS_STRIPPED);

        nLNFetch = new AtomicLongStat(stats, LN_FETCH);
        nBINFetch = new AtomicLongStat(stats, BIN_FETCH);
        nUpperINFetch = new AtomicLongStat(stats, UPPER_IN_FETCH);
        nLNFetchMiss = new AtomicLongStat(stats, LN_FETCH_MISS);
        nBINFetchMiss = new AtomicLongStat(stats, BIN_FETCH_MISS);
        nUpperINFetchMiss = new AtomicLongStat(stats, UPPER_IN_FETCH_MISS);
        nThreadUnavailable = new AtomicLongStat(stats, THREAD_UNAVAILABLE);

        nINSparseTarget =  new AtomicLong(0);
        nINNoTarget = new AtomicLong(0);
        nINCompactKey = new AtomicLong(0);

        EnumSet<EvictionSource> allSources =
            EnumSet.allOf(EvictionSource.class);
        int numSources = allSources.size();
           
        binEvictSources = new AtomicLongStat[numSources];
        inEvictSources = new AtomicLongStat[numSources];
        numBatches = new AtomicLong[numSources];   
        numBatchTargets = new AtomicLong[numSources];
        batchesPerSource = new LongStat[numSources];
        avgBatchPerSource = new LongStat[numSources];
           
        for (EvictionSource source : allSources) {
            int index = source.ordinal();
            binEvictSources[index] =
                new AtomicLongStat(stats, source.getBINStatDef());
            inEvictSources[index] =
                new AtomicLongStat(stats, source.getUpperINStatDef());
            numBatches[index] = new AtomicLong();
            numBatchTargets[index] = new AtomicLong();
            batchesPerSource[index] =
                new LongStat(stats, source.getNumBatchesStatDef());
            avgBatchPerSource[index] =
                new LongStat(stats, source.getAvgBatchStatDef());
        }

        selector = makeSelector();
        arbiter = new Arbiter(envImpl);

        logger = LoggerUtils.getLogger(getClass());
        reentrancyGuard = new ReentrancyGuard(envImpl, logger);
        shutdownRequested = new AtomicBoolean(false);

        DbConfigManager configManager = envImpl.getConfigManager();

        int corePoolSize =
            configManager.getInt(EnvironmentParams.EVICTOR_CORE_THREADS);
        int maxPoolSize =
            configManager.getInt(EnvironmentParams.EVICTOR_MAX_THREADS);
        long keepAliveTime =
            configManager.getDuration(EnvironmentParams.EVICTOR_KEEP_ALIVE);
        terminateMillis = configManager.getDuration
            (EnvironmentParams.EVICTOR_TERMINATE_TIMEOUT);

        RejectedExecutionHandler rejectHandler =
            new RejectEvictHandler(nThreadUnavailable);
       
        evictionPool =
            new ThreadPoolExecutor(corePoolSize,
                                   maxPoolSize,
                                   keepAliveTime,
                                   TimeUnit.MILLISECONDS,
                                   new ArrayBlockingQueue<Runnable>(1),
                                   new StoppableThreadFactory(envImpl,
                                                              "JEEvictor",
                                                              logger),
                                   rejectHandler);

        runEvictor =
            configManager.getBoolean(EnvironmentParams.ENV_RUN_EVICTOR);

        allowBinDeltas = configManager.getBoolean
            (EnvironmentParams.EVICTOR_ALLOW_BIN_DELTAS);

        /*
         * Request notification of mutable property changes. Do this after all
         * fields in the evictor have been initialized, in case this is called
         * quite soon.
         */
        envImpl.addConfigObserver(this);
    }

    /**
     * Respond to config updates.
     */
    public void envConfigUpdate(DbConfigManager configManager,
                                EnvironmentMutableConfig ignore)
        throws DatabaseException {

        int corePoolSize =
            configManager.getInt(EnvironmentParams.EVICTOR_CORE_THREADS);
        int maxPoolSize =
            configManager.getInt(EnvironmentParams.EVICTOR_MAX_THREADS);
        long keepAliveTime =
            configManager.getDuration(EnvironmentParams.EVICTOR_KEEP_ALIVE);

        terminateMillis = configManager.getDuration
            (EnvironmentParams.EVICTOR_TERMINATE_TIMEOUT);

        evictionPool.setCorePoolSize(corePoolSize);
        evictionPool.setMaximumPoolSize(maxPoolSize);
        evictionPool.setKeepAliveTime(keepAliveTime, TimeUnit.MILLISECONDS);
        runEvictor =
            configManager.getBoolean(EnvironmentParams.ENV_RUN_EVICTOR);
    }

    /*
     * Node selection varies based on whether this is a private or shared
     * cache.
     */
    abstract TargetSelector makeSelector();

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

        StatGroup copy = stats.cloneGroup(config.getClear());

        /*
         * These stats are not cleared. They represent the current state of
         * the cache.
         */
        new LongStat(copy, CACHED_IN_SPARSE_TARGET, nINSparseTarget.get());
        new LongStat(copy, CACHED_IN_NO_TARGET, nINNoTarget.get());
        new LongStat(copy, CACHED_IN_COMPACT_KEY, nINCompactKey.get());
        copy.addAll(selector.loadStats(config));
        copy.addAll(arbiter.loadStats(config));

        /*
         * The number and average size of batches, by type of caller, is
         * calculated each time we collect stats.
         */
        EnumSet<EvictionSource> allSources =
            EnumSet.allOf(EvictionSource.class);
        for (EvictionSource source : allSources) {
            int index = source.ordinal();

            long nBatches = numBatches[index].getAndSet(0);

            /* Guard against dividing by 0 */
            long avg = (nBatches == 0) ? 0 :
                (numBatchTargets[index].getAndSet(0)/nBatches);

            batchesPerSource[index].set(nBatches);
            avgBatchPerSource[index].set(avg);
        }

        return copy;
    }

    /**
     * Do some eviction before proceeding on with another operation.
     *
     * Note that this method is intentionally not synchronized in order to
     * minimize overhead when checking for critical eviction.  This method is
     * called from application threads for every cursor operation, and by many
     * daemon threads.
     */
    public void doCriticalEviction(boolean backgroundIO) {

        if (arbiter.isOverBudget()) {

            /*
             * Any time there's excessive cache usage, let the thread pool know
             * there's work to do.
             */
            alert();

            /*
             * If this is an application thread, only do eviction if the
             * memory budget overage fulfills the critical eviction
             * requirements. We want to avoid having application thread do
             * eviction.
             */
            if (arbiter.needCriticalEviction()) {
                doEvict(EvictionSource.CRITICAL, backgroundIO);
            }
        }
    }

    /**
     * Do a check on whether synchronous eviction is needed.
     *
     * Note that this method is intentionally not synchronized in order to
     * minimize overhead when checking for critical eviction.  This method is
     * called from application threads for every cursor operation.
     */
    public void doDaemonEviction(boolean backgroundIO) {

        if (arbiter.isOverBudget()) {

            /*
             * Any time there's excessive cache usage, let the thread pool know
             * there's work to do.
             */
            alert();

            /*
             * JE daemon threads should do synchronous eviction any time
             * the memory budget is over.
             */
            doEvict(EvictionSource.DAEMON, backgroundIO);
        }
    }

    /*
     * Eviction invoked by the API
     */
    public void doManualEvict()
        throws DatabaseException {

        doEvict(EvictionSource.MANUAL, true); // backgroundIO
    }

    /**
     * Evict a specific IN, used by cache modes.
     */
    public void doEvictOneIN(IN target, EvictionSource source) {
        if (!reentrancyGuard.enter()) {
            return;
        }

        try {
            evictIN(target, false /* backgroundIO */, source);
        } finally {
            reentrancyGuard.leave();
        }
    }

    /**
     * Let the eviction pool know there's work to do.
     */
    public void alert() {
        if (!runEvictor) {
            return;
        }

        evictionPool.execute
            (new BackgroundEvictTask(this, true /* backgroundIO */));
    }

    /**
     * @hidden
     * Return the ThreadPool, used by unit testing only.
     */
    public ThreadPoolExecutor getThreadPool() {
        return evictionPool;
    }
   
    /**
     * Can execute concurrently, called by app threads or by background evictor
     */
    void doEvict(EvictionSource source,  boolean backgroundIO)
        throws DatabaseException {

        if (!reentrancyGuard.enter()) {
            return;
        }

        try {

            /*
             * Repeat as necessary to keep up with allocations.  Stop if no
             * progress is made, to prevent an infinite loop.
             */
            boolean progress = true;
            int nBatches = 0;
            long bytesEvicted = 0;
            numBatches[source.ordinal()].incrementAndGet();
            while (progress && (nBatches < MAX_BATCHES_PER_RUN) &&
                   !shutdownRequested.get()) {

                /* Get some work from the arbiter. */
                long maxEvictBytes = arbiter.getEvictionPledge();

                /* Nothing to do. */
                if (maxEvictBytes == 0) {
                    break;
                }

                bytesEvicted = evictBatch(source, backgroundIO, maxEvictBytes);
                if (bytesEvicted == 0) {
                    progress = false;
                }

                nBatches += 1;
            }

            /* Really for debugging. */
            if (source == EvictionSource.EVICTORTHREAD) {
                if (logger.isLoggable(Level.FINEST)) {
                    LoggerUtils.finest(logger, envImpl,
                                       "Thread evicted " + bytesEvicted +
                                       " bytes in " + nBatches + " batches");
                }
            }
        } finally  {
            reentrancyGuard.leave();
        }
    }

    /**
     * Each iteration will attempt to evict maxEvictBytes, but will give up
     * after a complete pass over the INList, or if there is nothing more to
     * evict, due to actions by concurrently executing threads. This method is
     * thread safe and may be called concurrently.
     *
     * @return the number of bytes evicted, or zero if no progress was made.
     * Note that if the INList is completely empty, it's still possible to
     * return a non-zero number of bytes due to special eviction of items such
     * as utilization info, even though no IN eviction happened.
     */
    long evictBatch(Evictor.EvictionSource source,
                    boolean backgroundIO,
                    long maxEvictBytes)
        throws DatabaseException {

        int numNodesScannedThisBatch = 0;
        nEvictPasses.increment();

        assert TestHookExecute.doHookSetupIfSet(evictProfile);

        /*
         * Perform class-specific per-batch processing, in advance of getting a
         * batch. This is done under the TargetSelector mutex. TODO:
         * special eviction is done serially. We may want to absolve
         * application threads of that responsibility, to avoid blocking, and
         * only have evictor threads do special eviction.
         */
        SetupInfo setupInfo = selector.startBatch(true /* doSpecialEviction */);
        long evictBytes = setupInfo.specialEvictionBytes;
        int maxINsPerBatch = setupInfo.maxINsPerBatch;
        if (maxINsPerBatch == 0) {
            return evictBytes; // The INList(s) are empty.
        }

        try {

            /*
             * Keep evicting until we've freed enough memory or we've visited
             * the maximum number of nodes allowed. Each iteration of the while
             * loop is called an eviction batch.
             *
             * In order to prevent endless evicting, limit this run to one pass
             * over the IN list(s).
             */
            while ((evictBytes < maxEvictBytes) &&
                   (numNodesScannedThisBatch <= maxINsPerBatch) &&
                   arbiter.stillNeedsEviction()) {
               
                ScanInfo scanInfo = selector.selectIN(maxINsPerBatch);
                IN target = scanInfo.target;
                numNodesScannedThisBatch += scanInfo.numNodesScanned;

                if (target == null) {
                    break;
                }

                numBatchTargets[source.ordinal()].incrementAndGet();

                assert TestHookExecute.doHookIfSet(evictProfile, target);

                /*
                 * Check to make sure the DB was not deleted after selecting
                 * it, and prevent the DB from being deleted while we're
                 * working with it.
                 */
                DatabaseImpl targetDb = target.getDatabase();
                DbTree dbTree = targetDb.getDbEnvironment().getDbTree();
                DatabaseImpl refreshedDb = null;
                try {
                    refreshedDb = dbTree.getDb(targetDb.getId());
                    if (refreshedDb != null && !refreshedDb.isDeleted()) {
                        if (target.isDbRoot()) {
                            evictBytes += evictRoot(target, backgroundIO);
                        } else {
                            evictBytes +=
                                evictIN(target, backgroundIO, source);
                        }
                    } else {

                        /*
                         * We don't expect to see an IN that is resident on
                         * the INList with a database that has finished
                         * delete processing, because it should have been
                         * removed from the INList during post-delete
                         * cleanup.  It may have been returned by the
                         * INList iterator after being removed from the
                         * INList (because we're using ConcurrentHashMap),
                         * but then IN.getInListResident should return false.
                         */
                        if (targetDb.isDeleteFinished() &&
                            target.getInListResident()) {
                            String inInfo =
                                " IN type=" + target.getLogType() + " id=" +
                                target.getNodeId() + " not expected on INList";
                            String errMsg = (refreshedDb == null) ?
                                inInfo :
                                ("Database " + refreshedDb.getDebugName() +
                                 " id=" + refreshedDb.getId() + " rootLsn=" +
                                 DbLsn.getNoFormatString
                                 (refreshedDb.getTree().getRootLsn()) +
                                  ' ' + inInfo);
                            throw EnvironmentFailureException.
                                unexpectedState(errMsg);
                        }
                    }
                } finally {
                    dbTree.releaseDb(refreshedDb);
                }
            }
        } finally {
            nNodesScanned.add(numNodesScannedThisBatch);
        }

        return evictBytes;
    }

    /**
     * Evict this DB root node.  [#13415] Must be thread safe, executes
     * concurrently.
     *
     * @return number of bytes evicted.
     */
    private long evictRoot(final IN target,
                           final boolean backgroundIO)
        throws DatabaseException {

        final DatabaseImpl db = target.getDatabase();
        /* SharedEvictor uses multiple envs, do not use superclass envImpl. */
        final EnvironmentImpl useEnvImpl = db.getDbEnvironment();
        final INList inList = useEnvImpl.getInMemoryINs();

        class RootEvictor implements WithRootLatched {

            boolean flushed = false;
            long evictBytes = 0;

            public IN doWork(ChildReference root)
                throws DatabaseException {

                IN rootIN = (IN) root.fetchTarget(db, null);
                rootIN.latch(CacheMode.UNCHANGED);
                try {
                    /* Re-check that all conditions still hold. */
                    boolean isDirty = rootIN.getDirty();
                    if (rootIN == target &&
                        rootIN.isDbRoot() &&
                        rootIN.isEvictable() &&
                        !(useEnvImpl.isReadOnly() && isDirty)) {
                        boolean logProvisional =
                            coordinateWithCheckpoint(rootIN, null /*parent*/);

                        /* Flush if dirty. */
                        if (isDirty) {
                            long newLsn = rootIN.log
                                (useEnvImpl.getLogManager(),
                                 false, // allowDeltas
                                 false, // allowCompress
                                 logProvisional,
                                 backgroundIO,
                                 null); // parent
                            root.setLsn(newLsn);
                            flushed = true;
                        }

                        /* Take off the INList and adjust memory budget. */
                        inList.remove(rootIN);
                        evictBytes = rootIN.getBudgetedMemorySize();

                        /* Evict IN. */
                        root.clearTarget();

                        /* Stats */
                        nRootNodesEvicted.increment();
                    }
                } finally {
                    rootIN.releaseLatch();
                }

                return null;
            }
        }

        /* Attempt to evict the DB root IN. */
        RootEvictor evictor = new RootEvictor();
        db.getTree().withRootLatchedExclusive(evictor);

        /* If the root IN was flushed, write the dirtied MapLN. */
        if (evictor.flushed) {
            useEnvImpl.getDbTree().modifyDbRoot(db);
        }

        return evictor.evictBytes;
    }

    /**
     * Strip or evict this node. Must be thread safe, executes concurrently.
     *
     * @param source is EvictSource.CRITICAL or EVICTORTHREAD when this
     * operation is invoked by the evictor (either critical eviction or the
     * evictor background thread), and is EvictSource.CACHEMODE if invoked by a
     * user operation using CacheMode.EVICT_BIN.  If CACHEMODE, we will perform
     * the eviction regardless of whether:
     *  1) we have to wait for a latch, or
     *  2) the IN generation changes, or
     *  3) we are able to strip LNs.
     *
     * If not CACHEMODE, any of the above conditions will prevent eviction.
     *
     * @return number of bytes evicted.
     */
    private long evictIN(IN target, boolean backgroundIO, EvictionSource source)
        throws DatabaseException {

        DatabaseImpl db = target.getDatabase();
        /* SharedEvictor uses multiple envs, do not use superclass envImpl. */
        EnvironmentImpl useEnvImpl = db.getDbEnvironment();
        long evictedBytes = 0;

        /*
         * Non-BIN INs are evicted by detaching them from their parent.  For
         * BINS, the first step is to remove deleted entries by compressing
         * the BIN. The evictor indicates that we shouldn't fault in
         * non-resident children during compression. After compression,
         * LN logging and LN stripping may be performed.
         *
         * If LN stripping is used, first we strip the BIN by logging any dirty
         * LN children and detaching all its resident LN targets.  If we make
         * progress doing that, we stop and will not evict the BIN itself until
         * possibly later.  If it has no resident LNs then we evict the BIN
         * itself using the "regular" detach-from-parent routine.
         *
         * If the cleaner is doing clustering, we don't do BIN stripping if we
         * can write out the BIN.  Specifically LN stripping is not performed
         * if the BIN is dirty AND the BIN is evictable AND cleaner
         * clustering is enabled.  In this case the BIN is going to be written
         * out soon, and with clustering we want to be sure to write out the
         * LNs with the BIN; therefore we don't do stripping.
         */

        /*
         * Use latchNoWait because if it's latched we don't want the cleaner
         * to hold up eviction while it migrates an entire BIN.  Latched INs
         * have a high generation value, so not evicting makes sense.  Pass
         * false because we don't want to change the generation during the
         * eviction process.
         */
        boolean inline = (source == EvictionSource.CACHEMODE);
        if (inline) {
            target.latch(CacheMode.UNCHANGED);
        } else {
            if (!target.latchNoWait(CacheMode.UNCHANGED)) {
                return evictedBytes;
            }
        }
        boolean targetIsLatched = true;
        boolean success = false;
        try {
            if (target instanceof BIN) {

                /*
                 * Strip any resident LN targets right now. This may dirty
                 * the BIN if dirty LNs were written out. Note that
                 * migrated BIN entries cannot be stripped.
                 */
                evictedBytes = ((BIN) target).evictLNs();
                if (evictedBytes > 0) {
                    nBINsStripped.increment();
                }
            }

            /*
             * If we were able to free any memory by LN stripping above,
             * then we postpone eviction of the BIN until a later pass.
             * The presence of migrated entries would have inhibited LN
             * stripping. In that case, the BIN can still be evicted,
             * but the marked entries will have to be migrated. That would
             * happen when the target is logged in evictIN.
             */
            if (!inline && evictedBytes != 0) {
                success = true;
                return evictedBytes;
            }
            if (!target.isEvictable()) {
                success = true;
                return evictedBytes;
            }
            /* Regular eviction. */
            Tree tree = db.getTree();

            /*
             * Unit testing.  The target is latched and we are about to release
             * that latch and search for the parent.  Make sure that other
             * operations, such as dirtying an LN in the target BIN, can occur
             * safely in this window.  [#18227]
             */
            assert TestHookExecute.doHookIfSet(preEvictINHook);

            /* getParentINForChildIN unlatches target. */
            targetIsLatched = false;
            SearchResult result = tree.getParentINForChildIN
                (target, true /*requireExactMatch*/, CacheMode.UNCHANGED);

            if (result.exactParentFound) {
                evictedBytes = evictIN(target, result.parent,
                                       result.index, backgroundIO, source);
            }
            success = true;
            return evictedBytes;
        } finally {
            if (targetIsLatched) {
                target.releaseLatch();
            }
        }
    }

    /**
     * Evict an IN. Dirty nodes are logged before they're evicted.
     */
    private long evictIN(IN child,
                         IN parent,
                         int index,
                         boolean backgroundIO,
                         EvictionSource source)
        throws DatabaseException {

        long evictBytes = 0;
        try {
            assert parent.isLatchOwnerForWrite();

            long oldGenerationCount = child.getGeneration();

            /*
             * Get a new reference to the child, in case the reference
             * saved in the selection list became out of date because of
             * changes to that parent.
             */
            IN renewedChild = (IN) parent.getTarget(index);

            if (renewedChild == null) {
                return evictBytes;
            }
           
            boolean inline = (source == EvictionSource.CACHEMODE);
            if (!inline && renewedChild.getGeneration() > oldGenerationCount) {
                return evictBytes;
            }

            /*
             * See the evictIN() method in this class for an explanation for
             * calling latchNoWait().
             */
            if (inline) {
                renewedChild.latch(CacheMode.UNCHANGED);
            } else {
                if (!renewedChild.latchNoWait(CacheMode.UNCHANGED)) {
                    return evictBytes;
                }
            }
            try {
                if (!renewedChild.isEvictable()) {
                    return evictBytes;
                }

                DatabaseImpl db = renewedChild.getDatabase();
                /* Do not use superclass envImpl. */
                EnvironmentImpl useEnvImpl = db.getDbEnvironment();

                /*
                 * Log the child if dirty and env is not r/o. Remove
                 * from IN list.
                 */
                long renewedChildLsn = DbLsn.NULL_LSN;
                boolean newChildLsn = false;
                if (renewedChild.getDirty()) {
                    if (!useEnvImpl.isReadOnly()) {
                        boolean logProvisional =
                            coordinateWithCheckpoint(renewedChild, parent);

                        /*
                         * Log a full version (no deltas) and with cleaner
                         * migration allowed.  Allow compression of deleted
                         * slots in full version BINs.
                         */
                        renewedChildLsn = renewedChild.log
                            (useEnvImpl.getLogManager(),
                             allowBinDeltas,
                             true /*allowCompress*/,
                             logProvisional,
                             backgroundIO,
                             parent);
                        newChildLsn = true;
                    }
                } else {
                    renewedChildLsn = parent.getLsn(index);
                }

                if (renewedChildLsn != DbLsn.NULL_LSN) {
                    /* Take this off the inlist. */
                    useEnvImpl.getInMemoryINs().remove(renewedChild);

                    evictBytes = renewedChild.getBudgetedMemorySize();
                    if (newChildLsn) {

                        /*
                         * Update the parent so its reference is
                         * null and it has the proper LSN.
                         */
                        parent.updateNode
                            (index, null /*node*/, renewedChildLsn,
                             null /*lnSlotKey*/);
                    } else {

                        /*
                         * Null out the reference, but don't dirty
                         * the node since only the reference
                         * changed.
                         */
                        parent.updateNode
                            (index, (Node) null /*node*/,
                             null /*lnSlotKey*/);
                    }

                    /* Stats */
                    nNodesEvicted.increment();
                    renewedChild.incEvictStats(source);
                }
            } finally {
                renewedChild.releaseLatch();
            }
        } finally {
            parent.releaseLatch();
        }

        return evictBytes;
    }

    public void incBINEvictStats(EvictionSource source) {
        binEvictSources[source.ordinal()].increment();
    }

    public void incINEvictStats(EvictionSource source) {
        inEvictSources[source.ordinal()].increment();
    }

    /**
     * Update the appropriate fetch stat, based on node type.
     */
    public void incLNFetchStats(boolean isMiss) {
        nLNFetch.increment();
        if (isMiss) {
            nLNFetchMiss.increment();
        }
    }

    public void incBINFetchStats(boolean isMiss) {
        nBINFetch.increment();
        if (isMiss) {
            nBINFetchMiss.increment();
        }
    }

    public void incINFetchStats(boolean isMiss) {
        nUpperINFetch.increment();
        if (isMiss) {
            nUpperINFetchMiss.increment();
        }
    }

    public AtomicLong getNINSparseTarget() {
        return nINSparseTarget;
    }

    public AtomicLong getNINNoTarget() {
        return nINNoTarget;
    }

    public AtomicLong getNINCompactKey() {
        return nINCompactKey;
    }

    /**
     * Coordinates an eviction with an in-progress checkpoint and returns
     * whether provisional logging is needed.
     *
     * @return true if the target must be logged provisionally.
     */
    private boolean coordinateWithCheckpoint(IN target, IN parent) {

        /* SharedEvictor uses multiple envs, do not use superclass envImpl. */
        EnvironmentImpl useEnvImpl = target.getDatabase().getDbEnvironment();

        /*
         * The checkpointer could be null if it was shutdown or never
         * started.
         */
        Checkpointer ckpter = useEnvImpl.getCheckpointer();
        if (ckpter == null) {
            return false;
        }
        return ckpter.coordinateEvictionWithCheckpoint(target, parent);
    }

    public void addEnvironment(EnvironmentImpl additionalEnvImpl) {
        selector.addEnvironment(additionalEnvImpl);
    }

    public void removeEnvironment(EnvironmentImpl targetEnvImpl) {
        selector.removeEnvironment(targetEnvImpl);
    }

    /* For unit testing only. */
    public void setPreEvictINHook(TestHook<Object> hook) {
        preEvictINHook = hook;
    }

    /* For unit testing only. */
    public void setEvictProfileHook(TestHook<IN> hook) {
        evictProfile = hook;
    }

    /**
     * Called whenever INs are added to, or removed from, the INList.
     */
    public void noteINListChange(int nINs) {
        selector.noteINListChange(nINs);
    }

    /**
     * Only supported by SharedEvictor.
     */
    public boolean checkEnv(EnvironmentImpl env) {
        return selector.checkEnv(env);
    }

    public StatGroup getStatsGroup() {
        return stats;
    }

    /* For unit testing only. */
    public void setRunnableHook(TestHook<Boolean> hook) {
        arbiter.setRunnableHook(hook);
    }

    public boolean isCacheFull() {
        return arbiter.isCacheFull();
    }

    public boolean wasCacheEverFull() {
        return arbiter.wasCacheEverFull();
    }

    /* For unit test only */
    TargetSelector getSelector() {
       return selector;
    }

    /**
     * Request and wait for a shutdown of all running eviction tasks.
     */
    public void shutdown() {
        /*
         * Set the shutdown flag so that outstanding eviction tasks end
         * early. The call to evictionPool.shutdown is a ThreadPoolExecutor
         * call, and is an orderly shutdown that waits for and in flight tasks
         * to end.
         */
        shutdownRequested.set(true);
        evictionPool.shutdown();

        /*
         * AwaitTermination will wait for the timeout period, or will be
         * interrupted, but we don't really care which it is. The evictor
         * shouldn't be interrupted, but if it is, something urgent is
         * happening.
         */
        boolean shutdownFinished = false;
        try {
            shutdownFinished =
                evictionPool.awaitTermination(terminateMillis,
                                              TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            /* We've been interrupted, just give up and end. */
        } finally {
            if (!shutdownFinished) {
                evictionPool.shutdownNow();
            }
        }
    }

    public void requestShutdownPool() {
        shutdownRequested.set(true);
        evictionPool.shutdown();
    }

    private static class ReentrancyGuard {
        private final ConcurrentHashMap<Thread, Thread> activeThreads;
        private final EnvironmentImpl envImpl;
        private final Logger logger;

        ReentrancyGuard(EnvironmentImpl envImpl, Logger logger) {
            this.envImpl = envImpl;
            this.logger = logger;
            activeThreads = new ConcurrentHashMap<Thread, Thread>();
        }

        boolean enter() {
            Thread thisThread = Thread.currentThread();
            if (activeThreads.containsKey(thisThread)) {
                /* We don't really expect a reentrant call. */
                LoggerUtils.severe(logger, envImpl,
                                   "reentrant call to eviction from " +
                                   LoggerUtils.getStackTrace());

                /* If running w/assertions, in testing mode, assert here. */
                assert false: "reentrant call to eviction from " +
                    LoggerUtils.getStackTrace();
                return false;
            }

            activeThreads.put(thisThread, thisThread);
            return true;
        }

        void leave() {
            assert activeThreads.contains(Thread.currentThread());
            activeThreads.remove(Thread.currentThread());
        }
    }

    static class BackgroundEvictTask implements Runnable {

        private final Evictor evictor;
        private final boolean backgroundIO;

        BackgroundEvictTask(Evictor evictor,
                            boolean backgroundIO) {
            this.evictor = evictor;
            this.backgroundIO = backgroundIO;
        }
       
        public void run() {
            evictor.doEvict(EvictionSource.EVICTORTHREAD, backgroundIO);
        }
    }

    static class RejectEvictHandler implements RejectedExecutionHandler {
        private final AtomicLongStat threadUnavailableStat;

        RejectEvictHandler(AtomicLongStat threadUnavailableStat) {
            this.threadUnavailableStat = threadUnavailableStat;
        }
       
        public void rejectedExecution(Runnable r,
                                      ThreadPoolExecutor executor) {
            threadUnavailableStat.increment();
        }
    }
}
TOP

Related Classes of com.sleepycat.je.evictor.RootEvictor

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.