Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*-
    * See the file LICENSE for redistribution information.
    *
    * Copyright (c) 2002, 2013 Oracle and/or its affiliates.  All rights reserved.
    *
    */
   
   package com.sleepycat.je.recovery;
   
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  
  public class RecoveryManager {
      private static final String TRACE_LN_REDO = "LNRedo:";
      private static final String TRACE_LN_UNDO = "LNUndo";
      private static final String TRACE_IN_REPLACE = "INRecover:";
      private static final String TRACE_ROOT_REPLACE = "RootRecover:";
      private static final String TRACE_ROOT_DELETE = "RootDelete:";
  
      private final EnvironmentImpl envImpl;
      private final int readBufferSize;
      private final RecoveryInfo info;                // stat info
      /* Committed txn ID to Commit LSN */
      private final Map<LongLongcommittedTxnIds;
      private final Set<LongabortedTxnIds;          // aborted txns
      private Map<LongPreparedTxnpreparedTxns;    // txnid -> prepared Txn
  
      /*
       * A set of lsns for log entries that will be resurrected is kept so that
       * we can correctly redo utilization. See redoUtilization()
       */
      private Set<LongresurrectedLsns;
  
     /* dbs for which we have to build the in memory IN list. */
     private final Set<DatabaseIdinListBuildDbIds;
 
     private final Set<DatabaseIdtempDbIds;        // temp DBs to be removed
 
     private final Set<DatabaseIdexpectDeletedMapLNs;
 
     /* Handles rollback periods created by HA syncup. */
     private final RollbackTracker rollbackTracker;
 
     private final RecoveryUtilizationTracker tracker;
     private final StartupTracker startupTracker;
     private final Logger logger;
 
     /* DBs that may violate the rule for upgrading to log version 8. */
     private final Set<DatabaseIdlogVersion8UpgradeDbs;
 
     /* Whether deltas violate the rule for upgrading to log version 8. */
     private final AtomicBoolean logVersion8UpgradeDeltas;

    
Make a recovery manager
 
     public RecoveryManager(EnvironmentImpl env)
         throws DatabaseException {
 
         this. = env;
         DbConfigManager cm = env.getConfigManager();
          =
             cm.getInt(.);
          = new HashMap<LongLong>();
          = new HashSet<Long>();
          = new HashMap<LongPreparedTxn>();
          = new HashSet<Long>();
          = new HashSet<DatabaseId>();
          = new HashSet<DatabaseId>();
          = new HashSet<DatabaseId>();
          = new RecoveryUtilizationTracker(env);
          = LoggerUtils.getLogger(getClass());
          = new RollbackTracker();
          = new RecoveryInfo();
          = new HashSet<DatabaseId>();
          = new AtomicBoolean(false);
 
     }

    
Look for an existing log and use it to create an in memory structure for accessing existing databases. The file manager and logging system are only available after recovery.

Returns:
RecoveryInfo statistics about the recovery process.
 
     public RecoveryInfo recover(boolean readOnly)
         throws DatabaseException {
 
         try {
             FileManager fileManager = .getFileManager();
             DbConfigManager configManager = .getConfigManager();
             boolean forceCheckpoint =
                 configManager.getBoolean
                 (.);
             if (fileManager.filesExist()) {
 
                 /* 
                  * Check whether log files are correctly located in the sub 
                  * directories. 
                  */
                 fileManager.getAllFileNumbers();
 
                 /*
                  * Establish the location of the end of the log.  Log this
                  * information to the java.util.logging logger, but delay
                  * tracing this information in the .jdb file, because the
                  * logging system is not yet initialized. Because of that, be
                  * sure to use lazy logging, and do not use
                  * LoggerUtils.logAndTrace().
                  */
                 findEndOfLog(readOnly);
 
                 String endOfLogMsg = "Recovery underway, found end of log";
                 Trace.traceLazily(endOfLogMsg);
 
                 /*
                  * Establish the location of the root, the last checkpoint, and
                  * the first active LSN by finding the last checkpoint.
                  */
                 findLastCheckpoint();
 
                 .getLogManager().setLastLsnAtRecovery
                     (fileManager.getLastUsedLsn());
 
                 /* Read in the root. */
                 .readMapTreeFromLog(.);
 
                 /* Build the in memory tree from the log. */
                 buildTree();
             } else {
 
                 /*
                  * Nothing more to be done. Enable publishing of debug log
                  * messages to the database log.
                  */
                 LoggerUtils.logMsg
                     (."Recovery w/no files.");
 
                 /* Enable the INList and log the root of the mapping tree. */
                 .getInMemoryINs().enable();
                 /* Do not write LNs in a read-only environment. */
                 if (!readOnly) {
                     .logMapTreeRoot();
                 }
 
                 /* Add shared cache environment when buildTree is not used. */
                 if (.getSharedCache()) {
                     .getEvictor().addEnvironment();
                 }
 
                 /*
                  * Always force a checkpoint during creation.
                  */
                 forceCheckpoint = true;
             }
 
             int ptSize = .size();
             if (ptSize > 0) {
                 boolean singular = (ptSize == 1);
                 LoggerUtils.logMsg(.,
                                    "There " + (singular ? "is " : "are ") +
                                    ptSize + " prepared but unfinished " +
                                    (singular ? "txn." : "txns."));
 
                 /*
                  * We don't need this set any more since these are all
                  * registered with the TxnManager now.
                  */
                  = null;
             }
 
             /*
              * Open the UP database and populate the cache before the first
              * checkpoint so that the checkpoint may flush file summary
              * information.  May be disabled for unittests.
              */
             if (DbInternal.getCreateUP
                 (.getConfigManager().getEnvironmentConfig())) {
                 .start(.);
                 .setProgress
                     (.);
                 .getUtilizationProfile().populateCache
                     (.getCounter(.));
                 .stop(.);
             }
 
             /* Transfer recovery utilization info to the global tracker. */
             .transferToUtilizationTracker
                 (.getUtilizationTracker());
 
             /* Update cleaner's log summary info. */
             if (. != null) {
                 .getCleaner().setLogSummary
                     (..getCleanerLogSummary());
             }
 
             /*
              * After utilization info is complete and prior to the checkpoint,
              * remove all temporary databases encountered during recovery.
              */
             removeTempDbs();
 
             /*
              * For truncate/remove NameLNs with no corresponding deleted MapLN
              * found, delete the MapLNs now.
              */
             deleteMapLNs();
 
             /*
              * Execute any replication initialization that has to happen before
              * the checkpoint.
              */
             .preRecoveryCheckpointInit();
 
             /*
              * At this point, we've recovered (or there were no log files at
              * all). Write a checkpoint into the log.
              */
             if (!readOnly &&
                 ((.getLogManager().getLastLsnAtRecovery() !=
                   .) ||
                  forceCheckpoint)) {
 
                 CheckpointConfig config = new CheckpointConfig();
                 config.setForce(true);
                 config.setMinimizeRecoveryTime(true);
 
                 .setProgress(.);
                 .start(.);
                 .invokeCheckpoint(config"recovery");
                 .setStats
                     (.,
                      .getCheckpointer().loadStats(.));
                 .stop(.);
             } else {
                 /* Initialize intervals when there is no initial checkpoint. */
                 .getCheckpointer().initIntervals
                     (..,
                      System.currentTimeMillis());
             }
         } catch (IOException e) {
             LoggerUtils.traceAndLogException("RecoveryManager",
                                              "recover""Couldn't recover"e);
             throw new EnvironmentFailureException
                 (.e);
         } finally {
             .stop(.);
         }
 
         return ;
     }

    
Find the end of the log, initialize the FileManager. While we're perusing the log, return the last checkpoint LSN if we happen to see it.
 
     private void findEndOfLog(boolean readOnly)
         throws IOExceptionDatabaseException {
 
         Counter counter = .getCounter(.);
         LastFileReader reader = new LastFileReader();
 
         /*
          * Tell the reader to iterate through the log file until we hit the end
          * of the log or an invalid entry.  Remember the last seen CkptEnd, and
          * the first CkptStart with no following CkptEnd.
          */
         while (reader.readNextEntry()) {
             counter.incNumRead();
             counter.incNumProcessed();
             LogEntryType type = reader.getEntryType();
             if (..equals(type)) {
                 . = reader.getLastLsn();
                 . = .;
             } else if (..equals(type)) {
                 if (. == .) {
                     . = reader.getLastLsn();
                 }
             } else if (..equals(type)) {
                 . = reader.getLastLsn();
             }
         }
 
         /*
          * The last valid LSN should point to the start of the last valid log
          * entry, while the end of the log should point to the first byte of
          * blank space, so these two should not be the same.
          */
         assert (reader.getLastValidLsn() != reader.getEndOfLog()):
         "lastUsed=" + DbLsn.getNoFormatString(reader.getLastValidLsn()) +
             " end=" + DbLsn.getNoFormatString(reader.getEndOfLog());
 
         /* Now truncate if necessary. */
         if (!readOnly) {
             reader.setEndOfFile();
         }
 
         /* Tell the fileManager where the end of the log is. */
         . = reader.getLastValidLsn();
         . = reader.getEndOfLog();
         counter.setRepeatIteratorReads(reader.getNRepeatIteratorReads());
                                                  .,
                                                  reader.getPrevOffset());
     }

    
Find the last checkpoint and establish the firstActiveLsn point, checkpoint start, and checkpoint end.
 
     private void findLastCheckpoint()
         throws IOExceptionDatabaseException {
 
         Counter counter = .getCounter(.);
 
         /*
          * The checkpointLsn might have been already found when establishing
          * the end of the log.  If it was found, then partialCheckpointStartLsn
          * was also found.  If it was not found, search backwards for it now
          * and also set partialCheckpointStartLsn.
          */
         if (. == .) {
 
             /*
              * Search backwards though the log for a checkpoint end entry and a
              * root entry.
              */
             CheckpointFileReader searcher =
                 new CheckpointFileReader(false,
                                          ..,
                                          .);
 
             while (searcher.readNextEntry()) {
                 counter.incNumRead();
                 counter.incNumProcessed();
 
                 /*
                  * Continue iterating until we find a checkpoint end entry.
                  * While we're at it, remember the last root seen in case we
                  * don't find a checkpoint end entry.
                  */
                 if (searcher.isCheckpointEnd()) {
 
                     /*
                      * We're done, the checkpoint end will tell us where the
                      * root is.
                      */
                     . = searcher.getLastLsn();
                     break;
                 } else if (searcher.isCheckpointStart()) {
 
                     /*
                      * Remember the first CkptStart following the CkptEnd.
                      */
                     . = searcher.getLastLsn();
 
                 } else if (searcher.isDbTree()) {
 
                     /*
                      * Save the last root that was found in the log in case we
                      * don't see a checkpoint.
                      */
                     if (. == .) {
                         . = searcher.getLastLsn();
                     }
                 }
             }
             counter.setRepeatIteratorReads(searcher.getNRepeatIteratorReads());
         }
 
         /*
          * If we haven't found a checkpoint, we'll have to recover without
          * one. At a minimium, we must have found a root.
          */
         if (. == .) {
             . = .;
             . = .;
         } else {
             /* Read in the checkpoint entry. */
             CheckpointEnd checkpointEnd = (CheckpointEnd)
                 (.getLogManager().getEntry(.));
             . = checkpointEnd;
             . = checkpointEnd.getCheckpointStartLsn();
             . = checkpointEnd.getFirstActiveLsn();
 
             /*
              * Use the last checkpoint root only if there is no later root.
              * The latest root has the correct per-DB utilization info.
              */
             if (checkpointEnd.getRootLsn() != . &&
                 . == .) {
                 . = checkpointEnd.getRootLsn();
             }
 
             /* Init the checkpointer's id sequence.*/
             .getCheckpointer().setCheckpointId(checkpointEnd.getId());
         }
 
         /*
          * Let the rollback tracker know where the checkpoint start is.
          * Rollback periods before the checkpoint start do not need to be
          * processed.
          */
 
 
         if (. == .) {
             throw new EnvironmentFailureException
                 (,
                  .,
                  "This environment's log file has no root. Since the root " +
                  "is the first entry written into a log at environment " +
                  "creation, this should only happen if the initial creation " +
                  "of the environment was never checkpointed or synced. " +
                  "Please move aside the existing log files to allow the " +
                  "creation of a new environment");
         }
     }

    
Use the log to recreate an in memory tree.
 
     private void buildTree()
         throws DatabaseException {
 
 
         try {
 
             /*
              * Read all map database INs, find largest node ID before any
              * possiblity of splits, find largest txn Id before any need for a
              * root update (which would use an AutoTxn)
              */
             buildINs(true /*mappingTree*/
                      .,
                      .
                      .,
                      .);
 
             /*
              * Undo all aborted map LNs. Read and remember all committed,
              * prepared, and replicated transaction ids, to prepare for the
              * redo phases.
              */
             .start(.);
             Set<LogEntryTypemapLNSet = new HashSet<LogEntryType>();
             mapLNSet.add(.);
             mapLNSet.add(.);
             mapLNSet.add(.);
             mapLNSet.add(.);
             mapLNSet.add(.);
             mapLNSet.add(.);
             undoLNs(mapLNSettrue /*firstUndoPass*/,
                     .getCounter(.));
             .stop(.);
 
             /*
              * Replay all mapLNs, mapping tree in place now. Use the set of
              * committed txns, replicated and prepared txns found from the undo
              * pass.
              */
             .start(.);
             mapLNSet.add(.);
             redoLNs(mapLNSet.getCounter(.));
             .stop(.);
 
             /*
              * When the mapping DB is complete, check for log version 8 upgrade
              * violations. Will throw an exception if there is a violation.
              */
             checkLogVersion8UpgradeViolations();
 
             /*
              * Reconstruct the internal nodes for the main level trees.
              */
             buildINs(false /*mappingTree*/
                      .
                      .,
                      .,
                      .);
 
             /*
              * Build the in memory IN list. Before this point, the INList is not
              * enabled, and fetched INs have not been put on the list.  Once the
              * tree is complete we can add the environment to the evictor (for a
              * shared cache) and invoke the evictor.  The evictor will also be
              * invoked during the undo and redo passes.
              */
             buildINList();
             if (.getSharedCache()) {
                 .getEvictor().addEnvironment();
             }
             .invokeEvictor();
 
             /*
              * Undo aborted LNs. No need to include TxnAbort, TxnCommit,
              * TxnPrepare, RollbackStart and RollbackEnd records again, since
              * those were scanned during the the undo of all aborted MapLNs.
              */
             .start(.);
             Set<LogEntryTypelnSet = new HashSet<LogEntryType>();
             for (LogEntryType entryType : LogEntryType.getAllTypes()) {
                 if (entryType.isUserLNType() && entryType.isTransactional()) {
                     lnSet.add(entryType);
                 }
             }
             lnSet.add(.);
             undoLNs(lnSetfalse /*firstUndoPass*/,
                     .getCounter(.));
             .stop(.);
 
             /* Replay LNs. Also read non-transactional LNs. */
             .start(.);
             for (LogEntryType entryType : LogEntryType.getAllTypes()) {
                 if (entryType.isUserLNType() && !entryType.isTransactional()) {
                     lnSet.add(entryType);
                 }
             }
 
             lnSet.add(.);
             lnSet.add(.);
             redoLNs(lnSet.getCounter(.));
             .stop(.);
 
             .recoveryEndFsyncInvisible();
         } finally {
             .stop(.);
         }
     }
 
     /*
      * Up to two passes for the INs of a given level.
      *
      * @param mappingTree if true, we're building the mapping tree
      */
     private void buildINs(boolean mappingTree
                           StartupTracker.Phase phaseA,
                           StartupTracker.Phase phaseB,
                           RecoveryProgress progressA,
                           RecoveryProgress progressB)
         throws DatabaseException {
 
         Set<LogEntryTypetargetEntries = new HashSet<LogEntryType>();
       
         /* Main tree and mapping tree read these types of entries. */
         targetEntries.add(.);
         targetEntries.add(.);
         targetEntries.add(.);
 
         /*
          * Pass a: Read all INs and place into the proper location.
          */
         .start(phaseA);
         .setProgress(progressA);
         LevelRecorder recorder = new LevelRecorder();
 
         if (mappingTree) {
             readINsAndTrackIds(.recorder,
                                .getCounter(phaseA));
         } else {
             readINs(.,
                     false,  // mapping tree only
                     targetEntries,
                     recorder,
                     .getCounter(phaseA));
         }
         .stop(phaseA);
 
         /*
          * Pass b: Redo INs if the LevelRecorder detects a split/compression
          * was done after ckpt [#14424]
          */
         Set<DatabaseIdredoSet = recorder.getDbsWithDifferentLevels();
         if (redoSet.size() > 0) {
             .start(phaseB);
             .setProgress(progressB);
             repeatReadINs(.targetEntriesredoSet,
                           .getCounter(phaseB));
             .stop(phaseB);
         }
     }
 
     /*
      * Read every internal node and IN DeleteInfo in the mapping tree and place
      * in the in-memory tree. Also peruse all pertinent log entries in order to
      * update our knowledge of the last used database, transaction and node
      * ids, and to to track utilization profile and VLSN->LSN mappings.
      */
     private void readINsAndTrackIds(long rollForwardLsn,
                                     LevelRecorder recorder,
                                     StartupTracker.Counter counter)
         throws DatabaseException {
 
         INFileReader reader =
             new INFileReader(,
                              ,
                              rollForwardLsn,        // start lsn
                              .// end lsn
                              true,   // track node and db ids
                              false,  // map db only
                              .,
                              .,
                              ,
                              ,
                              );
         reader.addTargetType(.);
         reader.addTargetType(.);
         reader.addTargetType(.);
 
         /* Validate all entries in at least one full recovery pass. */
         reader.setAlwaysValidateChecksum(true);
 
         try {
             DbTree dbMapTree = .getDbTree();
 
             /* Process every IN and BIN in the mapping tree. */
             while (reader.readNextEntry()) {
                 counter.incNumRead();
                 DatabaseId dbId = reader.getDatabaseId();
                 if (dbId.equals(.)) {
                     DatabaseImpl db = dbMapTree.getDb(dbId);
                     try {
                         replayOneIN(readerdb,
                                     reader.isBINDelta() /*requireExactMatch*/,
                                     recorder);
                         counter.incNumProcessed();
                     } finally {
                         dbMapTree.releaseDb(db);
                     }
                 }
             }
             counter.setRepeatIteratorReads(reader.getNRepeatIteratorReads());
 
             /*
              * Update node ID, database ID, and txn ID sequences. Use either
              * the maximum of the IDs seen by the reader vs. the IDs stored in
              * the checkpoint.
              */
             . = reader.getMinReplicatedNodeId();
             . = reader.getMaxNodeId();
 
             . = reader.getMinReplicatedDbId();
             . = reader.getMaxDbId();
 
             . = reader.getMinReplicatedTxnId();
             . = reader.getMaxTxnId();
 
             if (. != null) {
                 CheckpointEnd ckptEnd = .;
 
                 if (. >
                     ckptEnd.getLastReplicatedNodeId()) {
                     . =
                         ckptEnd.getLastReplicatedNodeId();
                 }
                 if (. < ckptEnd.getLastLocalNodeId()) {
                     . = ckptEnd.getLastLocalNodeId();
                 }
 
                 if (. >
                     ckptEnd.getLastReplicatedDbId()) {
                     . =
                         ckptEnd.getLastReplicatedDbId();
                 }
                 if (. < ckptEnd.getLastLocalDbId()) {
                     . = ckptEnd.getLastLocalDbId();
                 }
 
                 if (. >
                     ckptEnd.getLastReplicatedTxnId()) {
                     . =
                         ckptEnd.getLastReplicatedTxnId();
                 }
                 if (. < ckptEnd.getLastLocalTxnId()) {
                     . = ckptEnd.getLastLocalTxnId();
                 }
             }
 
             .getNodeSequence().
                 setLastNodeId(..);
                                             .);
                                                  .);
 
             . = reader.getVLSNProxy();
         } catch (Exception e) {
             traceAndThrowException(reader.getLastLsn(), "readMapIns"e);
         }
     }

    
Read INs and process.
 
     private void readINs(long rollForwardLsn,
                          boolean mapDbOnly,
                          Set<LogEntryTypetargetLogEntryTypes,
                          LevelRecorder recorder,
                          StartupTracker.Counter counter)
         throws DatabaseException {
 
         /* Don't need to track IDs. */
         INFileReader reader =
             new INFileReader(,
                              ,
                              rollForwardLsn,                 // startlsn
                              .,          // finish
                              false,
                              mapDbOnly,
                              .,
                              .,
                              );
 
         for (LogEntryType t : targetLogEntryTypes) {
             reader.addTargetType(t);
         }
 
         try {
 
             /*
              * Read all non-provisional INs, and process if they don't belong
              * to the mapping tree.
              */
             DbTree dbMapTree = .getDbTree();
             while (reader.readNextEntry()) {
                 DatabaseId dbId = reader.getDatabaseId();
                 boolean isMapDb = dbId.equals(.);
                 boolean isTarget = false;
                 counter.incNumRead();
 
                 if (mapDbOnly && isMapDb) {
                     isTarget = true;
                 } else if (!mapDbOnly && !isMapDb) {
                     isTarget = true;
                 }
 
                 if (isTarget) {
                     DatabaseImpl db = dbMapTree.getDb(dbId);
                     try {
                         if (db == null) {
                             // This db has been deleted, ignore the entry.
                             counter.incNumDeleted();
                         } else {
                             replayOneIN
                                 (readerdb,
                                  reader.isBINDelta() /*requireExactMatch*/,
                                  recorder);
                             counter.incNumProcessed();
 
                             /*
                              * Add any db that we encounter IN's for because
                              * they'll be part of the in-memory tree and
                              * therefore should be included in the INList
                              * build.
                              */
                             .add(dbId);
                         }
                     } finally {
                         dbMapTree.releaseDb(db);
                     }
                 }
             }
 
             counter.setRepeatIteratorReads(reader.getNRepeatIteratorReads());
         } catch (Exception e) {
             traceAndThrowException(reader.getLastLsn(), "readNonMapIns"e);
         }
     }

    
Read INs and process.
 
     private void repeatReadINs(long rollForwardLsn,
                                Set<LogEntryTypetargetLogEntryTypes,
                                Set<DatabaseIdtargetDbs,
                                StartupTracker.Counter counter)
         throws DatabaseException {
 
         /* Don't need to track IDs. */
         INFileReader reader =
             new INFileReader(,
                              ,
                              rollForwardLsn,                 // startlsn
                              .,          // finish
                              false,
                              false,                          // mapDbOnly
                              .,
                              .,
                              );
 
         for (LogEntryType t : targetLogEntryTypes) {
             reader.addTargetType(t);
         }
 
         try {
 
             /* Read all non-provisional INs that are in the repeat set. */
             DbTree dbMapTree = .getDbTree();
             while (reader.readNextEntry()) {
                 counter.incNumRead();
                 DatabaseId dbId = reader.getDatabaseId();
                 if (targetDbs.contains(dbId)) {
                     DatabaseImpl db = dbMapTree.getDb(dbId);
                     try {
                         if (db == null) {
                             /* This db has been deleted, ignore the entry. */
                             counter.incNumDeleted();
                         } else {
                             counter.incNumProcessed();
                             replayOneIN(reader,
                                         db,
                                         true,  // requireExactMatch,
                                         null); // level recorder
                         }
                     } finally {
                         dbMapTree.releaseDb(db);
                     }
                 }
             }
 
             counter.setRepeatIteratorReads(reader.getNRepeatIteratorReads());
         } catch (Exception e) {
             traceAndThrowException(reader.getLastLsn(), "readNonMapIns"e);
         }
     }

    
Get an IN from the reader, set its database, and fit into tree.
 
     private void replayOneIN(INFileReader reader,
                              DatabaseImpl db,
                              boolean requireExactMatch,
                              LevelRecorder recorder)
         throws DatabaseException {
 
         /*
          * Last entry is a node, replay it. Now, we should really call
          * IN.postFetchInit, but we want to do something different from the
          * faulting-in-a-node path, because we don't want to put the IN on the
          * in memory list, and we don't want to search the db map tree, so we
          * have a IN.postRecoveryInit.
          */
         final long logLsn = reader.getLastLsn();
         final IN in = reader.getIN(db);
         in.postRecoveryInit(dblogLsn);
         in.latch();
 
         /*
          * Track the levels, in case we need an extra splits vs ckpt
          * reconcilliation.
          */
         if (recorder != null) {
             recorder.record(db.getId(), in.getLevel());
         }
         replaceOrInsert(dbinlogLsnrequireExactMatch);
     }

    
Undo all LNs that belong to aborted transactions. These are LNs in the log that (1) don't belong to a committed txn AND (2) aren't part of a prepared txn AND (3) shouldn't be resurrected as part of a replication ReplayTxn. LNs that are part of a rollback period do need to be undone, but in a different way from the other LNs. They are rolledback and take a different path. To find these LNs, walk the log backwards, using log entry commit record to create a collection of committed txns. If we see a log entry that doesn't fit the criteria above, undo it.

Parameters:
firstUndoPass is true if this is the first time that undoLNs is called. This is a little awkward, but is done to explicitly indicate to the rollback tracker that this is the tracker's construction phase. During this first pass, RollbackStart and RollbackEnd are in the target log types, and the rollback period map is created. We thought that it was better to be explicit than to reply on checking the logTypes parameter to see if RollbackStart/RollbackEnd is included.
 
     private void undoLNs(Set<LogEntryTypelogTypes,
                          boolean firstUndoPass,
                          StartupTracker.Counter counter)
         throws DatabaseException {
 
         long firstActiveLsn = .;
         long lastUsedLsn = .;
         long endOfFileLsn = .;
 
         /* Set up a reader to pick up target log entries from the log. */
         LNFileReader reader =
             new LNFileReader(lastUsedLsn,
                              falseendOfFileLsnfirstActiveLsnnull,
                              .);
 
         for (LogEntryType ltlogTypes) {
             reader.addTargetType(lt);
         }
 
         DbTree dbMapTree = .getDbTree();
 
         /*
          * See RollbackTracker.java for details on replication rollback
          * periods.  Standalone recovery must handle replication rollback at
          * recovery, because we might be opening a replicated environment in a
          * read-only, non-replicated way for use by a command line utility.
          * Even though the utility will not write invisible bits, it will need
          * to ensure that all btree nodes are in the proper state, and reflect
          * any rollback related changes.
          *
          * The rollbackScanner is a sort of cursor that acts with the known
          * state of the rollback period detection.
          *
          * We let the tracker know if it is the first pass or not, in order
          * to support some internal tracker assertions.
          */
         .setFirstPass(firstUndoPass);
         final Scanner rollbackScanner =  .getScanner();
 
        try {
            /*
             * Iterate over the target LNs and commit records, constructing the
             * tree.
             */
            while (reader.readNextEntry()) {
                counter.incNumRead();
                if (reader.isLN()) {
                    /* Get the txnId from the log entry. */
                    Long txnId = reader.getTxnId();
                    /* Skip past this, no need to undo non-txnal LNs. */
                    if (txnId == null) {
                        continue;
                    }
                    if (rollbackScanner.positionAndCheck(reader.getLastLsn(),
                                                         txnId)) {
                        /*
                         * If an LN is in the rollback period and was part of a
                         * rollback, let the rollback scanner decide how it
                         * should be handled. This does not include LNs that
                         * were explicitly aborted.
                         */
                        rollbackScanner.rollback(txnIdreader);
                        continue;
                    }
                    /* This LN is part of a committed txn. */
                    if (.containsKey(txnId)) {
                        continue;
                    }
                    /* This LN is part of a prepared txn. */
                    if (.get(txnId) != null) {
                        .add(reader.getLastLsn());
                        continue;
                    }
                    /*
                     * This LN is part of a uncommitted, unaborted
                     * replicated txn.
                     */
                    if (isReplicatedUncommittedLN(readertxnId)) {
                        createReplayTxn(txnId);
                        .add(reader.getLastLsn());
                        continue;
                    }
                    undoUncommittedLN(readerdbMapTree);
                    counter.incNumProcessed();
                } else if (reader.isPrepare()) {