/** * 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 * @throws DatabaseException on all other failures */ public EnvironmentImpl(File envHome, EnvironmentConfig envConfig) throws DatabaseException { try { this.envHome = envHome; envState = DbEnvState.INIT; /* Set up configuration parameters */ configManager = new DbConfigManager(envConfig); configObservers = new ArrayList(); addConfigObserver(this); /* * Decide on memory budgets based on environment config params and * memory available to this process. */ memoryBudget = new LogBufferBudget(this, configManager); /* * Set up debug logging. Depending on configuration, add handlers, * set logging level. */ // envLogger = initLogger(envHome); /* * Essential services. These must exist before recovery. */ hook_readProperties(configManager); forcedYield = configManager.getBoolean(EnvironmentParams.ENV_FORCED_YIELD); isNoLocking = !(configManager.getBoolean(EnvironmentParams.ENV_INIT_LOCKING)); 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); } logManager = new SyncedLogManager(this, isReadOnly); inMemoryINs = new INList(this); txnManager = new TxnManager(this); /* * Make sure that either log-size-based or time-based checkpointing is * enabled. */ checkpointer = new Checkpointer(this); cleaner = new Cleaner(this, "Cleaner"); /* * 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(); /* * Recovery will recreate the dbMapTree from the log if it exists. */ dbMapTree = new DbTree(this); referenceCount = 0; /* * Do not do recovery and start daemons 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); } finally { try { /* Flush to get all exception tracing out to the log. */ logManager.flush(); fileManager.clear(); } catch (IOException e) { throw new DatabaseException(e.getMessage()); } } } else { isReadOnly = true; noComparators = true; } /* Start daemons after recovery. */ runOrPauseDaemons(configManager); /* * Cache a few critical values. We keep our timeout in millis * instead of microseconds because Object.wait takes millis. */ lockTimeout = PropUtil.microsToMillis(configManager.getLong(EnvironmentParams.LOCK_TIMEOUT)); txnTimeout = PropUtil.microsToMillis(configManager.getLong(EnvironmentParams.TXN_TIMEOUT)); /* Mark as open. */ open(); } catch (DatabaseException e) { /* Release any environment locks if there was a problem. */ if (fileManager != null) { try { fileManager.close(); } catch (IOException IOE) { /* * Klockwork - ok Eat it, we want to throw the original * exception. */ } } throw e; } }
/** * Rollback the changes to this txn's write locked nodes up to but not including the entry at the * specified matchpoint. When we log a transactional entry, we record the LSN of the original, * before-this-transaction version as the abort LSN. This means that if there are multiple updates * to a given record in a single transaction, each update only references that original version * and its true predecessor. * * <p>This was done to streamline abort processing, so that an undo reverts directly to the * original version rather than stepping through all the intermediates. The intermediates are * skipped. However, undo to a matchpoint may need to stop at an intermediate point, so we need to * create a true chain of versions. * * <p>To do so, we read the transaction backwards from the last logged LSN to reconstruct a * transaction chain that links intermediate versions of records. For example, suppose our * transaction looks like this and that we are undoing up to LSN 250 * * <p>lsn=100 node=A (version 1) lsn=200 node=B (version 1) <-- matchpointLsn lsn=300 node=C * (version 1) lsn=400 node=A (version 2) lsn=500 node=B (version 2) lsn=600 node=A (version 3) * lsn=700 node=A (version 4) * * <p>To setup the old versions, We walk from LSN 700 -> 100 700 (A) rolls back to 600 600 (A) * rolls back to 400 500 (B) rolls back to 200 400 (A) rolls back to 100 300 (C) rolls back to an * empty slot (NULL_LSN). * * <p>A partial rollback also requires resetting the lastLoggedLsn field, because these operations * are no longer in the btree and their on-disk entries are no longer valid. * * <p>Lastly, the appropriate write locks must be released. * * @param matchpointLsn the rollback should go up to but not include this LSN. */ private void undoWrites(long matchpointLsn, List<Long> rollbackLsns) throws DatabaseException { /* * Generate a map of nodeId->List of intermediate LSNs for this node. * to re-create the transaction chain. */ TreeLocation location = new TreeLocation(); Long undoLsn = lastLoggedLsn; TxnChain chain = new TxnChain(undoLsn, id, matchpointLsn, undoDatabases, envImpl); try { while ((undoLsn != DbLsn.NULL_LSN) && DbLsn.compareTo(undoLsn, matchpointLsn) > 0) { UndoReader undo = new UndoReader(envImpl, undoLsn, undoDatabases); RevertInfo revertTo = chain.pop(); logFinest(undoLsn, undo, revertTo); /* * When we undo this log entry, we've logically truncated * it from the log. Remove it from the btree and mark it * obsolete. */ RecoveryManager.rollbackUndo(logger, Level.FINER, undo, revertTo, location, undoLsn); countObsoleteInexact(undoLsn, undo); rollbackLsns.add(undoLsn); /* * Move on to the previous log entry for this txn and update * what is considered to be the end of the transaction chain. */ undoLsn = undo.logEntry.getUserTxn().getLastLsn(); lastLoggedLsn = undoLsn; } /* * Correct the fields which hold LSN and VLSN state that may * now be changed. */ lastApplied = chain.getLastValidVLSN(); if (!updateLoggedForTxn()) { firstLoggedLsn = NULL_LSN; } } catch (DatabaseException e) { LoggerUtils.traceAndLogException( envImpl, "Txn", "undo", "For LSN=" + DbLsn.getNoFormatString(undoLsn), e); throw e; } catch (RuntimeException e) { throw EnvironmentFailureException.unexpectedException( "Txn undo for LSN=" + DbLsn.getNoFormatString(undoLsn), e); } if (lastLoggedLsn == DbLsn.NULL_LSN) { /* * The whole txn is rolled back, and it may not appear again. This * is the equivalent of an abort. Do any delete processing for an * abort which is needed. * * Set database state for deletes before releasing any write * locks. */ setDeletedDatabaseState(false); } /* Clear any write locks that are no longer needed. */ clearWriteLocks(chain.getRemainingLockedNodes()); }