| /*- |
| * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved. |
| * |
| * This file was distributed by Oracle as part of a version of Oracle Berkeley |
| * DB Java Edition made available at: |
| * |
| * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html |
| * |
| * Please see the LICENSE file included in the top-level directory of the |
| * appropriate version of Oracle Berkeley DB Java Edition for a copy of the |
| * license and additional information. |
| */ |
| package com.sleepycat.je.cleaner; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import com.sleepycat.je.config.EnvironmentParams; |
| import com.sleepycat.je.dbi.DatabaseId; |
| import com.sleepycat.je.dbi.DatabaseImpl; |
| import com.sleepycat.je.dbi.EnvironmentImpl; |
| |
| import org.checkerframework.checker.nullness.qual.NonNull; |
| |
| class DbCache implements Iterable<Map.Entry<DatabaseId, DbCache.DbInfo>> { |
| |
| private final EnvironmentImpl envImpl; |
| private final Cleaner cleaner; |
| private final long timoutMs; |
| private long lastClearTime; |
| private int lookups; |
| private final Map<DatabaseId, DbCache.DbInfo> cache = new HashMap<>(); |
| |
| DbCache(final EnvironmentImpl envImpl, final Cleaner cleaner) { |
| this.envImpl = envImpl; |
| this.cleaner = cleaner; |
| |
| timoutMs = envImpl.getConfigManager().getDuration( |
| EnvironmentParams.ENV_DB_CACHE_TIMEOUT); |
| |
| lastClearTime = System.currentTimeMillis(); |
| } |
| |
| /** |
| * Used to cache DatabaseImpl and some relatively static info about it. |
| * The static info can be used for determining obsolescence without doing |
| * an expensive DbTree lookup.This reduces the negative performance impact |
| * of periodically clearing the cache via {@link #releaseDbImpls}. The |
| * static info can be obtained with a single DbTree lookup per DB for each |
| * file cleaned. |
| */ |
| static class DbInfo { |
| |
| /** |
| * The dbImpl field is non-null if it is cached, but set to null |
| * periodically by calling {@link #releaseDbImpls}. |
| */ |
| DatabaseImpl dbImpl; |
| |
| /* |
| * The following fields are available even when dbImpl is null. When |
| * the cache is cleared, dbImpl is set to null but these fields are |
| * left in place. For the most part they can be used for determining |
| * obsolescence even when they are stale, with some caveats below. |
| */ |
| |
| /** |
| * The following fields are truly immutable. |
| */ |
| boolean dups; |
| boolean internal; |
| boolean isLNImmediatelyObsolete; |
| |
| /** |
| * The name field can change, but in all cases the old name can be |
| * used, either as debug output or to pass to the extinction filter. |
| */ |
| String name; |
| |
| /** |
| * If true, the deleted field is immutable and dbImpl will be null. |
| * If false, the deleting field can be used to determine whether a |
| * deletion is in progress. If both fields are false, the current |
| * dbImpl must be consulted to determine whether the DB is deleted. |
| * @see #getDbImpl |
| */ |
| boolean deleted; |
| |
| /** |
| * If true, the DB will definitely be deleted, so the only possible |
| * state transition is from [deleting=true,deleted=false] to |
| * [deleting=false,deleted=true]. |
| * @see #getDbImpl |
| */ |
| boolean deleting; |
| } |
| |
| /** |
| * Returns the possibly-stale info for a given DB ID. Does not call |
| * DbTree.getDb if the DB ID has been previously cached. |
| * |
| * DbInfo.dbImpl is not guaranteed to be non-null even if DbInfo.deleted |
| * is false. If DbInfo.dbImpl is null, the DB has been released and may be |
| * deleted by another thread. |
| */ |
| DbInfo getDbInfo(final DatabaseId dbId) { |
| final DbInfo info = cache.get(dbId); |
| if (info != null) { |
| return info; |
| } |
| return getDbImpl(dbId); |
| } |
| |
| /** |
| * Returns the up-to-date info for a given DB ID, returning a non-null |
| * DbInfo.dbImpl when the DB is not deleted or deleting. |
| * |
| * If the DB was previously found to be deleted or deleting, this method |
| * does not call DbTree.getDb again (this case is optimized). |
| */ |
| DbInfo getDbImpl(final DatabaseId id) { |
| DbInfo info = cache.get(id); |
| if (info == null) { |
| info = new DbInfo(); |
| cache.put(id, info); |
| } |
| if (info.dbImpl == null && !info.deleted && !info.deleting) { |
| final DatabaseImpl db = |
| envImpl.getDbTree().getDb(id, cleaner.lockTimeout); |
| if (db == null) { |
| info.deleted = true; |
| } else { |
| info.dbImpl = db; |
| info.dups = db.getSortedDuplicates(); |
| info.internal = db.getDbType().isInternal(); |
| info.isLNImmediatelyObsolete = db.isLNImmediatelyObsolete(); |
| info.name = db.getName(); |
| info.deleting = db.isDeleting(); |
| } |
| lookups++; |
| } |
| return info; |
| } |
| |
| /** |
| * Returns an iterator over the cached entries. |
| */ |
| @Override |
| @NonNull |
| public Iterator<Map.Entry<DatabaseId, DbInfo>> iterator() { |
| return cache.entrySet().iterator(); |
| } |
| |
| /** |
| * Returns the number of calls to DbTree.getDb during the cleaner run. |
| */ |
| int getLookups() { |
| return lookups; |
| } |
| |
| /** |
| * Releases all non-null DbInfo.dbImpl objects in the cache and sets this |
| * field to null. |
| */ |
| void releaseDbImpls() { |
| for (final DbInfo info : cache.values()) { |
| releaseDbImpl(info); |
| } |
| } |
| |
| /** |
| * Releases DbInfo.dbImpl if non-null and sets this field to null. |
| */ |
| void releaseDbImpl(final DbInfo info) { |
| if (info.dbImpl != null) { |
| envImpl.getDbTree().releaseDb(info.dbImpl); |
| info.dbImpl = null; |
| } |
| } |
| |
| /** |
| * Release cached dbImpls periodically to prevent starving other threads |
| * that need exclusive access to the MapLN (for example, |
| * DbTree.deleteMapLN). [#21015] |
| */ |
| void clearCachePeriodically() { |
| |
| final long now = System.currentTimeMillis(); |
| |
| if (now >= lastClearTime + timoutMs) { |
| releaseDbImpls(); |
| lastClearTime = now; |
| } |
| } |
| } |