package com.sleepycat.je.dbi;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import com.sleepycat.je.CheckpointConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.EnvironmentMutableConfig;
import com.sleepycat.je.LockStats;
import com.sleepycat.je.RunRecoveryException;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.je.cleaner.Cleaner;
import com.sleepycat.je.cleaner.UtilizationProfile;
import com.sleepycat.je.cleaner.UtilizationTracker;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.log.FileManager;
import com.sleepycat.je.log.LogManager;
import com.sleepycat.je.log.SyncedLogManager;
import com.sleepycat.je.recovery.Checkpointer;
import com.sleepycat.je.recovery.RecoveryInfo;
import com.sleepycat.je.recovery.RecoveryManager;
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.txn.Locker;
import com.sleepycat.je.txn.Txn;
import com.sleepycat.je.txn.TxnManager;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.PropUtil;
import com.sleepycat.je.utilint.Tracer;
import de.ovgu.cide.jakutil.*;
/**
* Underlying Environment implementation. There is a single instance for any
* database environment opened by the application.
*/
public class EnvironmentImpl implements EnvConfigObserver {
private static final boolean TEST_NO_LOCKING_MODE=false;
private DbEnvState envState;
private boolean closing;
private File envHome;
private int referenceCount;
private boolean isTransactional;
private boolean isNoLocking;
private boolean isReadOnly;
private MemoryBudget memoryBudget;
private long lockTimeout;
private long txnTimeout;
private DbTree dbMapTree;
private long mapTreeRootLsn=DbLsn.NULL_LSN;
private INList inMemoryINs;
private DbConfigManager configManager;
private List configObservers;
protected LogManager logManager;
private FileManager fileManager;
private TxnManager txnManager;
private Checkpointer checkpointer;
private Cleaner cleaner;
private RecoveryInfo lastRecoveryInfo;
private RunRecoveryException savedInvalidatingException;
private static boolean forcedYield=false;
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;
public static final boolean JAVA5_AVAILABLE;
private static final String DISABLE_JAVA_ADLER32="je.disable.java.adler32";
static {
boolean ret=false;
if (System.getProperty(DISABLE_JAVA_ADLER32) == null) {
String javaVersion=System.getProperty("java.version");
if (javaVersion != null && !javaVersion.startsWith("1.4.")) {
ret=true;
}
}
JAVA5_AVAILABLE=ret;
}
/**
* 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 envHomeabsolute path of the database environment home directory
* @param envConfig
* @throws DatabaseExceptionon all other failures
*/
public EnvironmentImpl( File envHome, EnvironmentConfig envConfig) throws DatabaseException {
try {
this.envHome=envHome;
envState=DbEnvState.INIT;
this.hook323();
configManager=new DbConfigManager(envConfig);
configObservers=new ArrayList();
addConfigObserver(this);
memoryBudget=new MemoryBudget(this,configManager);
this.hook336(envHome);
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';");
}
}
this.hook322();
isReadOnly=configManager.getBoolean(EnvironmentParams.ENV_RDONLY);
fileManager=new FileManager(this,envHome,isReadOnly);
if (!envConfig.getAllowCreate() && !fileManager.filesExist()) {
throw new DatabaseException("Enviroment creation isn't allowed, " + " but there is no pre-existing " + " environment in "+ envHome);
}
this.hook321();
inMemoryINs=new INList(this);
txnManager=new TxnManager(this);
createDaemons();
dbMapTree=new DbTree(this);
referenceCount=0;
this.hook320();
if (configManager.getBoolean(EnvironmentParams.ENV_RECOVERY)) {
try {
RecoveryManager recoveryManager=new RecoveryManager(this);
lastRecoveryInfo=recoveryManager.recover(isReadOnly);
}
finally {
try {
logManager.flush();
fileManager.clear();
}
catch ( IOException e) {
throw new DatabaseException(e.getMessage());
}
}
}
else {
isReadOnly=true;
noComparators=true;
}
runOrPauseDaemons(configManager);
lockTimeout=PropUtil.microsToMillis(configManager.getLong(EnvironmentParams.LOCK_TIMEOUT));
txnTimeout=PropUtil.microsToMillis(configManager.getLong(EnvironmentParams.TXN_TIMEOUT));
this.hook335();
open();
}
catch ( DatabaseException e) {
if (fileManager != null) {
try {
fileManager.close();
}
catch ( IOException IOE) {
}
}
throw e;
}
}
/**
* Respond to config updates.
*/
public void envConfigUpdate( DbConfigManager mgr) throws DatabaseException {
runOrPauseDaemons(mgr);
}
/**
* Read configurations for daemons, instantiate.
*/
private void createDaemons() throws DatabaseException {
new EnvironmentImpl_createDaemons(this).execute();
}
/**
* Run or pause daemons, depending on config properties.
*/
private void runOrPauseDaemons( DbConfigManager mgr) throws DatabaseException {
if (!isReadOnly) {
this.hook330(mgr);
this.hook333(mgr);
this.hook326(mgr);
}
this.hook317(mgr);
}
/**
* Returns the UtilizationTracker.
*/
public UtilizationTracker getUtilizationTracker(){
return cleaner.getUtilizationTracker();
}
/**
* Returns the UtilizationProfile.
*/
public UtilizationProfile getUtilizationProfile(){
return cleaner.getUtilizationProfile();
}
/**
* Log the map tree root and save the LSN.
*/
public void logMapTreeRoot() throws DatabaseException {
mapTreeRootLsn=logManager.log(dbMapTree);
}
/**
* Force a rewrite of the map tree root if required.
*/
public void rewriteMapTreeRoot( long cleanerTargetLsn) throws DatabaseException {
if (DbLsn.compareTo(cleanerTargetLsn,mapTreeRootLsn) == 0) {
mapTreeRootLsn=logManager.log(dbMapTree);
}
}
/**
* @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 {
dbMapTree=(DbTree)logManager.get(rootLsn);
dbMapTree.setEnvironmentImpl(this);
this.hook324(rootLsn);
}
/**
* Not much to do, mark state.
*/
public void open(){
envState=DbEnvState.OPEN;
}
/**
* Invalidate the environment. Done when a fatal exception
* (RunRecoveryException) is thrown.
*/
public void invalidate( RunRecoveryException e){
savedInvalidatingException=e;
envState=DbEnvState.INVALID;
requestShutdownDaemons();
}
/**
* @return true if environment is open.
*/
public boolean isOpen(){
return (envState == DbEnvState.OPEN);
}
/**
* @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 RunRecoveryException 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 RunRecoveryException {
if (envState == DbEnvState.INVALID) {
savedInvalidatingException.setAlreadyThrown();
throw savedInvalidatingException;
}
}
public void checkNotClosed() throws DatabaseException {
if (envState == DbEnvState.CLOSED) {
throw new DatabaseException("Attempt to use a Environment that has been closed.");
}
}
public synchronized void close() throws DatabaseException {
if (--referenceCount <= 0) {
doClose(true);
}
}
public synchronized void close( boolean doCheckpoint) throws DatabaseException {
if (--referenceCount <= 0) {
doClose(doCheckpoint);
}
}
private void doClose( boolean doCheckpoint) throws DatabaseException {
StringBuffer errors=new StringBuffer();
try {
this.hook319();
try {
envState.checkState(DbEnvState.VALID_FOR_CLOSE,DbEnvState.CLOSED);
}
catch ( DatabaseException DBE) {
throw DBE;
}
requestShutdownDaemons();
if (doCheckpoint && !isReadOnly && (envState != DbEnvState.INVALID)&& logManager.getLastLsnAtRecovery() != fileManager.getLastUsedLsn()) {
CheckpointConfig ckptConfig=new CheckpointConfig();
ckptConfig.setForce(true);
ckptConfig.setMinimizeRecoveryTime(true);
try {
invokeCheckpoint(ckptConfig,false,"close");
}
catch ( DatabaseException IE) {
errors.append("\nException performing checkpoint: ");
errors.append(IE.toString()).append("\n");
}
}
try {
shutdownDaemons();
}
catch ( InterruptedException IE) {
errors.append("\nException shutting down daemon threads: ");
errors.append(IE.toString()).append("\n");
}
this.hook318();
try {
logManager.flush();
}
catch ( DatabaseException DBE) {
errors.append("\nException flushing log manager: ");
errors.append(DBE.toString()).append("\n");
}
try {
fileManager.clear();
}
catch ( IOException IOE) {
errors.append("\nException clearing file manager: ");
errors.append(IOE.toString()).append("\n");
}
catch ( DatabaseException DBE) {
errors.append("\nException clearing file manager: ");
errors.append(DBE.toString()).append("\n");
}
try {
fileManager.close();
}
catch ( IOException IOE) {
errors.append("\nException clearing file manager: ");
errors.append(IOE.toString()).append("\n");
}
catch ( DatabaseException DBE) {
errors.append("\nException clearing file manager: ");
errors.append(DBE.toString()).append("\n");
}
try {
inMemoryINs.clear();
}
catch ( DatabaseException DBE) {
errors.append("\nException closing file manager: ");
errors.append(DBE.toString()).append("\n");
}
this.hook337();
DbEnvPool.getInstance().remove(envHome);
this.hook325(errors);
}
finally {
envState=DbEnvState.CLOSED;
}
if (errors.length() > 0 && savedInvalidatingException == null) {
throw new RunRecoveryException(this,errors.toString());
}
}
public synchronized void closeAfterRunRecovery() throws DatabaseException {
try {
shutdownDaemons();
}
catch ( InterruptedException IE) {
}
try {
fileManager.clear();
}
catch ( Exception e) {
}
try {
fileManager.close();
}
catch ( Exception e) {
}
DbEnvPool.getInstance().remove(envHome);
}
public synchronized void forceClose() throws DatabaseException {
referenceCount=1;
close();
}
public synchronized void incReferenceCount(){
referenceCount++;
}
public static int getThreadLocalReferenceCount(){
return threadLocalReferenceCount;
}
public static synchronized void incThreadLocalReferenceCount(){
threadLocalReferenceCount++;
}
public static synchronized void decThreadLocalReferenceCount(){
threadLocalReferenceCount--;
}
public static boolean getNoComparators(){
return noComparators;
}
/**
* Invoke a checkpoint programatically. 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;
}
else {
return false;
}
}
public int invokeCleaner() throws DatabaseException {
if (cleaner != null) {
return cleaner.doClean(true,false);
}
else {
return 0;
}
}
private void requestShutdownDaemons(){
closing=true;
this.hook331();
this.hook334();
this.hook327();
}
/**
* Ask all daemon threads to shut down.
*/
private void shutdownDaemons() throws InterruptedException {
shutdownCheckpointer();
}
void shutdownCheckpointer() throws InterruptedException {
if (checkpointer != null) {
this.hook328();
checkpointer=null;
}
return;
}
public boolean isNoLocking(){
return isNoLocking;
}
public boolean isTransactional(){
return isTransactional;
}
public boolean isReadOnly(){
return isReadOnly;
}
public DatabaseImpl createDb( Locker locker, String databaseName, DatabaseConfig dbConfig, Database databaseHandle) throws DatabaseException {
return dbMapTree.createDb(locker,databaseName,dbConfig,databaseHandle);
}
/**
* Get a database object given a database name.
* @param databaseNametarget database.
* @return null if database doesn't exist.
*/
public DatabaseImpl getDb( Locker locker, String databaseName, Database databaseHandle) throws DatabaseException {
return dbMapTree.getDb(locker,databaseName,databaseHandle);
}
public List getDbNames() throws DatabaseException {
return dbMapTree.getDbNames();
}
/**
* For debugging.
*/
public void dumpMapTree() throws DatabaseException {
dbMapTree.dump();
}
/**
* Transactional services.
*/
public Txn txnBegin( Transaction parent, TransactionConfig txnConfig) throws DatabaseException {
if (!isTransactional) {
throw new DatabaseException("beginTransaction called, " + " but Environment was not opened " + "with transactional cpabilities");
}
return txnManager.txnBegin(parent,txnConfig);
}
public LogManager getLogManager(){
return logManager;
}
public FileManager getFileManager(){
return fileManager;
}
public DbTree getDbMapTree(){
return dbMapTree;
}
/**
* Returns the config manager for the current base configuration.
* <p>
* 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.
* </p>
*/
public DbConfigManager getConfigManager(){
return configManager;
}
/**
* Clones the current configuration.
*/
public EnvironmentConfig cloneConfig(){
return DbInternal.cloneConfig(configManager.getEnvironmentConfig());
}
/**
* 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( EnvironmentConfig config) throws IllegalArgumentException {
DbInternal.checkImmutablePropsForEquality(configManager.getEnvironmentConfig(),config);
}
/**
* Changes the mutable config properties that are present in the given
* config, and notifies all config observer.
*/
public synchronized void setMutableConfig( EnvironmentMutableConfig config) throws DatabaseException {
EnvironmentConfig newConfig=DbInternal.cloneConfig(configManager.getEnvironmentConfig());
DbInternal.copyMutablePropsTo(config,newConfig);
configManager=new DbConfigManager(newConfig);
for (int i=configObservers.size() - 1; i >= 0; i-=1) {
EnvConfigObserver o=(EnvConfigObserver)configObservers.get(i);
o.envConfigUpdate(configManager);
}
}
/**
* 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;
}
/**
* Info about the last recovery
*/
public RecoveryInfo getLastRecoveryInfo(){
return lastRecoveryInfo;
}
/**
* Get the environment home directory.
*/
public File getEnvironmentHome(){
return envHome;
}
public long getTxnTimeout(){
return txnTimeout;
}
public long getLockTimeout(){
return lockTimeout;
}
/**
* For stress testing. Should only ever be called from an assert.
*/
public static boolean maybeForceYield(){
if (forcedYield) {
Thread.yield();
}
return true;
}
@MethodObject static class EnvironmentImpl_createDaemons {
EnvironmentImpl_createDaemons( EnvironmentImpl _this){
this._this=_this;
}
void execute() throws DatabaseException {
checkpointerWakeupTime=0;
this.hook329();
_this.checkpointer=new Checkpointer(_this,checkpointerWakeupTime,"Checkpointer");
this.hook332();
_this.cleaner=new Cleaner(_this,"Cleaner");
}
protected EnvironmentImpl _this;
protected long checkpointerWakeupTime;
protected long compressorWakeupInterval;
protected void hook329() throws DatabaseException {
}
protected void hook332() throws DatabaseException {
}
}
protected void hook317( DbConfigManager mgr) throws DatabaseException {
}
protected void hook318() throws DatabaseException {
}
protected void hook319() throws DatabaseException {
}
protected void hook320() throws DatabaseException {
}
protected void hook321() throws DatabaseException {
logManager=new SyncedLogManager(this,isReadOnly);
}
protected void hook322() throws DatabaseException {
}
protected void hook323() throws DatabaseException {
}
protected void hook324( long rootLsn) throws DatabaseException {
mapTreeRootLsn=rootLsn;
}
protected void hook325( StringBuffer errors) throws DatabaseException {
}
protected void hook326( DbConfigManager mgr) throws DatabaseException {
}
protected void hook327(){
}
protected void hook328() throws InterruptedException {
}
protected void hook330( DbConfigManager mgr) throws DatabaseException {
}
protected void hook331(){
}
protected void hook333( DbConfigManager mgr) throws DatabaseException {
}
protected void hook334(){
}
protected void hook335() throws DatabaseException {
}
protected void hook336( File envHome) throws DatabaseException {
}
protected void hook337() throws DatabaseException {
}
}