Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*-
    * See the file LICENSE for redistribution information.
    *
    * Copyright (c) 2002, 2013 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;
  
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(),
                                       );
         }
 
         public StatDefinition getUpperINStatDef() {
             return new StatDefinition("nUpperINsEvicted" + toString(),
                                       );
         }
 
         public StatDefinition getNumBatchesStatDef() {
             return new StatDefinition("nBatches" + toString(),
                                       );
         }
 
         public StatDefinition getAvgBatchStatDef() {
             return new StatDefinition("avgBatch" + toString(),
                                       );
         }
     }
 
     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;
 
     /* Access count after which we clear the DatabaseImpl cache. */
     int dbCacheClearCount;
 
     /* 
      * 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<ObjectpreEvictINHook;
     private TestHook<INevictProfile;
 
     /* 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;
 
         /* Do the stats definitions. */
          = new StatGroup();
          = new LongStat();
          = new LongStat();
          = new LongStat();
          = new LongStat();
 
          = new AtomicLongStat();
          = new AtomicLongStat();
          = new AtomicLongStat();
          = new AtomicLongStat();
          = new AtomicLongStat();
 
          =  new AtomicLong(0);
          = new AtomicLong(0);
          = new AtomicLong(0);
 
         EnumSet<EvictionSourceallSources = 
             EnumSet.allOf(EvictionSource.class);
         int numSources = allSources.size();
             
          = new AtomicLongStat[numSources];
          = new AtomicLongStat[numSources];
          = new AtomicLong[numSources];    
          = new AtomicLong[numSources];
          = new LongStat[numSources];
          = new LongStat[numSources];
             
         for (EvictionSource source : allSources) {
             int index = source.ordinal();
             [index] = 
                 new AtomicLongStat(source.getBINStatDef());
             [index] = 
                 new AtomicLongStat(source.getUpperINStatDef());
             [index] = new AtomicLong();
             [index] = new AtomicLong();
             [index] = 
                 new LongStat(source.getNumBatchesStatDef());
             [index] = 
                 new LongStat(source.getAvgBatchStatDef());
         }
 
          = makeSelector();
          = new Arbiter(envImpl);
 
          = LoggerUtils.getLogger(getClass());
          = new ReentrancyGuard(envImpl);
          = new AtomicBoolean(false);
 
         DbConfigManager configManager = envImpl.getConfigManager();
 
         int corePoolSize = 
             configManager.getInt(.);
         int maxPoolSize = 
             configManager.getInt(.);
         long keepAliveTime =
             configManager.getDuration(.);
          = configManager.getDuration
             (.);
          = 
             configManager.getInt(.);
 
         RejectedExecutionHandler rejectHandler = 
             new RejectEvictHandler();
         
          = 
             new ThreadPoolExecutor(corePoolSize,
                                    maxPoolSize,
                                    keepAliveTime,
                                    .,
                                    new ArrayBlockingQueue<Runnable>(1),
                                    new StoppableThreadFactory(envImpl,
                                                               "JEEvictor",
                                                               ),
                                    rejectHandler);
 
          = 
             configManager.getBoolean(.);
 
          = configManager.getBoolean
             (.);
 
         /* 
          * 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(.);
         int maxPoolSize = 
             configManager.getInt(.);
         long keepAliveTime =
             configManager.getDuration(.);
          = configManager.getDuration
             (.);
          = 
             configManager.getInt(.);
 
         .setCorePoolSize(corePoolSize);
         .setMaximumPoolSize(maxPoolSize);
         .setKeepAliveTime(keepAliveTime.);
          = 
             configManager.getBoolean(.);
     }
 
     /* 
      * 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 = .cloneGroup(config.getClear());
 
         /*
          * These stats are not cleared. They represent the current state of
          * the cache.
          */
         new LongStat(copy.get());
         new LongStat(copy.get());
         new LongStat(copy.get());
         copy.addAll(.loadStats(config));
         copy.addAll(.loadStats(config));
 
         /* 
          * The number and average size of batches, by type of caller, is
          * calculated each time we collect stats.
          */
         EnumSet<EvictionSourceallSources = 
             EnumSet.allOf(EvictionSource.class);
         for (EvictionSource source : allSources) {
             int index = source.ordinal();
 
             long nBatches = [index].getAndSet(0);
 
             /* Guard against dividing by 0 */
             long avg = (nBatches == 0) ? 0 :
                 ([index].getAndSet(0)/nBatches);
 
             [index].set(nBatches);
             [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 (.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 (.needCriticalEviction()) {
                 doEvict(.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 (.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(.backgroundIO);
         }
     }
 
     /*
      * Eviction invoked by the API
      */
     public void doManualEvict() 
         throws DatabaseException {
 
         doEvict(.true); // backgroundIO
     }

    
Evict a specific IN, used by cache modes.
 
     public void doEvictOneIN(IN targetEvictionSource source) {
         if (!.enter()) {
             return;
         }
 
         try {
             evictIN(targetfalse /* backgroundIO */source);
         } finally {
             .leave();
         }
     }

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

    

Hidden:
Return the ThreadPool, used by unit testing only.
 
     public ThreadPoolExecutor getThreadPool() {
         return ;
     }
    
    
Can execute concurrently, called by app threads or by background evictor
 
     void doEvict(EvictionSource source,  boolean backgroundIO)
         throws DatabaseException {
 
         if (!.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;
             [source.ordinal()].incrementAndGet();
             while (progress && (nBatches < ) &&
                    !.get()) {
 
                 /* Get some work from the arbiter. */
                 long maxEvictBytes = .getEvictionPledge();
 
                 /* Nothing to do. */
                 if (maxEvictBytes == 0) {
                     break;
                 }
 
                 bytesEvicted = evictBatch(sourcebackgroundIOmaxEvictBytes);
                 if (bytesEvicted == 0) {
                     progress = false;
                 }
 
                 nBatches += 1;
             }
 
             /* Really for debugging. */
             if (source == .) {
                 if (.isLoggable(.)) {
                     LoggerUtils.finest(
                                        "Thread evicted " + bytesEvicted +
                                        " bytes in " + nBatches + " batches");
                 }
             }
         } finally  {
             .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.

Returns:
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;
         .increment();
 
         assert TestHookExecute.doHookSetupIfSet();
 
         /* 
          * 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.
          */
         final SetupInfo setupInfo =
             .startBatch(true /*doSpecialEviction*/);
         long evictBytes = setupInfo.specialEvictionBytes;
         final int maxINsPerBatch = setupInfo.maxINsPerBatch;
         if (maxINsPerBatch == 0) {
             return evictBytes// The INList(s) are empty.
         }
 
         /* Use local caching to reduce DbTree.getDb overhead. [#21330] */
         final DbCache dbCache = createDbCache();
 
         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) &&
                    .stillNeedsEviction()) {
                 
                 final ScanInfo scanInfo = .selectIN(maxINsPerBatch);
                 final IN target = scanInfo.target;
                 numNodesScannedThisBatch += scanInfo.numNodesScanned;
 
                 if (target == null) {
                     break;
                 } 
 
                 [source.ordinal()].incrementAndGet();
 
                 assert TestHookExecute.doHookIfSet(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.
                  *
                  * Also check that the refreshedDb is the same instance as the
                  * targetDb.  If not, then targetDb was recently evicted; it
                  * and its IN are orphaned and cannot be processed.  [#21686] 
                  */
                 final DatabaseImpl targetDb = target.getDatabase();
                 final DatabaseImpl refreshedDb = dbCache.getDb
                     (targetDb.getDbEnvironment(), targetDb.getId());
 
                 if (refreshedDb != null &&
                     refreshedDb == targetDb &&
                     !refreshedDb.isDeleted()) {
                     if (target.isDbRoot()) {
                         evictBytes += evictRoot(targetbackgroundIO);
                     } else {
                         evictBytes += 
                             evictIN(targetbackgroundIOsource);
                     }
                 } 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()) {
                         final String inInfo =
                             " IN type=" + target.getLogType() + " id=" +
                             target.getNodeId() + " not expected on INList";
                         final String errMsg = (refreshedDb == null) ?
                             inInfo :
                             ("Database " + refreshedDb.getDebugName() +
                              " id=" + refreshedDb.getId() + " rootLsn=" +
                              DbLsn.getNoFormatString
                              (refreshedDb.getTree().getRootLsn()) +
                               ' ' + inInfo);
                         throw EnvironmentFailureException.
                             unexpectedState(errMsg);
                     }
                 }
             }
         } finally {
             .add(numNodesScannedThisBatch);
             dbCache.releaseDbs();
         }
 
         return evictBytes;
     }

    
Evict this DB root node. [#13415] Must be thread safe, executes concurrently.

Returns:
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 {
 
                 /*
                  * Do not call fetchTarget since this root or DB should be
                  * resident already if it is to be the target of eviction. If
                  * it is not present, it has been evicted by another thread and
                  * should not be fetched for two reasons: 1) this would be
                  * counterproductive, 2) to guard against bringing in a root
                  * for an evicted DB.  The check for getInListResident below
                  * also guards against this later possibility.  [#21686]
                  */
                 IN rootIN = (INroot.getTarget();
                 if (rootIN == null) {
                     return null;
                 }
                 rootIN.latch(.);
                 try {
                     /* Re-check that all conditions still hold. */
                     boolean isDirty = rootIN.getDirty();
                     if (rootIN == target &&
                         rootIN.getInListResident() &&
                         rootIN.isDbRoot() &&
                         rootIN.isEvictable() &&
                         !(useEnvImpl.isReadOnly() && isDirty)) {
                         boolean logProvisional =
                             coordinateWithCheckpoint(rootINnull /*parent*/);
 
                         /* Flush if dirty. */
                         if (isDirty) {
                             long newLsn = rootIN.log
                                 (useEnvImpl.getLogManager(),
                                  false// allowDeltas
                                  false// allowCompress
                                  logProvisional,
                                  backgroundIO,
                                  null); // parent
                             root.setLsn(newLsn);
                              = true;
                         }
 
                         /* Take off the INList and adjust memory budget. */
                         inList.remove(rootIN);
                          = rootIN.getBudgetedMemorySize();
 
                         /* Evict IN. */
                         root.clearTarget();
 
                         /* Stats */
                         .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.

Parameters:
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.
Returns:
number of bytes evicted.
 
     private long evictIN(IN targetboolean backgroundIOEvictionSource 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 == .);
         if (inline) {
             target.latch(.);
         } else {
             if (!target.latchNoWait(.)) {
                 return evictedBytes;
             }
         }
         boolean targetIsLatched = true;
         boolean success = false;
         try {
 
             /*
              * After latching it, ensure that this node was not evicted by
              * another thread.  Do this now, before the Btree lookup, since we
              * should not compress or evict LNs for an orphaned IN. [#21686]
              */
             if (!target.getInListResident()) {
                 return evictedBytes;
             }
 
             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 = ((BINtarget).evictLNs();
                 if (evictedBytes > 0) {
                     .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();
 
             /* getParentINForChildIN unlatches target. */
             targetIsLatched = false;
             SearchResult result = tree.getParentINForChildIN
                 (targettrue /*requireExactMatch*/.);
 
             if (result.exactParentFound) {
                 evictedBytes = evictIN(targetresult.parent,
                                        result.indexbackgroundIOsource);
             }
             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 = (INparent.getTarget(index);
 
             if (renewedChild == null) {
                 return evictBytes;
             }
             
             boolean inline = (source == .);
             if (!inline && renewedChild.getGeneration() > oldGenerationCount) {
                 return evictBytes;
             }
 
             /*
              * See the evictIN() method in this class for an explanation for
              * calling latchNoWait().
              */
             if (inline) {
                 renewedChild.latch(.);
             } else {
                 if (!renewedChild.latchNoWait(.)) {
                     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 = .;
                 boolean newChildLsn = false;
                 if (renewedChild.getDirty()) {
                     if (!useEnvImpl.isReadOnly()) {
                         boolean logProvisional =
                             coordinateWithCheckpoint(renewedChildparent);
 
                         /*
                          * 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(),
                              ,
                              true /*allowCompress*/,
                              logProvisional,
                              backgroundIO,
                              parent);
                         newChildLsn = true;
                     }
                 } else {
                     renewedChildLsn = parent.getLsn(index);
                 }
 
                 if (renewedChildLsn != .) {
                     /* 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
                            (indexnull /*node*/renewedChildLsn,
                             null /*lnSlotKey*/);
                    } else {
                        /*
                         * Null out the reference, but don't dirty
                         * the node since only the reference
                         * changed.
                         */
                        parent.updateNode
                            (index, (Nodenull /*node*/,
                             null /*lnSlotKey*/);
                    }
                    /* Stats */
                    .increment();
                    renewedChild.incEvictStats(source);
                }
            } finally {
                renewedChild.releaseLatch();
            }
        } finally {
            parent.releaseLatch();
        }
        return evictBytes;
    }
    public void incBINEvictStats(EvictionSource source) {
        [source.ordinal()].increment();
    }
    public void incINEvictStats(EvictionSource source) {
        [source.ordinal()].increment();
    }

    
Update the appropriate fetch stat, based on node type.
    public void incLNFetchStats(boolean isMiss) {
        .increment();
        if (isMiss) {
            .increment();
        }
    }
    public void incBINFetchStats(boolean isMiss) {
        .increment();
        if (isMiss) {
            .increment();
        }
    }
    public void incINFetchStats(boolean isMiss) {
        .increment();
        if (isMiss) {
            .increment();
        }
    }
    public AtomicLong getNINSparseTarget() {
        return ;
    }
    public AtomicLong getNINNoTarget() {
        return ;
    }
    public AtomicLong getNINCompactKey() {
        return ;
    }

    
Coordinates an eviction with an in-progress checkpoint and returns whether provisional logging is needed.

Returns:
true if the target must be logged provisionally.
    private boolean coordinateWithCheckpoint(IN targetIN 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(targetparent);
    }
    public void addEnvironment(EnvironmentImpl additionalEnvImpl) {
        .addEnvironment(additionalEnvImpl);
    }
    public void removeEnvironment(EnvironmentImpl targetEnvImpl) {
        .removeEnvironment(targetEnvImpl);
    }
    /* For unit testing only. */
    public void setPreEvictINHook(TestHook<Objecthook) {
         = hook;
    }
    /* For unit testing only. */
    public void setEvictProfileHook(TestHook<INhook) {
         = hook;
    }

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

    
Only supported by SharedEvictor.
    public boolean checkEnv(EnvironmentImpl env) {
        return .checkEnv(env);
    }
    public StatGroup getStatsGroup() {
        return ;
    }
    /* For unit testing only. */
    public void setRunnableHook(TestHook<Booleanhook) {
        .setRunnableHook(hook);
    }
    public boolean isCacheFull() {
        return .isCacheFull();
    }
    public boolean wasCacheEverFull() {
        return .wasCacheEverFull();
    }
    /* For unit test only */
       return ;
    }

    
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.
         */
        .set(true);
        .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 = 
                .awaitTermination(
                                              .);
        } catch (InterruptedException e) {
            /* We've been interrupted, just give up and end. */
        } finally {
            if (!shutdownFinished) {
                .shutdownNow();
            }
        }
    }
    public void requestShutdownPool() {
        .set(true);
        .shutdown();
    }
    private static class ReentrancyGuard {
        private final ConcurrentHashMap<ThreadThreadactiveThreads;
        private final EnvironmentImpl envImpl;
        private final Logger logger;
        ReentrancyGuard(EnvironmentImpl envImplLogger logger) {
            this. = envImpl;
            this. = logger;
             = new ConcurrentHashMap<ThreadThread>();
        }
        boolean enter() {
            Thread thisThread = Thread.currentThread();
            if (.containsKey(thisThread)) {
                /* We don't really expect a reentrant call. */
                LoggerUtils.severe(
                                   "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;
            }
            .put(thisThreadthisThread);
            return true;
        }
        void leave() {
            assert .contains(Thread.currentThread());
            .remove(Thread.currentThread());
        }
    }
    static class BackgroundEvictTask implements Runnable {
        private final Evictor evictor;
        private final boolean backgroundIO;