com.sleepycat.je.dbi.EnvironmentImpl Maven / Gradle / Ivy
Show all versions of je Show documentation
/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2010 Oracle. All rights reserved.
*
* $Id: EnvironmentImpl.java,v 1.390 2010/01/04 15:50:40 cwl Exp $
*/
package com.sleepycat.je.dbi;
import static com.sleepycat.je.dbi.DbiStatDefinition.ENV_GROUP_NAME;
import static com.sleepycat.je.dbi.DbiStatDefinition.ENV_GROUP_DESC;
import static com.sleepycat.je.dbi.DbiStatDefinition.ENVIMPL_RELATCHES_REQUIRED;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.MemoryHandler;
import com.sleepycat.je.CheckpointConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.EnvironmentLockedException;
import com.sleepycat.je.EnvironmentMutableConfig;
import com.sleepycat.je.EnvironmentNotFoundException;
import com.sleepycat.je.EnvironmentStats;
import com.sleepycat.je.ExceptionListener;
import com.sleepycat.je.LockStats;
import com.sleepycat.je.LogScanConfig;
import com.sleepycat.je.LogScanner;
import com.sleepycat.je.OperationFailureException;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.je.TransactionStats;
import com.sleepycat.je.VerifyConfig;
import com.sleepycat.je.TransactionStats.Active;
import com.sleepycat.je.cleaner.Cleaner;
import com.sleepycat.je.cleaner.LocalUtilizationTracker;
import com.sleepycat.je.cleaner.UtilizationProfile;
import com.sleepycat.je.cleaner.UtilizationTracker;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.evictor.Evictor;
import com.sleepycat.je.evictor.PrivateEvictor;
import com.sleepycat.je.evictor.SharedEvictor;
import com.sleepycat.je.incomp.INCompressor;
import com.sleepycat.je.latch.Latch;
import com.sleepycat.je.latch.LatchSupport;
import com.sleepycat.je.latch.SharedLatch;
import com.sleepycat.je.log.FileManager;
import com.sleepycat.je.log.LNFileReader;
import com.sleepycat.je.log.LatchedLogManager;
import com.sleepycat.je.log.LogEntryHeader;
import com.sleepycat.je.log.LogEntryType;
import com.sleepycat.je.log.LogItem;
import com.sleepycat.je.log.LogManager;
import com.sleepycat.je.log.ReplicationContext;
import com.sleepycat.je.log.SyncedLogManager;
import com.sleepycat.je.log.Trace;
import com.sleepycat.je.log.entry.LogEntry;
import com.sleepycat.je.log.entry.SingleItemEntry;
import com.sleepycat.je.recovery.Checkpointer;
import com.sleepycat.je.recovery.RecoveryInfo;
import com.sleepycat.je.recovery.RecoveryManager;
import com.sleepycat.je.recovery.VLSNRecoveryProxy;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.BINReference;
import com.sleepycat.je.tree.IN;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.txn.LockType;
import com.sleepycat.je.txn.LockUpgrade;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.ThreadLocker;
import com.sleepycat.je.txn.Txn;
import com.sleepycat.je.txn.TxnManager;
import com.sleepycat.je.util.DbBackup;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.ExceptionListenerUser;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.LongStat;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.TestHook;
import com.sleepycat.je.utilint.TestHookExecute;
import com.sleepycat.je.utilint.TracerFormatter;
import com.sleepycat.je.utilint.VLSN;
/**
* Underlying Environment implementation. There is a single instance for any
* database environment opened by the application.
*/
public class EnvironmentImpl implements EnvConfigObserver {
/*
* Set true and run unit tests for NO_LOCKING_MODE test.
* EnvironmentConfigTest.testInconsistentParams will fail. [#13788]
*/
private static final boolean TEST_NO_LOCKING_MODE = false;
/* Attributes of the entire environment */
private volatile DbEnvState envState;
private volatile boolean closing; // true if close has begun
private File envHome;
private int referenceCount; // count of open environment handles
private boolean isTransactional; // true if env opened with DB_INIT_TRANS
private boolean isNoLocking; // true if env has no locking
private boolean isReadOnly; // true if env opened with the read only flag.
private boolean isMemOnly; // true if je.log.memOnly=true
private boolean sharedCache; // true if je.sharedCache=true
private static boolean fairLatches;// true if user wants fair latches
private static boolean useSharedLatchesForINs;
/* true if offset tracking should be used for deferred write dbs. */
private boolean dbEviction;
/* Whether or not initialization succeeded. */
private boolean initializedSuccessfully = false;
/*
* Represents whether this environment needs to be converted from
* standalone to replicated.
*/
protected boolean needConvert = false;
private MemoryBudget memoryBudget;
private static int adler32ChunkSize;
/* Save so we don't have to look it up in the config manager frequently. */
private long lockTimeout;
private long txnTimeout;
/* Directory of databases */
protected DbTree dbMapTree;
private long mapTreeRootLsn = DbLsn.NULL_LSN;
private Latch mapTreeRootLatch;
private INList inMemoryINs;
/* Services */
protected DbConfigManager configManager;
private List configObservers;
protected Logger envLogger;
private LogManager logManager;
private FileManager fileManager;
private TxnManager txnManager;
/* Daemons */
private Evictor evictor;
private INCompressor inCompressor;
private Checkpointer checkpointer;
private Cleaner cleaner;
/* Stats, debug information */
private RecoveryInfo lastRecoveryInfo;
private EnvironmentFailureException savedInvalidatingException;
private TestHook cleanerBarrierHoook;
/* If true, call Thread.yield() at strategic points (stress test aid) */
private static boolean forcedYield = false;
/*
* Used by Database to protect access to the trigger list. A single latch
* for all databases is used to prevent deadlocks.
*/
private SharedLatch triggerLatch;
/**
* The exception listener for this envimpl, if any has been specified.
*/
private ExceptionListener exceptionListener = null;
/*
* ExceptionListenerUsers are threads that will send their exceptions
* to the listener. The set lets us notify the threads if the application
* has reconfigured the exception listener.
*/
private final Set exceptionListenerUsers;
/*
* Configuration and tracking of background IO limits. Managed by the
* updateBackgroundReads, updateBackgroundWrites and sleepAfterBackgroundIO
* methods. The limits and the backlog are volatile because we check them
* outside the synchronized block. Other fields are updated and checked
* while synchronized on the tracking mutex object. The sleep mutex is
* used to block multiple background threads while sleeping.
*/
private volatile int backgroundSleepBacklog;
private volatile int backgroundReadLimit;
private volatile int backgroundWriteLimit;
private long backgroundSleepInterval;
private int backgroundReadCount;
private long backgroundWriteBytes;
private TestHook> backgroundSleepHook;
private final Object backgroundTrackingMutex = new Object();
private final Object backgroundSleepMutex = new Object();
/*
* ThreadLocal.get() is not cheap so we want to minimize calls to it. We
* only use ThreadLocals for the TreeStatsAccumulator which are only called
* in limited circumstances. Use this reference count to indicate that a
* thread has set a TreeStatsAccumulator. When it's done, it decrements
* the counter. It's static so that we don't have to pass around the
* EnvironmentImpl.
*/
private static int threadLocalReferenceCount = 0;
/**
* DbPrintLog doesn't need btree and dup comparators to function properly
* don't require any instantiations. This flag, if true, indicates that
* we've been called from DbPrintLog.
*/
private static boolean noComparators = false;
/*
* A preallocated EnvironmentFailureException that is used in OOME and
* other java.lang.Error situations so that allocation does not need to be
* done in the OOME context.
*/
public final EnvironmentFailureException SAVED_EFE =
EnvironmentFailureException.makeJavaErrorWrapper();
public static final boolean USE_JAVA5_ADLER32;
private static final String DISABLE_JAVA_ADLER32_NAME =
"je.disable.java.adler32";
static {
USE_JAVA5_ADLER32 =
System.getProperty(DISABLE_JAVA_ADLER32_NAME) == null;
}
/*
* JE MBeans.
*
* Note that MBeans are loaded dynamically in order to support the
* Android platform, which does not include javax.management.
*/
/* The property name of setting these two MBeans. */
private static final String REGISTER_MONITOR = "JEMonitor";
/* The two MBeans registered or not. */
private volatile boolean isMBeanRegistered = false;
/*
* Log handlers used in java.util.logging. Handlers are per-environment,
* and must not be static, because the output is tagged with an identifier
* that associates the information with that environment. Handlers should
* be closed to release resources when the environment is closed.
*
* Note that handlers are not statically attached to loggers. See
* LoggerUtils.java for information on how redirect loggers are used.
*/
private static final String INFO_FILES = "je.info";
private static final int FILEHANDLER_LIMIT = 10000000;
private static final int FILEHANDLER_COUNT = 10;
private final ConsoleHandler consoleHandler;
/* The target handler for MemoryHandler. */
private ConsoleHandler memoryTarget;
private final MemoryHandler memoryHandler;
private final FileHandler fileHandler;
/* cache this value as a performance optimization. */
private final boolean dbLoggingDisabled;
/* Formatter for java.util.logging. */
protected final Formatter formatter;
/**
* Because the Android platform does not have any javax.management classes,
* we load JEMonitor dynamically to ensure that there are no explicit
* references to com.sleepycat.je.jmx.*.
*/
public static interface MBeanRegistrar {
public void doRegister(Environment env)
throws Exception;
public void doUnregister()
throws Exception;
}
private final ArrayList mBeanRegList =
new ArrayList();
public static final boolean IS_DALVIK;
static {
IS_DALVIK = "Dalvik".equals(System.getProperty("java.vm.name"));
}
/* NodeId sequence counters */
private final NodeSequence nodeSequence;
/* Stats */
private StatGroup stats;
/* Number of relatches required in this environment. */
private LongStat relatchesRequired;
/* Refer to comment near declaration of these static LockUpgrades. */
static {
LockUpgrade.ILLEGAL.setUpgrade(null);
LockUpgrade.EXISTING.setUpgrade(null);
LockUpgrade.WRITE_PROMOTE.setUpgrade(LockType.WRITE);
LockUpgrade.RANGE_READ_IMMED.setUpgrade(LockType.RANGE_READ);
LockUpgrade.RANGE_WRITE_IMMED.setUpgrade(LockType.RANGE_WRITE);
LockUpgrade.RANGE_WRITE_PROMOTE.setUpgrade(LockType.RANGE_WRITE);
}
private final String nodeName;
public EnvironmentImpl(File envHome,
EnvironmentConfig envConfig,
EnvironmentImpl sharedCacheEnv)
throws EnvironmentNotFoundException, EnvironmentLockedException {
this(envHome, envConfig, sharedCacheEnv, null);
}
/**
* Create a database environment to represent the data in envHome.
* dbHome. Properties from the je.properties file in that directory are
* used to initialize the system wide property bag. Properties passed to
* this method are used to influence the open itself.
*
* @param envHome absolute path of the database environment home directory
* @param envConfig is the configuration to be used. It's already had
* the je.properties file applied, and has been validated.
* @param sharedCacheEnv if non-null, is another environment that is
* sharing the cache with this environment; if null, this environment is
* not sharing the cache or is the first environment to share the cache.
*
* @throws DatabaseException on all other failures
*
* @throws IllegalArgumentException via Environment ctor.
*/
protected EnvironmentImpl(File envHome,
EnvironmentConfig envConfig,
EnvironmentImpl sharedCacheEnv,
RepConfigProxy repConfigProxy)
throws EnvironmentNotFoundException, EnvironmentLockedException {
boolean success = false;
try {
this.envHome = envHome;
envState = DbEnvState.INIT;
mapTreeRootLatch = new Latch("MapTreeRoot");
exceptionListenerUsers = new HashSet();
/* Do the stats definition. */
stats = new StatGroup(ENV_GROUP_NAME, ENV_GROUP_DESC);
relatchesRequired =
new LongStat(stats, ENVIMPL_RELATCHES_REQUIRED);
/* Set up configuration parameters */
configManager = initConfigManager(envConfig, repConfigProxy);
configObservers = new ArrayList();
addConfigObserver(this);
/*
* Essential services. These must exist before recovery.
*/
forcedYield =
configManager.getBoolean(EnvironmentParams.ENV_FORCED_YIELD);
isTransactional =
configManager.getBoolean(EnvironmentParams.ENV_INIT_TXN);
isNoLocking = !(configManager.getBoolean
(EnvironmentParams.ENV_INIT_LOCKING));
if (isTransactional && isNoLocking) {
if (TEST_NO_LOCKING_MODE) {
isNoLocking = !isTransactional;
} else {
throw new IllegalArgumentException
("Can't set 'je.env.isNoLocking' and " +
"'je.env.isTransactional';");
}
}
fairLatches =
configManager.getBoolean(EnvironmentParams.ENV_FAIR_LATCHES);
isReadOnly =
configManager.getBoolean(EnvironmentParams.ENV_RDONLY);
isMemOnly =
configManager.getBoolean(EnvironmentParams.LOG_MEMORY_ONLY);
useSharedLatchesForINs =
configManager.getBoolean(EnvironmentParams.ENV_SHARED_LATCHES);
dbEviction =
configManager.getBoolean(EnvironmentParams.ENV_DB_EVICTION);
adler32ChunkSize =
configManager.getInt(EnvironmentParams.ADLER32_CHUNK_SIZE);
sharedCache =
configManager.getBoolean(EnvironmentParams.ENV_SHARED_CACHE);
/*
* Set up java.util.logging handlers and their environment specific
* formatters. These are used by the redirect handlers, rather
* than specific loggers.
*/
dbLoggingDisabled =
!configManager.getBoolean(EnvironmentParams.JE_LOGGING_DBLOG);
formatter = initFormatter();
consoleHandler =
new com.sleepycat.je.util.ConsoleHandler(formatter, this);
memoryHandler = initMemoryHandler();
fileHandler = initFileHandler();
envLogger = LoggerUtils.getLogger(getClass());
/*
* Decide on memory budgets based on environment config params and
* memory available to this process.
*/
memoryBudget =
new MemoryBudget(this, sharedCacheEnv, configManager);
fileManager = new FileManager(this, envHome, isReadOnly);
if (!envConfig.getAllowCreate() && !fileManager.filesExist()) {
throw new EnvironmentNotFoundException
(this, "Home directory: " + envHome);
}
nodeName = envConfig.getNodeName();
if (fairLatches) {
logManager = new LatchedLogManager(this, isReadOnly);
} else {
logManager = new SyncedLogManager(this, isReadOnly);
}
inMemoryINs = new INList(this);
txnManager = new TxnManager(this);
/*
* Daemons are always made here, but only started after recovery.
* We want them to exist so we can call them programatically even
* if the daemon thread is not started.
*/
createDaemons(sharedCacheEnv);
/*
* The node sequence transient ids, but not the node ids, must be
* created before the DbTree is created because the transient node
* id sequence is used during the creation of the dbtree.
*/
nodeSequence = new NodeSequence(this);
nodeSequence.initTransientNodeId();
/*
* Instantiate a new, blank dbtree. If the environment already
* exists, recovery will recreate the dbMapTree from the log and
* overwrite this instance.
*/
dbMapTree = new DbTree(this, isReplicated());
referenceCount = 0;
triggerLatch = new SharedLatch("TriggerLatch");
/*
* Allocate node sequences before recovery. We expressly wait to
* allocate it after the DbTree is created, because these sequences
* should not be used by the DbTree before recovery has
* run. Waiting until now to allocate them will make errors more
* evident, since there will be a NullPointerException.
*/
nodeSequence.initRealNodeId();
success = true;
} finally {
if (!success) {
/* Release any environment locks if there was a problem. */
clearFileManager();
closeHandlers();
}
}
}
/**
* Create a config manager that holds the configuration properties that
* have been passed in. These properties are already validated, and have
* had the proper order of precedence applied; that is, the je.properties
* file has been applied. The configuration properties need to be available
* before the rest of environment creation proceeds.
*
* This method is overridden by replication environments.
*
* @param envConfig is the environment configuration to use
* @param replicationParams are the replication configurations to use. In
* this case, the Properties bag has been extracted from the configuration
* instance, to avoid crossing the compilation firewall.
*/
protected DbConfigManager initConfigManager(EnvironmentConfig envConfig,
RepConfigProxy unused) {
return new DbConfigManager(envConfig);
}
public synchronized void finishInit(EnvironmentConfig envConfig)
throws DatabaseException {
try {
if (!initializedSuccessfully) {
/* Do not do recovery if this environment is for a utility. */
if (configManager.getBoolean(EnvironmentParams.ENV_RECOVERY)) {
/*
* Run recovery. Note that debug logging to the database
* log is disabled until recovery is finished.
*/
try {
RecoveryManager recoveryManager =
new RecoveryManager(this);
lastRecoveryInfo = recoveryManager.recover(isReadOnly);
postRecoveryConversion();
} finally {
try {
/*
* Flush to get all exception tracing out to the
* log.
*/
logManager.flush();
fileManager.clear();
} catch (IOException e) {
throw new EnvironmentFailureException
(this, EnvironmentFailureReason.LOG_INTEGRITY,
e);
}
}
} else {
isReadOnly = true;
noComparators = true;
}
/*
* Cache a few critical values. We keep our timeout in millis
* because Object.wait takes millis.
*/
lockTimeout =
configManager.getDuration(EnvironmentParams.LOCK_TIMEOUT);
txnTimeout =
configManager.getDuration(EnvironmentParams.TXN_TIMEOUT);
/*
* Initialize the environment memory usage number. Must be
* called after recovery, because recovery determines the
* starting size of the in-memory tree.
*/
memoryBudget.initCacheMemoryUsage
(dbMapTree.getTreeAdminMemory());
/* Mark as open before starting daemons. */
open();
/*
* Call config observer and start daemons last after everything
* else is initialized. Note that all config parameters, both
* mutable and non-mutable, needed by the memoryBudget have
* already been initialized when the configManager was
* instantiated.
*/
envConfigUpdate(configManager, envConfig);
initializedSuccessfully = true;
}
} catch (RuntimeException e) {
/* Release any environment locks if there was a problem. */
clearFileManager();
throw e;
} finally {
/*
* DbEnvPool.addEnvironment is called by
* RecoveryManager.buildTree during recovery above, to enable
* eviction during recovery. If we fail to create the
* environment, we must remove it.
*/
if (!initializedSuccessfully && sharedCache && evictor != null) {
evictor.removeEnvironment(this);
}
}
}
/*
* JE MBean registration is performed during Environment creation so that
* the MBean has access to the Environment API which is not available from
* EnvironmentImpl. This precludes registering MBeans in
* EnvironmentImpl.finishInit.
*/
public synchronized void registerMBean(Environment env)
throws DatabaseException {
if (!isMBeanRegistered) {
if (System.getProperty(REGISTER_MONITOR) != null) {
doRegisterMBean(getMonitorClassName(), env);
doRegisterMBean(getDiagnosticsClassName(), env);
}
isMBeanRegistered = true;
}
}
protected String getMonitorClassName() {
return "com.sleepycat.je.jmx.JEMonitor";
}
protected String getDiagnosticsClassName() {
return "com.sleepycat.je.jmx.JEDiagnostics";
}
private void doRegisterMBean(String className, Environment env)
throws DatabaseException {
try {
Class> newClass = Class.forName(className);
MBeanRegistrar mBeanReg = (MBeanRegistrar) newClass.newInstance();
mBeanReg.doRegister(env);
mBeanRegList.add(mBeanReg);
} catch (Exception e) {
throw new EnvironmentFailureException
(DbInternal.getEnvironmentImpl(env),
EnvironmentFailureReason.MONITOR_REGISTRATION, e);
}
}
private synchronized void unregisterMBean()
throws Exception {
for (MBeanRegistrar mBeanReg : mBeanRegList) {
mBeanReg.doUnregister();
}
}
/*
* Release and close the FileManager when there are problems during the
* initialization of this EnvironmentImpl. An exception is already in
* flight when this method is called.
*/
private void clearFileManager()
throws DatabaseException {
if (fileManager != null) {
try {
/*
* Clear again, in case an exception in logManager.flush()
* caused us to skip the earlier call to clear().
*/
fileManager.clear();
} catch (IOException IOE) {
/*
* Klockwork - ok
* Eat it, we want to throw the original exception.
*/
}
try {
fileManager.close();
} catch (IOException IOE) {
/*
* Klockwork - ok
* Eat it, we want to throw the original exception.
*/
}
}
}
/**
* Respond to config updates.
*/
public void envConfigUpdate(DbConfigManager mgr,
EnvironmentMutableConfig newConfig) {
backgroundReadLimit = mgr.getInt
(EnvironmentParams.ENV_BACKGROUND_READ_LIMIT);
backgroundWriteLimit = mgr.getInt
(EnvironmentParams.ENV_BACKGROUND_WRITE_LIMIT);
backgroundSleepInterval = mgr.getDuration
(EnvironmentParams.ENV_BACKGROUND_SLEEP_INTERVAL);
/* Reset logging levels if they're set in EnvironmentMutableConfig. */
if (newConfig.isConfigParamSet(EnvironmentConfig.CONSOLE_LOGGING_LEVEL)) {
Level newConsoleHandlerLevel =
Level.parse(mgr.get(EnvironmentParams.JE_CONSOLE_LEVEL));
consoleHandler.setLevel(newConsoleHandlerLevel);
}
if (newConfig.isConfigParamSet
(EnvironmentConfig.FILE_LOGGING_LEVEL)) {
Level newFileHandlerLevel =
Level.parse(mgr.get(EnvironmentParams.JE_FILE_LEVEL));
fileHandler.setLevel(newFileHandlerLevel);
}
exceptionListener = newConfig.getExceptionListener();
for (ExceptionListenerUser u : exceptionListenerUsers) {
u.setExceptionListener(exceptionListener);
}
/* Start daemons last, after all other parameters are set. */
runOrPauseDaemons(mgr);
}
public void registerExceptionListenerUser(ExceptionListenerUser u) {
exceptionListenerUsers.add(u);
}
public boolean unregisterExceptionListenerUser(ExceptionListenerUser u) {
return exceptionListenerUsers.remove(u);
}
/**
* Read configurations for daemons, instantiate.
*/
private void createDaemons(EnvironmentImpl sharedCacheEnv)
throws DatabaseException {
/* Evictor */
if (sharedCacheEnv != null) {
assert sharedCache;
evictor = sharedCacheEnv.evictor;
} else if (sharedCache) {
evictor = new SharedEvictor(this, "SharedEvictor");
} else {
evictor = new PrivateEvictor(this, "Evictor");
}
/* Checkpointer */
/*
* Make sure that either log-size-based or time-based checkpointing
* is enabled.
*/
long checkpointerWakeupTime =
Checkpointer.getWakeupPeriod(configManager);
checkpointer = new Checkpointer(this,
checkpointerWakeupTime,
Environment.CHECKPOINTER_NAME);
/* INCompressor */
long compressorWakeupInterval = configManager.getDuration
(EnvironmentParams.COMPRESSOR_WAKEUP_INTERVAL);
inCompressor = new INCompressor(this, compressorWakeupInterval,
Environment.INCOMP_NAME);
/* The cleaner is not time-based so no wakeup interval is used. */
cleaner = new Cleaner(this, Environment.CLEANER_NAME);
}
/**
* Run or pause daemons, depending on config properties.
*/
private void runOrPauseDaemons(DbConfigManager mgr) {
if (!isReadOnly) {
/* INCompressor */
inCompressor.runOrPause
(mgr.getBoolean(EnvironmentParams.ENV_RUN_INCOMPRESSOR));
/* Cleaner. Do not start it if running in-memory */
cleaner.runOrPause
(mgr.getBoolean(EnvironmentParams.ENV_RUN_CLEANER) &&
!isMemOnly);
/*
* Checkpointer. Run in both transactional and non-transactional
* environments to guarantee recovery time.
*/
checkpointer.runOrPause
(mgr.getBoolean(EnvironmentParams.ENV_RUN_CHECKPOINTER));
}
/* Evictor */
evictor.runOrPause
(mgr.getBoolean(EnvironmentParams.ENV_RUN_EVICTOR));
}
/**
* Return the incompressor. In general, don't use this directly because
* it's easy to forget that the incompressor can be null at times (i.e
* during the shutdown procedure. Instead, wrap the functionality within
* this class, like lazyCompress.
*/
public INCompressor getINCompressor() {
return inCompressor;
}
/**
* Returns the UtilizationTracker.
*/
public UtilizationTracker getUtilizationTracker() {
return cleaner.getUtilizationTracker();
}
/**
* Returns the UtilizationProfile.
*/
public UtilizationProfile getUtilizationProfile() {
return cleaner.getUtilizationProfile();
}
/**
* If a background read limit has been configured and that limit is
* exceeded when the cumulative total is incremented by the given number of
* reads, increment the sleep backlog to cause a sleep to occur. Called by
* background activities such as the cleaner after performing a file read
* operation.
*
* @see #sleepAfterBackgroundIO
*/
public void updateBackgroundReads(int nReads) {
/*
* Make a copy of the volatile limit field since it could change
* between the time we check it and the time we use it below.
*/
int limit = backgroundReadLimit;
if (limit > 0) {
synchronized (backgroundTrackingMutex) {
backgroundReadCount += nReads;
if (backgroundReadCount >= limit) {
backgroundSleepBacklog += 1;
/* Remainder is rolled forward. */
backgroundReadCount -= limit;
assert backgroundReadCount >= 0;
}
}
}
}
/**
* If a background write limit has been configured and that limit is
* exceeded when the given amount written is added to the cumulative total,
* increment the sleep backlog to cause a sleep to occur. Called by
* background activities such as the checkpointer and evictor after
* performing a file write operation.
*
* The number of writes is estimated by dividing the bytes written by
* the log buffer size. Since the log write buffer is shared by all
* writers, this is the best approximation possible.
*
* @see #sleepAfterBackgroundIO
*/
public void updateBackgroundWrites(int writeSize, int logBufferSize) {
/*
* Make a copy of the volatile limit field since it could change
* between the time we check it and the time we use it below.
*/
int limit = backgroundWriteLimit;
if (limit > 0) {
synchronized (backgroundTrackingMutex) {
backgroundWriteBytes += writeSize;
int writeCount = (int) (backgroundWriteBytes / logBufferSize);
if (writeCount >= limit) {
backgroundSleepBacklog += 1;
/* Remainder is rolled forward. */
backgroundWriteBytes -= (limit * logBufferSize);
assert backgroundWriteBytes >= 0;
}
}
}
}
/**
* If the sleep backlog is non-zero (set by updateBackgroundReads or
* updateBackgroundWrites), sleep for the configured interval and decrement
* the backlog.
*
* If two threads call this method and the first call causes a sleep,
* the call by the second thread will block until the first thread's sleep
* interval is over. When the call by the second thread is unblocked, if
* another sleep is needed then the second thread will sleep again. In
* other words, when lots of sleeps are needed, background threads may
* backup. This is intended to give foreground threads a chance to "catch
* up" when background threads are doing a lot of IO.
*/
public void sleepAfterBackgroundIO() {
if (backgroundSleepBacklog > 0) {
synchronized (backgroundSleepMutex) {
/* Sleep. Rethrow interrupts if they occur. */
try {
/* FindBugs: OK that we're sleeping with a mutex held. */
Thread.sleep(backgroundSleepInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
/* Assert has intentional side effect for unit testing. */
assert TestHookExecute.doHookIfSet(backgroundSleepHook);
}
synchronized (backgroundTrackingMutex) {
/* Decrement backlog last to make other threads wait. */
if (backgroundSleepBacklog > 0) {
backgroundSleepBacklog -= 1;
}
}
}
}
/* For unit testing only. */
public void setBackgroundSleepHook(TestHook> hook) {
backgroundSleepHook = hook;
}
/* For unit testing only. */
public void setCleanerBarrierHook(TestHook hook) {
cleanerBarrierHoook = hook;
}
/**
* @throws IllegalArgumentException via Environment.scanLog.
*/
public boolean scanLog(long startPosition,
long endPosition,
LogScanConfig config,
LogScanner scanner)
throws DatabaseException {
DbConfigManager cm = getConfigManager();
int readBufferSize =
cm.getInt(EnvironmentParams.LOG_ITERATOR_READ_SIZE);
long endOfLogLsn = fileManager.getNextLsn();
boolean forwards = config.getForwards();
LNFileReader reader = null;
if (forwards) {
if (endPosition > endOfLogLsn) {
throw new IllegalArgumentException
("endPosition (" + endPosition +
") is past the end of the log on a forewards scan.");
}
reader = new LNFileReader(this,
readBufferSize,
startPosition, /*startLsn*/
true, /*forwards*/
endPosition, /*endOfFileLsn*/
DbLsn.NULL_LSN, /*finishLsn*/
null, /*singleFileNum*/
DbLsn.NULL_LSN);/*ckptEnd*/
} else {
if (startPosition > endOfLogLsn) {
throw new IllegalArgumentException
("startPosition (" + startPosition +
") is past the end of the log on a backwards scan.");
}
reader = new LNFileReader(this,
readBufferSize,
startPosition, /*startLsn*/
false, /*forwards*/
endOfLogLsn, /*endOfFileLsn*/
endPosition, /*finishLsn*/
null, /*singleFileNum*/
DbLsn.NULL_LSN);/*ckptEnd*/
}
reader.addTargetType(LogEntryType.LOG_LN_TRANSACTIONAL);
reader.addTargetType(LogEntryType.LOG_LN);
reader.addTargetType(LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL);
reader.addTargetType(LogEntryType.LOG_DEL_DUPLN);
Map dbNameMap = dbMapTree.getDbNamesAndIds();
while (reader.readNextEntry()) {
if (reader.isLN()) {
LN theLN = reader.getLN();
byte[] theKey = reader.getKey();
DatabaseId dbId = reader.getDatabaseId();
String dbName = dbNameMap.get(dbId);
if (DbTree.isReservedDbName(dbName)) {
continue;
}
boolean continueScanning =
scanner.scanRecord(new DatabaseEntry(theKey),
new DatabaseEntry(theLN.getData()),
theLN.isDeleted(),
dbName);
if (!continueScanning) {
break;
}
}
}
return true;
}
/**
* Logs the map tree root and saves the LSN.
*/
public void logMapTreeRoot()
throws DatabaseException {
logMapTreeRoot(DbLsn.NULL_LSN);
}
/**
* Logs the map tree root, but only if its current LSN is before the
* ifBeforeLsn parameter or ifBeforeLsn is NULL_LSN.
*/
public void logMapTreeRoot(long ifBeforeLsn)
throws DatabaseException {
mapTreeRootLatch.acquire();
try {
if (ifBeforeLsn == DbLsn.NULL_LSN ||
DbLsn.compareTo(mapTreeRootLsn, ifBeforeLsn) < 0) {
mapTreeRootLsn = logManager.log
(new SingleItemEntry(LogEntryType.LOG_ROOT,
dbMapTree),
ReplicationContext.NO_REPLICATE);
}
} finally {
mapTreeRootLatch.release();
}
}
/**
* Force a rewrite of the map tree root if required.
*/
public void rewriteMapTreeRoot(long cleanerTargetLsn)
throws DatabaseException {
mapTreeRootLatch.acquire();
try {
if (DbLsn.compareTo(cleanerTargetLsn, mapTreeRootLsn) == 0) {
/*
* The root entry targetted for cleaning is in use. Write a
* new copy.
*/
mapTreeRootLsn = logManager.log
(new SingleItemEntry(LogEntryType.LOG_ROOT,
dbMapTree),
ReplicationContext.NO_REPLICATE);
}
} finally {
mapTreeRootLatch.release();
}
}
/**
* @return the mapping tree root LSN.
*/
public long getRootLsn() {
return mapTreeRootLsn;
}
/**
* Set the mapping tree from the log. Called during recovery.
*/
public void readMapTreeFromLog(long rootLsn)
throws DatabaseException {
if (dbMapTree != null) {
dbMapTree.close();
}
dbMapTree = (DbTree) logManager.getEntryHandleFileNotFound(rootLsn);
/* Set the dbMapTree to replicated when converted. */
if (!dbMapTree.isReplicated() && getAllowConvert()) {
dbMapTree.setIsReplicated();
dbMapTree.setIsConverted();
needConvert = true;
}
dbMapTree.initExistingEnvironment(this);
/* Set the map tree root */
mapTreeRootLatch.acquire();
try {
mapTreeRootLsn = rootLsn;
} finally {
mapTreeRootLatch.release();
}
}
/**
* Tells the asynchronous IN compressor thread about a BIN with a deleted
* entry.
*/
public void addToCompressorQueue(BIN bin,
Key deletedKey,
boolean doWakeup) {
/*
* May be called by the cleaner on its last cycle, after the compressor
* is shut down.
*/
if (inCompressor != null) {
inCompressor.addBinKeyToQueue(bin, deletedKey, doWakeup);
}
}
/**
* Tells the asynchronous IN compressor thread about a BINReference with a
* deleted entry.
*/
public void addToCompressorQueue(BINReference binRef,
boolean doWakeup) {
/*
* May be called by the cleaner on its last cycle, after the compressor
* is shut down.
*/
if (inCompressor != null) {
inCompressor.addBinRefToQueue(binRef, doWakeup);
}
}
/**
* Tells the asynchronous IN compressor thread about a collections of
* BINReferences with deleted entries.
*/
public void addToCompressorQueue(Collection binRefs,
boolean doWakeup) {
/*
* May be called by the cleaner on its last cycle, after the compressor
* is shut down.
*/
if (inCompressor != null) {
inCompressor.addMultipleBinRefsToQueue(binRefs, doWakeup);
}
}
/**
* Do lazy compression at opportune moments.
*/
public void lazyCompress(IN in, LocalUtilizationTracker localTracker)
throws DatabaseException {
/*
* May be called by the cleaner on its last cycle, after the compressor
* is shut down.
*/
if (inCompressor != null) {
inCompressor.lazyCompress(in, localTracker);
}
}
/**
* Reset the logging level for specified loggers in a JE environment.
*
* @throws IllegalArgumentException via JEDiagnostics.OP_RESET_LOGGING
*/
public void resetLoggingLevel(String changedLoggerName, Level level) {
/*
* Go through the loggers registered in the global log manager, and
* set the new level. If the specified logger name is not valid, throw
* an IllegalArgumentException.
*/
java.util.logging.LogManager loggerManager =
java.util.logging.LogManager.getLogManager();
Enumeration loggers = loggerManager.getLoggerNames();
boolean validName = false;
while (loggers.hasMoreElements()) {
String loggerName = loggers.nextElement();
Logger logger = loggerManager.getLogger(loggerName);
if ("all".equals(changedLoggerName) ||
loggerName.endsWith(changedLoggerName) ||
loggerName.endsWith(changedLoggerName +
LoggerUtils.NO_ENV) ||
loggerName.endsWith(changedLoggerName +
LoggerUtils.FIXED_PREFIX) ||
loggerName.startsWith(changedLoggerName)) {
logger.setLevel(level);
validName = true;
}
}
if (!validName) {
throw new IllegalArgumentException
("The logger name parameter: " + changedLoggerName +
" is invalid!");
}
}
/* Push out the logging information in memoryHandler. */
public void pushMemoryHandler() {
if (memoryHandler.getLevel() != Level.OFF) {
Level level = memoryTarget.getLevel();
memoryTarget.publish(new LogRecord(level,
"***************************************"));
memoryTarget.publish(new LogRecord(level,
"Start pushing out memory handler......."));
memoryHandler.push();
memoryHandler.flush();
memoryTarget.publish(new LogRecord(level,
"Finish pushing out memory handler......"));
memoryTarget.publish(new LogRecord(level,
"***************************************"));
}
}
/* Initialize the handler's formatter. */
protected Formatter initFormatter() {
return new TracerFormatter(getName());
}
/* Initialize an environment specific memoryHandler. */
private MemoryHandler initMemoryHandler() {
/* Get memoryHandler buffer size. */
String memHandlerName =
com.sleepycat.je.util.MemoryHandler.class.getName();
int memoryHandlerSize = 1000;
String size = LoggerUtils.getLoggerProperty(memHandlerName + ".size");
if (size != null) {
memoryHandlerSize = Integer.valueOf(size);
}
/*
* Use a new console handler rather than the environment
* consoleHandler, since we need to print out the logging information
* in memoryHandler even if the environment consoleHandler is turned
* off.
*/
memoryTarget = new ConsoleHandler();
memoryTarget.setLevel(Level.ALL);
memoryTarget.setFormatter(formatter);
/* Get the push level for the MemoryHandler. */
Level pushLevel = LoggerUtils.getPushLevel(memHandlerName);
return new com.sleepycat.je.util.MemoryHandler(memoryTarget,
memoryHandlerSize,
pushLevel,
formatter);
}
private FileHandler initFileHandler()
throws DatabaseException {
if (isReadOnly || isMemOnly) {
return null;
}
String handlerName = com.sleepycat.je.util.FileHandler.class.getName();
String logFilePattern = envHome + "/" + INFO_FILES;
/* Log with a rotating set of files, use append mode. */
int limit = FILEHANDLER_LIMIT;
String logLimit =
LoggerUtils.getLoggerProperty(handlerName + ".limit");
if (logLimit != null) {
limit = Integer.parseInt(logLimit);
}
/* Limit the number of files. */
int count = FILEHANDLER_COUNT;
String logCount =
LoggerUtils.getLoggerProperty(handlerName + ".count");
if (logCount != null) {
count = Integer.parseInt(logCount);
}
try {
return new com.sleepycat.je.util.FileHandler(logFilePattern,
limit,
count,
formatter,
this);
} catch (IOException e) {
throw EnvironmentFailureException.unexpectedException
("Problem creating output files in: " + logFilePattern, e);
}
}
public ConsoleHandler getConsoleHandler() {
return consoleHandler;
}
public MemoryHandler getMemoryHandler() {
return memoryHandler;
}
public FileHandler getFileHandler() {
return fileHandler;
}
private void closeHandlers() {
if (consoleHandler != null) {
consoleHandler.close();
}
if (memoryHandler != null) {
memoryHandler.close();
}
if (fileHandler != null) {
fileHandler.close();
}
}
/**
* Not much to do, mark state.
*/
public void open() {
envState = DbEnvState.OPEN;
}
/**
* Invalidate the environment. Done when a fatal exception
* (EnvironmentFailureException) is thrown.
*/
public void invalidate(EnvironmentFailureException e) {
/*
* Remember the fatal exception so we can redisplay it if the
* environment is called by the application again.
*/
savedInvalidatingException = e;
envState = DbEnvState.INVALID;
requestShutdownDaemons();
}
/**
* Invalidate the environment when a Java Error is thrown.
*/
public void invalidate(Error e) {
if (SAVED_EFE.getCause() == null) {
SAVED_EFE.initCause(e);
invalidate(SAVED_EFE);
}
}
/**
* Predicate used to determine whether the EnvironmentImpl is valid.
*
* @return true if it's valid, false otherwise
*/
public boolean isInvalid() {
return (savedInvalidatingException != null);
}
/**
* @return true if environment is open.
*/
public boolean isValid() {
return (envState == DbEnvState.OPEN);
}
/**
* @return true if environment is still in init
*/
public boolean isInInit() {
return (envState == DbEnvState.INIT);
}
/**
* @return true if close has begun, although the state may still be open.
*/
public boolean isClosing() {
return closing;
}
public boolean isClosed() {
return (envState == DbEnvState.CLOSED);
}
/**
* When a EnvironmentFailureException occurs or the environment is closed,
* further writing can cause log corruption.
*/
public boolean mayNotWrite() {
return (envState == DbEnvState.INVALID) ||
(envState == DbEnvState.CLOSED);
}
public void checkIfInvalid()
throws EnvironmentFailureException {
if (envState == DbEnvState.INVALID) {
/*
* Set a flag in the exception so the exception message will be
* clear that this was an earlier exception.
*/
savedInvalidatingException.setAlreadyThrown(true);
if (savedInvalidatingException == SAVED_EFE) {
savedInvalidatingException.fillInStackTrace();
/* Do not wrap to avoid allocations after an OOME. */
throw savedInvalidatingException;
}
throw savedInvalidatingException.wrapSelf
("Environment must be closed, caused by: " +
savedInvalidatingException);
}
}
public void checkNotClosed()
throws DatabaseException {
if (envState == DbEnvState.CLOSED) {
throw new IllegalStateException
("Attempt to use a Environment that has been closed.");
}
}
/**
* Decrements the reference count and closes the environment when it
* reaches zero. A checkpoint is always performed when closing.
*/
public void close()
throws DatabaseException {
/* Calls doClose while synchronized on DbEnvPool. */
DbEnvPool.getInstance().closeEnvironment
(this, true /* doCheckpoint */, true /* doCheckLeaks */);
}
/**
* Decrements the reference count and closes the environment when it
* reaches zero. A checkpoint when closing is optional.
*/
public void close(boolean doCheckpoint)
throws DatabaseException {
/* Calls doClose while synchronized on DbEnvPool. */
DbEnvPool.getInstance().closeEnvironment
(this, doCheckpoint, true /* doCheckLeaks */);
}
/**
* Used by error handling to forcibly close an environment, and by tests to
* close an environment to simulate a crash. Database handles do not have
* to be closed before calling this method. A checkpoint is not performed.
*/
public void abnormalClose()
throws DatabaseException {
/*
* We are assuming that the environment will be cleared out of the
* environment pool, so it's safe to assert that the reference count is
* zero.
*/
int count = getReferenceCount();
if (count > 1) {
throw EnvironmentFailureException.unexpectedState
(this, "Abnormal close assumes that the reference count on " +
"this handle is 1, not " + count);
}
/* Calls doClose while synchronized on DbEnvPool. */
DbEnvPool.getInstance().closeEnvironment
(this, false /* doCheckpoint */, false /* doCheckLeaks */);
}
/**
* Closes the environment, optionally performing a checkpoint and checking
* for resource leaks. This method must be called while synchronized on
* DbEnvPool.
*
* @throws IllegalStateException if the environment is already closed.
*
* @throws EnvironmentFailureException if leaks or other problems are
* detected while closing.
*/
synchronized void doClose(boolean doCheckpoint, boolean doCheckLeaks) {
StringWriter errorStringWriter = new StringWriter();
PrintWriter errors = new PrintWriter(errorStringWriter);
try {
Trace.traceLazily
(this, "Close of environment " + envHome + " started");
LoggerUtils.fine(envLogger,
this,
"Close of environment " + envHome + " started");
envState.checkState(DbEnvState.VALID_FOR_CLOSE,
DbEnvState.CLOSED);
setupClose(errors);
/*
* Begin shutdown of the deamons before checkpointing. Cleaning
* during the checkpoint is wasted and slows down the checkpoint.
*/
requestShutdownDaemons();
try {
unregisterMBean();
} catch (Exception e) {
errors.append("\nException unregistering MBean: ");
e.printStackTrace(errors);
errors.println();
}
/* Checkpoint to bound recovery time. */
boolean checkpointHappened = false;
if (doCheckpoint &&
!isReadOnly &&
(envState != DbEnvState.INVALID) &&
logManager.getLastLsnAtRecovery() !=
fileManager.getLastUsedLsn()) {
/*
* Force a checkpoint. Don't allow deltas (minimize recovery
* time) because they cause inefficiencies for two reasons: (1)
* recovering BINDeltas causes extra random I/O in order to
* reconstitute BINS, which can greatly increase recovery time,
* and (2) logging deltas during close causes redundant logging
* by the full checkpoint after recovery.
*/
CheckpointConfig ckptConfig = new CheckpointConfig();
ckptConfig.setForce(true);
ckptConfig.setMinimizeRecoveryTime(true);
try {
invokeCheckpoint
(ckptConfig,
false, // flushAll
"close");
} catch (DatabaseException e) {
errors.append("\nException performing checkpoint: ");
e.printStackTrace(errors);
errors.println();
}
checkpointHappened = true;
}
postCheckpointClose(checkpointHappened);
/* Flush log. */
LoggerUtils.fine(envLogger,
this,
"About to shutdown daemons for Env " + envHome);
shutdownDaemons();
try {
logManager.flush();
} catch (Exception e) {
errors.append("\nException flushing log manager: ");
e.printStackTrace(errors);
errors.println();
}
try {
fileManager.clear();
} catch (Exception e) {
errors.append("\nException clearing file manager: ");
e.printStackTrace(errors);
errors.println();
}
try {
fileManager.close();
} catch (Exception e) {
errors.append("\nException closing file manager: ");
e.printStackTrace(errors);
errors.println();
}
/*
* Close the memory budgets on these components before the
* INList is forcibly released and the treeAdmin budget is
* cleared.
*/
dbMapTree.close();
cleaner.close();
inMemoryINs.clear();
closeHandlers();
if (doCheckLeaks &&
(envState != DbEnvState.INVALID)) {
try {
checkLeaks();
} catch (Exception e) {
errors.append("\nException performing validity checks: ");
e.printStackTrace(errors);
errors.println();
}
}
} finally {
envState = DbEnvState.CLOSED;
}
/* Don't whine again if we've already whined. */
if (errorStringWriter.getBuffer().length() > 0 &&
savedInvalidatingException == null) {
throw EnvironmentFailureException.unexpectedState
(errorStringWriter.toString());
}
}
/**
* Release any resources from a subclass that need to be released before
* close is called on regular environment components.
* @throws DatabaseException
*/
protected synchronized void setupClose(PrintWriter errors)
throws DatabaseException {
}
/**
* Release any resources from a subclass that need to be released after
* the closing checkpoint.
* @param checkpointed if true, a checkpoint as issued before the close
* @throws DatabaseException
*/
protected synchronized void postCheckpointClose(boolean checkpointed)
throws DatabaseException {
}
/**
* Convert user defined databases to replicated after doing recovery.
*
* @throws DatabaseException
*/
protected void postRecoveryConversion()
throws DatabaseException {
}
/*
* Clear as many resources as possible, even in the face of an environment
* that has received a fatal error, in order to support reopening the
* environment in the same JVM.
*/
public void closeAfterInvalid()
throws DatabaseException {
/* Calls doCloseAfterInvalid while synchronized on DbEnvPool. */
DbEnvPool.getInstance().closeEnvironmentAfterInvalid(this);
}
/**
* This method must be called while synchronized on DbEnvPool.
*/
public synchronized void doCloseAfterInvalid() {
try {
unregisterMBean();
} catch (Exception e) {
/* Klockwork - ok */
}
shutdownDaemons();
try {
fileManager.clear();
} catch (Exception e) {
/* Klockwork - ok */
}
try {
fileManager.close();
} catch (Exception e) {
/* Klockwork - ok */
}
/*
* Release resources held by handlers, such as memory and file
* descriptors
*/
closeHandlers();
}
synchronized void incReferenceCount() {
referenceCount++;
}
/**
* Returns true if the environment should be closed.
*/
synchronized boolean decReferenceCount() {
return (--referenceCount <= 0);
}
synchronized protected int getReferenceCount() {
return referenceCount;
}
public static int getThreadLocalReferenceCount() {
return threadLocalReferenceCount;
}
static synchronized void incThreadLocalReferenceCount() {
threadLocalReferenceCount++;
}
static synchronized void decThreadLocalReferenceCount() {
threadLocalReferenceCount--;
}
public static boolean getNoComparators() {
return noComparators;
}
/**
* Debugging support. Check for leaked locks and transactions.
*/
private void checkLeaks()
throws DatabaseException {
/* Only enabled if this check leak flag is true. */
if (!configManager.getBoolean(EnvironmentParams.ENV_CHECK_LEAKS)) {
return;
}
boolean clean = true;
StatsConfig statsConfig = new StatsConfig();
/* Fast stats will not return NTotalLocks below. */
statsConfig.setFast(false);
LockStats lockStat = lockStat(statsConfig);
if (lockStat.getNTotalLocks() != 0) {
clean = false;
System.err.println("Problem: " + lockStat.getNTotalLocks() +
" locks left");
txnManager.getLockManager().dump();
}
TransactionStats txnStat = txnStat(statsConfig);
if (txnStat.getNActive() != 0) {
clean = false;
System.err.println("Problem: " + txnStat.getNActive() +
" txns left");
TransactionStats.Active[] active = txnStat.getActiveTxns();
if (active != null) {
for (Active element : active) {
System.err.println(element);
}
}
}
if (LatchSupport.countLatchesHeld() > 0) {
clean = false;
System.err.println("Some latches held at env close.");
LatchSupport.dumpLatchesHeld();
}
long memoryUsage = memoryBudget.getVariableCacheUsage();
if (memoryUsage!= 0) {
clean = false;
System.err.println("Local Cache Usage = " + memoryUsage);
System.err.println(memoryBudget.loadStats());
}
boolean assertionsEnabled = false;
assert assertionsEnabled = true; // Intentional side effect.
if (!clean && assertionsEnabled) {
throw EnvironmentFailureException.unexpectedState
("Lock, transaction, latch or memory " +
"left behind at environment close");
}
}
/**
* Invoke a checkpoint programmatically. Note that only one checkpoint may
* run at a time.
*/
public boolean invokeCheckpoint(CheckpointConfig config,
boolean flushAll,
String invokingSource)
throws DatabaseException {
if (checkpointer != null) {
checkpointer.doCheckpoint(config, flushAll, invokingSource);
return true;
}
return false;
}
/**
* Flip the log to a new file, forcing an fsync. Return the LSN of the
* trace record in the new file.
*/
public long forceLogFileFlip()
throws DatabaseException {
return logManager.logForceFlip
(new SingleItemEntry(LogEntryType.LOG_TRACE,
new Trace("File Flip")));
}
/**
* Invoke a compress programatically. Note that only one compress may run
* at a time.
*/
public boolean invokeCompressor()
throws DatabaseException {
if (inCompressor != null) {
inCompressor.doCompress();
return true;
}
return false;
}
public void invokeEvictor()
throws DatabaseException {
if (evictor != null) {
evictor.doEvict(Evictor.SOURCE_MANUAL);
}
}
/**
* @throws UnsupportedOperationException via Environment.cleanLog.
*/
public int invokeCleaner()
throws DatabaseException {
if (isReadOnly || isMemOnly) {
throw new UnsupportedOperationException
("Log cleaning not allowed in a read-only or memory-only " +
"environment");
}
if (cleaner != null) {
return cleaner.doClean(true, // cleanMultipleFiles
false); // forceCleaning
}
return 0;
}
private void requestShutdownDaemons() {
closing = true;
if (inCompressor != null) {
inCompressor.requestShutdown();
}
/*
* Don't shutdown the shared cache evictor here. It is shutdown when
* the last shared cache environment is removed in DbEnvPool.
*/
if (evictor != null && !sharedCache) {
evictor.requestShutdown();
}
if (checkpointer != null) {
checkpointer.requestShutdown();
}
if (cleaner != null) {
cleaner.requestShutdown();
}
}
/**
* For unit testing -- shuts down daemons completely but leaves environment
* usable since environment references are not nulled out.
*/
public void stopDaemons() {
if (inCompressor != null) {
inCompressor.shutdown();
}
if (evictor != null) {
evictor.shutdown();
}
if (checkpointer != null) {
checkpointer.shutdown();
}
if (cleaner != null) {
cleaner.shutdown();
}
}
/**
* Ask all daemon threads to shut down.
*/
protected void shutdownDaemons() {
shutdownINCompressor();
/*
* Cleaner has to be shutdown before checkpointer because former calls
* the latter.
*/
shutdownCleaner();
shutdownCheckpointer();
/*
* The evictor has to get shutdown last because the other daemons might
* create changes to the memory usage which result in a notify to
* eviction.
*/
shutdownEvictor();
}
void shutdownINCompressor() {
if (inCompressor != null) {
inCompressor.shutdown();
/*
* If daemon thread doesn't shutdown for any reason, at least clear
* the reference to the environment so it can be GC'd.
*/
inCompressor.clearEnv();
inCompressor = null;
}
return;
}
void shutdownEvictor() {
if (evictor != null) {
if (sharedCache) {
/*
* Don't shutdown the SharedEvictor here. It is shutdown when
* the last shared cache environment is removed in DbEnvPool.
* Instead, remove this environment from the SharedEvictor's
* list so we won't try to evict from a closing/closed
* environment. Note that we do this after the final checkpoint
* so that eviction is possible during the checkpoint, and just
* before deconstructing the environment. Leave the evictor
* field intact so DbEnvPool can get it.
*/
evictor.removeEnvironment(this);
} else {
evictor.shutdown();
/*
* If daemon thread doesn't shutdown for any reason, at least
* clear the reference to the environment so it can be GC'd.
*/
evictor.clearEnv();
evictor = null;
}
}
return;
}
void shutdownCheckpointer() {
if (checkpointer != null) {
checkpointer.shutdown();
/*
* If daemon thread doesn't shutdown for any reason, at least clear
* the reference to the environment so it can be GC'd.
*/
checkpointer.clearEnv();
checkpointer = null;
}
return;
}
/**
* public for unit tests.
*/
public void shutdownCleaner() {
if (cleaner != null) {
cleaner.shutdown();
/*
* Don't call clearEnv -- Cleaner.shutdown does this for each
* cleaner thread. Don't set the cleaner field to null because we
* use it to get the utilization profile and tracker.
*/
}
return;
}
public boolean isNoLocking() {
return isNoLocking;
}
public boolean isTransactional() {
return isTransactional;
}
public boolean isReadOnly() {
return isReadOnly;
}
public boolean isMemOnly() {
return isMemOnly;
}
public String getNodeName() {
return nodeName;
}
/*
* FUTURE: change this to be non-static. It's static now just to avoid
* passing down parameters in various places.
*/
public static boolean getFairLatches() {
return fairLatches;
}
public static boolean getSharedLatches() {
return useSharedLatchesForINs;
}
/**
* Returns whether DB/MapLN eviction is enabled.
*/
public boolean getDbEviction() {
return dbEviction;
}
public static int getAdler32ChunkSize() {
return adler32ChunkSize;
}
public boolean getSharedCache() {
return sharedCache;
}
/**
* Transactional services.
*/
public Txn txnBegin(Transaction parent, TransactionConfig txnConfig)
throws DatabaseException {
return txnManager.txnBegin(parent, txnConfig);
}
/* Services. */
public LogManager getLogManager() {
return logManager;
}
public FileManager getFileManager() {
return fileManager;
}
public DbTree getDbTree() {
return dbMapTree;
}
/**
* Returns the config manager for the current base configuration.
*
* The configuration can change, but changes are made by replacing the
* config manager object with a enw one. To use a consistent set of
* properties, call this method once and query the returned manager
* repeatedly for each property, rather than getting the config manager via
* this method for each property individually.
*/
public DbConfigManager getConfigManager() {
return configManager;
}
public NodeSequence getNodeSequence() {
return nodeSequence;
}
/**
* Clones the current configuration.
*/
public EnvironmentConfig cloneConfig() {
return configManager.getEnvironmentConfig().clone();
}
/**
* Clones the current mutable configuration.
*/
public EnvironmentMutableConfig cloneMutableConfig() {
return DbInternal.cloneMutableConfig
(configManager.getEnvironmentConfig());
}
/**
* Throws an exception if an immutable property is changed.
*/
public void checkImmutablePropsForEquality(Properties handleConfigProps)
throws IllegalArgumentException {
DbInternal.checkImmutablePropsForEquality
(configManager.getEnvironmentConfig(), handleConfigProps);
}
/**
* Changes the mutable config properties that are present in the given
* config, and notifies all config observer.
*/
public void setMutableConfig(EnvironmentMutableConfig config)
throws DatabaseException {
/* Calls doSetMutableConfig while synchronized on DbEnvPool. */
DbEnvPool.getInstance().setMutableConfig(this, config);
}
/**
* This method must be called while synchronized on DbEnvPool.
*/
synchronized void doSetMutableConfig(EnvironmentMutableConfig config)
throws DatabaseException {
/* Clone the current config. */
EnvironmentConfig newConfig =
configManager.getEnvironmentConfig().clone();
/* Copy in the mutable props. */
DbInternal.copyMutablePropsTo(config, newConfig);
/*
* Update the current config and notify observers. The config manager
* is replaced with a new instance that uses the new configuration.
* This avoids synchronization issues: other threads that have a
* reference to the old configuration object are not impacted.
*
* Notify listeners in reverse order of registration so that the
* environment listener is notified last and can start daemon threads
* after they are configured.
*/
configManager = resetConfigManager(newConfig);
for (int i = configObservers.size() - 1; i >= 0; i -= 1) {
EnvConfigObserver o = configObservers.get(i);
o.envConfigUpdate(configManager, newConfig);
}
}
/**
* Make a new config manager that has all the properties needed. More
* complicated for subclasses.
*/
protected DbConfigManager resetConfigManager(EnvironmentConfig newConfig) {
return new DbConfigManager(newConfig);
}
/* For unit tests */
public ExceptionListener getExceptionListener() {
return exceptionListener;
}
/**
* Adds an observer of mutable config changes.
*/
public synchronized void addConfigObserver(EnvConfigObserver o) {
configObservers.add(o);
}
/**
* Removes an observer of mutable config changes.
*/
public synchronized void removeConfigObserver(EnvConfigObserver o) {
configObservers.remove(o);
}
public INList getInMemoryINs() {
return inMemoryINs;
}
public TxnManager getTxnManager() {
return txnManager;
}
public Checkpointer getCheckpointer() {
return checkpointer;
}
public Cleaner getCleaner() {
return cleaner;
}
public MemoryBudget getMemoryBudget() {
return memoryBudget;
}
/**
* @return environment Logger, for use in debugging output.
*/
public Logger getLogger() {
return envLogger;
}
public boolean isDbLoggingDisabled() {
return dbLoggingDisabled;
}
/*
* Verification, must be run while system is quiescent.
*/
public boolean verify(VerifyConfig config, PrintStream out)
throws DatabaseException {
/* For now, verify all databases */
return dbMapTree.verify(config, out);
}
public void verifyCursors()
throws DatabaseException {
inCompressor.verifyCursors();
}
/*
* Statistics
*/
/**
* Retrieve and return stat information.
*/
public synchronized EnvironmentStats loadStats(StatsConfig config)
throws DatabaseException {
EnvironmentStats envStats = new EnvironmentStats();
envStats.setINCompStats(inCompressor.loadStats(config));
envStats.setCkptStats(checkpointer.loadStats(config));
envStats.setCleanerStats(cleaner.loadStats(config));
envStats.setLogStats(logManager.loadStats(config));
envStats.setMBAndEvictorStats(memoryBudget.loadStats(),
evictor.loadStats(config));
envStats.setLockStats(txnManager.loadStats(config));
envStats.setEnvImplStats(loadEnvImplStats(config));
return envStats;
}
public StatGroup loadEnvImplStats(StatsConfig config) {
return stats.cloneGroup(config.getClear());
}
public void incRelatchesRequired() {
relatchesRequired.increment();
}
/**
* For replicated environments only; just return true for a standalone
* environment.
*/
public boolean addDbBackup(DbBackup backup) {
return true;
}
/**
* For replicated environments only; do nothing for a standalone
* environment.
*/
public void removeDbBackup(DbBackup backup) {
}
/**
* Retrieve lock statistics
*/
public synchronized LockStats lockStat(StatsConfig config)
throws DatabaseException {
return txnManager.lockStat(config);
}
/**
* Retrieve txn statistics
*/
public synchronized TransactionStats txnStat(StatsConfig config) {
return txnManager.txnStat(config);
}
public int getINCompressorQueueSize() {
return inCompressor.getBinRefQueueSize();
}
/**
* Info about the last recovery.
*/
public RecoveryInfo getLastRecoveryInfo() {
return lastRecoveryInfo;
}
/**
* Get the environment home directory.
*/
public File getEnvironmentHome() {
return envHome;
}
/**
* Get an environment name, for tagging onto logging and debug message.
* Useful for multiple environments in a JVM, or for HA.
*/
public String getName() {
if (nodeName == null){
return envHome.toString();
} else {
return getNodeName();
}
}
public long getTxnTimeout() {
return txnTimeout;
}
public long getLockTimeout() {
return lockTimeout;
}
public long getReplayTxnTimeout() {
if (lockTimeout != 0) {
return lockTimeout;
} else {
/* It can't be disabled, so make it the minimum. */
return 1;
}
}
/**
* Returns the shared trigger latch.
*/
public SharedLatch getTriggerLatch() {
return triggerLatch;
}
public Evictor getEvictor() {
return evictor;
}
void alertEvictor() {
if (evictor != null) {
evictor.alert();
}
}
/**
* Performs critical eviction if necessary. Is called before and after
* each cursor operation.
*
* May be used here or by an overridden method in RepImpl to perform
* periodic actions. Since this method is called often by app threads, it
* may be used as a substitute for creating an internal thread.
*
* WARNING: The action performed here should be as inexpensive as possible,
* since it will impact app operation latency. Unconditional
* synchronization must not be performed, since that would introduce a new
* synchronization point for all app threads.
*
* An overriding method must call super.criticalEviction.
*
* No latches are held or synchronization is in use when this method is
* called.
*/
public void criticalEviction(boolean backgroundIO) {
evictor.doCriticalEviction(backgroundIO);
}
/**
* Performs special eviction (eviction other than standard IN eviction)
* for this environment. This method is called once per eviction batch to
* give other components an opportunity to perform eviction. For a shared
* cached, it is called for only one environment (in rotation) per batch.
*
* An overriding method must call super.specialEviction and return the sum
* of the long value it returns and any additional amount of budgeted
* memory that is evicted.
*
* No latches are held when this method is called, but it is called while
* synchronized on the evictor.
*
* @return the number of bytes evicted from the JE cache.
*/
public long specialEviction() {
return cleaner.getUtilizationTracker().evictMemory();
}
/**
* For stress testing. Should only ever be called from an assert.
*/
public static boolean maybeForceYield() {
if (forcedYield) {
Thread.yield();
}
return true; // so assert doesn't fire
}
/**
* Return true if this environment is part of a replication group.
*/
public boolean isReplicated() {
return false;
}
/**
* True if ReplicationConfig set allowConvert as true. Standalone
* environment is prohibited to do conversion, return false always.
*/
public boolean getAllowConvert() {
return false;
}
/**
* True if this environment is converted from non-replicated to
* replicated.
*/
public boolean isConverted() {
return dbMapTree.isConverted();
}
public boolean needConvert() {
return needConvert;
}
public VLSN bumpVLSN() {
/* NOP for non-replicated environment. */
return null;
}
public void decrementVLSN() {
/* NOP for non-replicated environment. */
}
/**
* @throws DatabaseException from subclasses.
*/
public VLSNRecoveryProxy getVLSNProxy()
throws DatabaseException {
return new NoopVLSNProxy();
}
public boolean isMaster() {
/* NOP for non-replicated environment. */
return false;
}
/**
*/
public void preRecoveryCheckpointInit(RecoveryInfo recoveryInfo) {
/* NOP for non-replicated environment. */
}
public void registerVLSN(LogItem logItem) {
/* NOP for non-replicated environment. */
}
/**
* Adjust the vlsn index after cleaning.
*/
public void vlsnHeadTruncate(VLSN lastVLSN, long deleteFileNum) {
/* NOP for non-replicated environment. */
}
/**
* Do any work that must be done before the checkpoint end is written, as
* as part of the checkpoint process.
* @throws DatabaseException
*/
public void preCheckpointEndFlush()
throws DatabaseException {
/* NOP for non-replicated environment. */
}
/**
* For replicated environments only; only the overridden method should
* ever be called.
* @throws DatabaseException from subclasses.
*/
public Txn createReplayTxn(long txnId) {
throw EnvironmentFailureException.unexpectedState
("Should not be called on a non replicated environment");
}
/**
* For replicated environments only; only the overridden method should
* ever be called.
* @throws DatabaseException from subclasses.
*/
public ThreadLocker createRepThreadLocker() {
throw EnvironmentFailureException.unexpectedState
("Should not be called on a non replicated environment");
}
/**
* For replicated environments only; only the overridden method should
* ever be called.
* @throws DatabaseException from subclasses.
*/
public Txn createRepUserTxn(TransactionConfig config) {
throw EnvironmentFailureException.unexpectedState
("Should not be called on a non replicated environment");
}
/**
* For replicated environments only; only the overridden method should
* ever be called.
* @throws DatabaseException from subclasses.
*/
public Txn createRepTxn(TransactionConfig config,
long mandatedId) {
throw EnvironmentFailureException.unexpectedState
("Should not be called on a non replicated environment");
}
/**
* For replicated environments only; only the overridden method should
* ever be called.
* @throws com.sleepycat.je.rep.LockPreemptedException from subclasses.
*/
public OperationFailureException
createLockPreemptedException(Locker locker, Throwable cause) {
throw EnvironmentFailureException.unexpectedState
("Should not be called on a non replicated environment");
}
/**
* For replicated environments only; only the overridden method should
* ever be called.
* @throws com.sleepycat.je.rep.DatabasePreemptedException from subclasses.
*/
public OperationFailureException
createDatabasePreemptedException(String msg,
String dbName,
Database db) {
throw EnvironmentFailureException.unexpectedState
("Should not be called on a non replicated environment");
}
/**
* For replicated environments only; only the overridden method should
* ever be called.
* @throws com.sleepycat.je.rep.LogOverwriteException from subclasses.
*/
public OperationFailureException createLogOverwriteException(String msg) {
throw EnvironmentFailureException.unexpectedState
("Should not be called on a non replicated environment");
}
/**
* Returns the first protected file number. All files from this file
* (inclusive) to the end of the log will be protected from deletion.
*
* For replicated environments, this method should be overridden to return
* the CBVLSN file.
*
* Returns -1 if all file deletion is prohibited.
*
* Requirement: This method may never return a file number less that
* (prior to) a file number returned earlier.
*/
public long getCleanerBarrierStartFile() {
/* Allow test hook to return barrier value. */
if (cleanerBarrierHoook != null) {
return cleanerBarrierHoook.getHookValue();
}
/* No files are protected by the default implementation. */
return Long.MAX_VALUE;
}
/**
* Check whether this environment can be opened on an existing environment
* directory.
*
* @throws UnsupportedOperationException via Environment ctor.
*/
public void checkRulesForExistingEnv(boolean dbTreeReplicatedBit)
throws UnsupportedOperationException {
/*
* We only permit standalone Environment construction on an existing
* environment when we are in read only mode, to support command
* line utilities. We prohibit read/write opening, because we don't
* want to chance corruption of the environment by writing non-VLSN
* tagged entries in.
*/
if (dbTreeReplicatedBit && (!isReadOnly())) {
throw new UnsupportedOperationException
("This environment was previously opened for replication."+
" It cannot be re-opened for in read/write mode for" +
" non-replicated operation.");
}
}
/**
* The VLSNRecoveryProxy is only needed for replicated environments.
*/
private class NoopVLSNProxy implements VLSNRecoveryProxy {
public void trackMapping(long lsn,
LogEntryHeader currentEntryHeader,
LogEntry targetLogEntry) {
/* intentional no-op */
}
}
}