All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.je.log.LogManager Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.log;

import static com.sleepycat.je.log.LogStatDefinition.GROUP_DESC;
import static com.sleepycat.je.log.LogStatDefinition.GROUP_NAME;
import static com.sleepycat.je.log.LogStatDefinition.LOGMGR_END_OF_LOG;
import static com.sleepycat.je.log.LogStatDefinition.LOGMGR_REPEAT_FAULT_READS;
import static com.sleepycat.je.log.LogStatDefinition.LOGMGR_REPEAT_ITERATOR_READS;
import static com.sleepycat.je.log.LogStatDefinition.LOGMGR_TEMP_BUFFER_WRITES;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.cleaner.ExpirationTracker;
import com.sleepycat.je.cleaner.LocalUtilizationTracker;
import com.sleepycat.je.cleaner.TrackedFileSummary;
import com.sleepycat.je.cleaner.UtilizationTracker;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.EnvironmentFailureReason;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.entry.LogEntry;
import com.sleepycat.je.log.entry.RestoreRequired;
import com.sleepycat.je.txn.WriteLockInfo;
import com.sleepycat.je.util.verify.VerifierUtils;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.LSNStat;
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.VLSN;

/**
 * The LogManager supports reading and writing to the JE log.
 * The writing of data to the log is serialized via the logWriteMutex.
 * Typically space is allocated under the LWL. The client computes
 * the checksum and copies the data into the log buffer (not holding
 * the LWL).
 */
public class LogManager {

    private final LogBufferPool logBufferPool; // log buffers
    private final Object logWriteMutex;           // synchronizes log writes
    private final boolean doChecksumOnRead;      // if true, do checksum on read
    private final FileManager fileManager;       // access to files
    private final FSyncManager grpManager;
    private final EnvironmentImpl envImpl;
    private final boolean readOnly;

    /* How many bytes to read when faulting in. */
    private final int readBufferSize;

    /* The last LSN in the log during recovery. */
    private long lastLsnAtRecovery = DbLsn.NULL_LSN;

    /* Stats */
    private final StatGroup stats;

    /*
     * Number of times we have to repeat a read when we fault in an object
     * because the initial read was too small.
     */
    private final LongStat nRepeatFaultReads;

    /*
     * Number of times that FileReaders can't grown the read buffer to
     * accomodate a large log entry.
     */
    private final LongStat nRepeatIteratorReads;

    /*
     * Number of times we have to use the temporary marshalling buffer to
     * write to the log.
     */
    private final LongStat nTempBufferWrites;

    /* The location of the next entry to be written to the log. */
    private final LSNStat endOfLog;

    /*
     * Used to determine if we switched log buffers. For
     * NOSYNC durability, if we switched log buffers,
     * the thread will write the previous dirty buffers.
     */
    private LogBuffer prevLogBuffer = null;

    /* For unit tests */
    private TestHook readHook; // used for generating exceptions on log reads

    /* For unit tests. */
    private TestHook delayVLSNRegisterHook;
    private TestHook flushHook;

    /* A queue to hold log entries which are to be logged lazily. */
    private final Queue lazyLogQueue =
        new ConcurrentLinkedQueue<>();

    /*
     * Used for tracking the current file. Is null if no tracking should occur.
     * Read/write of this field is protected by the LWL, but the tracking
     * actually occurs outside the LWL.
     */
    private ExpirationTracker expirationTracker = null;

    /*
     * An entry in the lazyLogQueue. A struct to hold the entry and repContext.
     */
    private static class LazyQueueEntry {
        private final LogEntry entry;
        private final ReplicationContext repContext;

        private LazyQueueEntry(LogEntry entry, ReplicationContext repContext) {
            this.entry = entry;
            this.repContext = repContext;
        }
    }

    /**
     * There is a single log manager per database environment.
     */
    public LogManager(EnvironmentImpl envImpl,
                      boolean readOnly)
        throws DatabaseException {

        /* Set up log buffers. */
        this.envImpl = envImpl;
        this.fileManager = envImpl.getFileManager();
        this.grpManager = new FSyncManager(this.envImpl);
        DbConfigManager configManager = envImpl.getConfigManager();
        this.readOnly = readOnly;
        logBufferPool = new LogBufferPool(fileManager, envImpl);

        /* See if we're configured to do a checksum when reading in objects. */
        doChecksumOnRead =
            configManager.getBoolean(EnvironmentParams.LOG_CHECKSUM_READ);

        logWriteMutex = new Object();
        readBufferSize =
            configManager.getInt(EnvironmentParams.LOG_FAULT_READ_SIZE);

        /* Do the stats definitions. */
        stats = new StatGroup(GROUP_NAME, GROUP_DESC);
        nRepeatFaultReads = new LongStat(stats, LOGMGR_REPEAT_FAULT_READS);
        nRepeatIteratorReads =
            new LongStat(stats, LOGMGR_REPEAT_ITERATOR_READS);
        nTempBufferWrites = new LongStat(stats, LOGMGR_TEMP_BUFFER_WRITES);
        endOfLog = new LSNStat(stats, LOGMGR_END_OF_LOG);
    }

    boolean getChecksumOnRead() {
        return doChecksumOnRead;
    }

    public long getLastLsnAtRecovery() {
        return lastLsnAtRecovery;
    }

    public void setLastLsnAtRecovery(long lastLsnAtRecovery) {
        this.lastLsnAtRecovery = lastLsnAtRecovery;
    }

    /**
     * Called at the end of recovery to begin expiration tracking using the
     * given tracker. During recovery we are single threaded, so we can set
     * the field without taking the LWL.
     */
    public void initExpirationTracker(final ExpirationTracker tracker) {
        expirationTracker = tracker;
    }

    /**
     * Reset the pool when the cache is resized.  This method is called after
     * the memory budget has been calculated.
     */
    public void resetPool(DbConfigManager configManager)
            throws DatabaseException {
        synchronized (logWriteMutex) {
           logBufferPool.reset(configManager);
        }
    }

    /*
     * Writing to the log
     */

    /**
     * Log this single object and force a write of the log files.
     * @param entry object to be logged
     * @param fsyncRequired if true, log files should also be fsynced.
     * @return LSN of the new log entry
     */
    public long logForceFlush(LogEntry entry,
                              boolean fsyncRequired,
                              ReplicationContext repContext)
        throws DatabaseException {

        return log(entry,
                   Provisional.NO,
                   true,           // flush required
                   fsyncRequired,
                   false,          // forceNewLogFile
                   repContext);    // repContext
    }

    /**
     * Log this single object and force a flip of the log files.
     * @param entry object to be logged
     * @return LSN of the new log entry
     */
    public long logForceFlip(LogEntry entry)
        throws DatabaseException {

        return log(entry,
                   Provisional.NO,
                   true,           // flush required
                   false,          // fsync required
                   true,           // forceNewLogFile
                   ReplicationContext.NO_REPLICATE);
    }

    /**
     * Write a log entry.
     * @param entry object to be logged
     * @return LSN of the new log entry
     */
    public long log(LogEntry entry, ReplicationContext repContext)
        throws DatabaseException {

        return log(entry,
                   Provisional.NO,
                   false,           // flush required
                   false,           // fsync required
                   false,           // forceNewLogFile
                   repContext);
    }

    /**
     * Write a log entry lazily.
     * @param entry object to be logged
     */
    void logLazily(LogEntry entry, ReplicationContext repContext) {

        lazyLogQueue.add(new LazyQueueEntry(entry, repContext));
    }

    /**
     * Translates individual log params to LogItem and LogContext fields.
     */
    private long log(final LogEntry entry,
                     final Provisional provisional,
                     final boolean flushRequired,
                     final boolean fsyncRequired,
                     final boolean forceNewLogFile,
                     final ReplicationContext repContext)
        throws DatabaseException {

        final LogParams params = new LogParams();

        params.entry = entry;
        params.provisional = provisional;
        params.repContext = repContext;
        params.flushRequired = flushRequired;
        params.fsyncRequired = fsyncRequired;
        params.forceNewLogFile = forceNewLogFile;

        final LogItem item = log(params);

        return item.lsn;
    }

    /**
     * Log an item, first logging any items on the lazyLogQueue, and finally
     * flushing and sync'ing (if requested).
     */
    public LogItem log(LogParams params)
        throws DatabaseException {

        final LogItem item = new LogItem();

        /*
         * In a read-only env we return NULL_LSN (the default value for
         * LogItem.lsn) for all entries.  We allow this to proceed, rather
         * than throwing an exception, to support logging INs for splits that
         * occur during recovery, for one reason.  Logging LNs in a read-only
         * env is not allowed, and this is checked in the LN class.
         */
        if (readOnly) {
            return item;
        }

        try {
            /* Flush any pending lazy entries. */
            for (LazyQueueEntry lqe = lazyLogQueue.poll();
                 lqe != null;
                 lqe = lazyLogQueue.poll()) {

                LogParams lqeParams = new LogParams();
                lqeParams.entry = lqe.entry;
                lqeParams.provisional = Provisional.NO;
                lqeParams.repContext = lqe.repContext;

                logItem(new LogItem(), lqeParams);
            }

            final LogEntry logEntry = params.entry;

            /*
             * If possible, marshall this entry outside the log write latch to
             * allow greater concurrency by shortening the write critical
             * section.  Note that the header may only be created during
             * marshalling because it calls entry.getSize().
             */
            if (logEntry.getLogType().marshallOutsideLatch()) {

                item.header = new LogEntryHeader(
                    logEntry, params.provisional, params.repContext);

                item.buffer = marshallIntoBuffer(item.header, logEntry);
            }

            logItem(item, params);

            if (params.fsyncRequired || params.flushRequired) {

                /* Flush log buffers and write queue, and optionally fsync. */
                grpManager.flushAndSync(params.fsyncRequired);

            } else if (params.switchedLogBuffer) {
                /*
                 * The operation does not require writing to the log file, but
                 * since we switched log buffers, this thread will write the
                 * previously dirty log buffers (not this thread's log entry
                 * though). This is done for NOSYNC durability so those types
                 * of transactions won't fill all the log buffers thus forcing
                 * to have to write the buffers under the log write latch.
                 */
                logBufferPool.writeDirty(false /*flushWriteQueue*/);
            }

            TestHookExecute.doHookIfSet(flushHook);

            /*
             * We've logged this log entry from the replication stream. Let the
             * Replicator know, so this node can create a VLSN->LSN mapping. Do
             * this before the ckpt so we have a better chance of writing this
             * mapping to disk.
             */
            if (params.repContext.inReplicationStream()) {

                assert (item.header.getVLSN() != null) :
                    "Unexpected null vlsn: " + item.header + " " +
                    params.repContext;

                /* Block the VLSN registration, used by unit tests. */
                TestHookExecute.doHookIfSet(delayVLSNRegisterHook);

                envImpl.registerVLSN(item);
            }

        } catch (EnvironmentFailureException e) {

            /*
             * Final checks are below for unexpected exceptions during the
             * critical write path.  Most should be caught by
             * serialLogInternal, but the catches here account for other
             * exceptions above.  Note that Errors must be caught here as well
             * as Exceptions.  [#21929]
             *
             * If we've already invalidated the environment, rethrow so as not
             * to excessively wrap the exception.
             */
            if (!envImpl.isValid()) {
                throw e;
            }
            throw EnvironmentFailureException.unexpectedException(envImpl, e);

        } catch (Exception e) {
            throw EnvironmentFailureException.unexpectedException(envImpl, e);

        } catch (Error e) {
            envImpl.invalidate(e);
            throw e;
        }

        /*
         * Periodically, as a function of how much data is written, ask the
         * checkpointer or the cleaner to wake up.
         */
        envImpl.getCheckpointer().wakeupAfterWrite();
        envImpl.getCleaner().wakeupAfterWrite(item.size);

        /* Update background writes. */
        if (params.backgroundIO) {
            envImpl.updateBackgroundWrites(
                item.size, logBufferPool.getLogBufferSize());
        }

        return item;
    }

    private void logItem(final LogItem item, final LogParams params)
        throws IOException, DatabaseException {

        final UtilizationTracker tracker = envImpl.getUtilizationTracker();

        final LogWriteInfo lwi = serialLog(item, params, tracker);

        if (lwi != null) {

            /*
             * Add checksum, prev offset, and VLSN to the entry.
             * Copy data into the log buffer.
             */
            item.buffer = item.header.addPostMarshallingInfo(
                item.buffer, lwi.fileOffset, lwi.vlsn);

            lwi.lbs.put(item.buffer);
        }

        /* Update obsolete info under the LWL */
        updateObsolete(params, tracker);

        /* Expiration tracking is protected by the Btree latch, not the LWL. */
        if (params.expirationTrackerToUse != null) {
            params.expirationTrackerToUse.track(params.entry, item.size);
        }

        /* Queue flushing of expiration tracker after a file flip. */
        if (params.expirationTrackerCompleted != null) {
            envImpl.getExpirationProfile().addCompletedTracker(
                params.expirationTrackerCompleted);
        }
    }

    /**
     * This method handles exceptions to be certain that the Environment is
     * invalidated when any exception occurs in the critical write path, and it
     * checks for an invalid environment to be sure that no subsequent write is
     * allowed.  [#21929]
     *
     * Invalidation is necessary because a logging operation does not ensure
     * that the internal state -- correspondence of LSN pointer, log buffer
     * position and file position, and the integrity of the VLSN index [#20919]
     * -- is maintained correctly when an exception occurs.  Allowing a
     * subsequent write can cause log corruption.
     */
    private LogWriteInfo serialLog(
        final LogItem item,
        final LogParams params,
        final UtilizationTracker tracker)
        throws IOException {

        synchronized (logWriteMutex) {

            /* Do not attempt to write with an invalid environment. */
            envImpl.checkIfInvalid();

            try {
                return serialLogWork(item, params, tracker);

            } catch (EnvironmentFailureException e) {
                /*
                 * If we've already invalidated the environment, rethrow so
                 * as not to excessively wrap the exception.
                 */
                if (!envImpl.isValid()) {
                    throw e;
                }

                /* Otherwise, invalidate the environment. */
                throw EnvironmentFailureException.unexpectedException(
                    envImpl, e);

            } catch (Exception e) {
                throw EnvironmentFailureException.unexpectedException(
                    envImpl, e);

            } catch (Error e) {
                /* Errors must be caught here as well as Exceptions.[#21929] */
                envImpl.invalidate(e);
                throw e;
            }
        }
    }

    /**
     * This method is used as part of writing data to the log. Called
     * under the LogWriteLatch.
     * Data is either written to the LogBuffer or allocates space in the
     * LogBuffer. The LogWriteInfo object is used to save information about
     * the space allocate in the LogBuffer. The caller uses the object to
     * copy data into the underlying LogBuffer. A null value returned
     * indicates that the item was written to the log. This occurs when the
     * data item is too big to fit into an empty LogBuffer.
     *
     * @param params log params.
     * @param tracker utilization.
     * @return a LogWriteInfo object used to access allocated LogBuffer space.
     *           If null, the data was written to the log.
     */
    private LogWriteInfo serialLogWork(
        final LogItem item,
        final LogParams params,
        final UtilizationTracker tracker)
        throws IOException {

        /*
         * Do obsolete tracking before marshalling a FileSummaryLN into the
         * log buffer so that a FileSummaryLN counts itself.
         * countObsoleteNode must be called before computing the entry
         * size, since it can change the size of a FileSummaryLN entry that
         * we're logging
         */
        final LogEntryType entryType = params.entry.getLogType();

        if (!DbLsn.isTransientOrNull(params.oldLsn)) {
            if (params.obsoleteDupsAllowed) {
                tracker.countObsoleteNodeDupsAllowed(
                    params.oldLsn, entryType, params.oldSize);
            } else {
                tracker.countObsoleteNode(
                    params.oldLsn, entryType, params.oldSize);
            }
        }

        /* Count auxOldLsn for same database; no specified size. */
        if (!DbLsn.isTransientOrNull(params.auxOldLsn)) {
            if (params.obsoleteDupsAllowed) {
                tracker.countObsoleteNodeDupsAllowed(
                    params.auxOldLsn, entryType, 0);
            } else {
                tracker.countObsoleteNode(params.auxOldLsn, entryType, 0);
            }
        }

        /*
         * Compute the VLSNs and modify the DTVLSN in commit/abort entries
         * before the entry is marshalled or its size is required. At that
         * at this point we are committed to writing a log entry with  the
         * computed VLSN.
         */
        final VLSN vlsn;

        if (params.repContext.getClientVLSN() != null ||
            params.repContext.mustGenerateVLSN()) {

            if (params.repContext.mustGenerateVLSN()) {
                vlsn = envImpl.assignVLSNs(params.entry);
            } else {
                vlsn = params.repContext.getClientVLSN();
                if (params.repContext.inReplicationStream()) {
                    envImpl.setReplicaLatestVLSNSeq(vlsn.getSequence());
                }
            }
        } else {
            vlsn = null;
        }

        /*
         * If an entry must be protected within the log write latch for
         * marshalling, take care to also calculate its size in the
         * protected section. Note that we have to get the size *before*
         * marshalling so that the currentLsn and size are correct for
         * utilization tracking.
         */
        final boolean marshallOutsideLatch = (item.buffer != null);
        final int entrySize;

        if (marshallOutsideLatch) {
            entrySize = item.buffer.limit();
            assert item.header != null;
        } else {
            assert item.header == null;
            item.header = new LogEntryHeader(
                params.entry, params.provisional, params.repContext);
            entrySize = item.header.getEntrySize();
        }

        /*
         * Get the next free slot in the log, under the log write latch.
         */
        if (params.forceNewLogFile) {
            fileManager.forceNewLogFile();
        }

        final boolean flippedFile = fileManager.shouldFlipFile(entrySize);
        final long currentLsn = fileManager.calculateNextLsn(flippedFile);

        /*
         * TODO: Count file header, since it is not logged via LogManager.
         * Some tests (e.g., INUtilizationTest) will need to be adjusted.
         *
        final int fileHeaderSize = FileManager.firstLogEntryOffset();
        if (DbLsn.getFileOffset(currentLsn) == fileHeaderSize) {
            final long fileNum = DbLsn.getFileNumber(currentLsn);

            tracker.countNewLogEntry(
                DbLsn.makeLsn(fileNum, 0), LogEntryType.LOG_FILE_HEADER,
                fileHeaderSize, null);
        }
        */

        /*
         * countNewLogEntry and countObsoleteNodeInexact cannot change
         * a FileSummaryLN size, so they are safe to call after
         * getSizeForWrite.
         */
        tracker.countNewLogEntry(currentLsn, entryType, entrySize);

        /*
         * LN deletions and dup DB LNs are obsolete immediately.  Inexact
         * counting is used to save resources because the cleaner knows
         * that all such LNs are obsolete.
         */
        if (params.entry.isImmediatelyObsolete(params.nodeDb)) {
            tracker.countObsoleteNodeInexact(currentLsn, entryType, entrySize);
        }

        /*
         * This entry must be marshalled within the log write latch.
         */
        if (!marshallOutsideLatch) {
            assert item.buffer == null;
            item.buffer = marshallIntoBuffer(item.header, params.entry);
        }

        /* Sanity check */
        if (entrySize != item.buffer.limit()) {
            throw EnvironmentFailureException.unexpectedState(
                "Logged entry entrySize= " + entrySize +
                " but marshalledSize=" + item.buffer.limit() +
                " type=" + entryType + " currentLsn=" +
                DbLsn.getNoFormatString(currentLsn));
        }

        /*
         * Ask for a log buffer suitable for holding this new entry. If
         * entrySize is larger than the LogBuffer capacity, this will flush
         * all dirty buffers and return the next empty (but too small) buffer.
         * The returned buffer is not latched.
         */
        final LogBuffer lastLogBuffer =
            logBufferPool.getWriteBuffer(entrySize, flippedFile);

        /*
         * Bump the LSN values, which gives us a valid previous pointer,
         * which is part of the log entry header. This must be done:
         *  - before logging the currentLsn.
         *  - after calling getWriteBuffer, to flush the prior file when
         *    flippedFile is true.
         */
        final long prevOffset = fileManager.advanceLsn(
            currentLsn, entrySize, flippedFile);

        if (lastLogBuffer != prevLogBuffer) {
            params.switchedLogBuffer = true;
        }
        prevLogBuffer = lastLogBuffer;

        final LogBufferSegment useBuffer;

        lastLogBuffer.latchForWrite();
        try {
            useBuffer = lastLogBuffer.allocate(entrySize);

            if (useBuffer != null) {
                /* Register the lsn while holding the buffer latch. */
                lastLogBuffer.registerLsn(currentLsn);
            } else {
                /*
                 * The item buffer is larger than the LogBuffer capacity, so
                 * write the item buffer to the file directly. Note that
                 * getWriteBuffer has flushed all dirty buffers.
                 *
                 * First add checksum, prev offset, and VLSN to the entry.
                 */
                item.buffer = item.header.addPostMarshallingInfo(
                    item.buffer, prevOffset, vlsn);

                final boolean flushWriteQueue =
                    params.flushRequired && !params.fsyncRequired;

                fileManager.writeLogBuffer(
                    new LogBuffer(item.buffer, currentLsn),
                    flushWriteQueue);

                assert lastLogBuffer.getDataBuffer().position() == 0;

                /* Leave a clue that the buffer size needs to be increased. */
                nTempBufferWrites.increment();
            }
        } finally {
            lastLogBuffer.release();
        }

        /*
         * If the txn is not null, the first entry is an LN. Update the txn
         * with info about the latest LSN. Note that this has to happen
         * within the log write latch.
         */
        params.entry.postLogWork(item.header, currentLsn, vlsn);

        item.lsn = currentLsn;
        item.size = entrySize;

        /* If the expirationTracker field is null, no tracking should occur. */
        if (expirationTracker != null) {
            /*
             * When logging to a new file, also flip the expirationTracker
             * under the LWL and return expirationTrackerCompleted so it will
             * be queued for flushing.
             */
            final long newFile = DbLsn.getFileNumber(item.lsn);
            if (flippedFile && newFile != expirationTracker.getFileNum()) {
                params.expirationTrackerCompleted = expirationTracker;
                expirationTracker = new ExpirationTracker(newFile);
            }
            /*
             * Increment the pending calls under the LWL, so we can determine
             * when we're finished.
             */
            expirationTracker.incrementPendingTrackCalls();
            params.expirationTrackerToUse = expirationTracker;
        }

        return (useBuffer == null ?
                null : new LogWriteInfo(useBuffer, vlsn, prevOffset));
    }

    /**
     * Serialize a loggable object into this buffer.
     */
    private ByteBuffer marshallIntoBuffer(LogEntryHeader header,
                                          LogEntry entry) {
        int entrySize = header.getSize() + header.getItemSize();

        ByteBuffer destBuffer = ByteBuffer.allocate(entrySize);
        header.writeToLog(destBuffer);

        /* Put the entry in. */
        entry.writeEntry(destBuffer);

        /* Set the limit so it can be used as the size of the entry. */
        destBuffer.flip();

        return destBuffer;
    }

    /**
     * Serialize a log entry into this buffer with proper entry header. Return
     * it ready for a copy.
     */
    ByteBuffer putIntoBuffer(LogEntry entry,
                             long prevLogEntryOffset) {
        LogEntryHeader header = new LogEntryHeader
            (entry, Provisional.NO, ReplicationContext.NO_REPLICATE);

        /*
         * Currently this method is only used for serializing the FileHeader.
         * Assert that we do not need the Txn mutex in case this method is used
         * in the future for other log entries. See LN.log. [#17204]
         */
        assert !entry.getLogType().isTransactional();

        ByteBuffer destBuffer = marshallIntoBuffer(header, entry);

        return header.addPostMarshallingInfo(destBuffer,
            prevLogEntryOffset,
            null);
    }

    /*
     * Reading from the log.
     */

    /**
     * Instantiate all the objects in the log entry at this LSN.
     */
    public LogEntry getLogEntry(long lsn)
        throws FileNotFoundException, ErasedException {

        return getLogEntry(lsn, 0, false /*invisibleReadAllowed*/).
            getEntry();
    }

    public WholeEntry getWholeLogEntry(long lsn)
        throws FileNotFoundException, ErasedException {

        return getLogEntry(lsn, 0, false /*invisibleReadAllowed*/);
    }

    /**
     * Instantiate all the objects in the log entry at this LSN. Allow the
     * fetch of invisible log entries if we are in recovery.
     */
    public WholeEntry getLogEntryAllowInvisibleAtRecovery(long lsn, int size)
        throws FileNotFoundException, ErasedException {

        return getLogEntry(
            lsn, size, envImpl.isInInit() /*invisibleReadAllowed*/);
    }

    /**
     * Instantiate all the objects in the log entry at this LSN. The entry
     * may be marked invisible.
     */
    public WholeEntry getLogEntryAllowInvisible(long lsn)
        throws FileNotFoundException, ErasedException {

        return getLogEntry(lsn, 0, true);
    }

    /**
     * Instantiate all the objects in the log entry at this LSN.
     * @param lsn location of entry in log.
     * @param invisibleReadAllowed true if it's expected that the target log
     * entry might be invisible. Correct the known-to-be-bad checksum before
     * proceeding.
     * @return log entry that embodies all the objects in the log entry.
     */
    private WholeEntry getLogEntry(
        long lsn,
        int lastLoggedSize,
        boolean invisibleReadAllowed)
        throws FileNotFoundException, ErasedException {

        /* Fail loudly if the environment is invalid. */
        envImpl.checkIfInvalid();

        LogSource logSource = null;
        try {

            /*
             * Get a log source for the log entry which provides an abstraction
             * that hides whether the entry is in a buffer or on disk. Will
             * register as a reader for the buffer or the file, which will take
             * a latch if necessary. Latch is released in finally block.
             */
            logSource = getLogSource(lsn);

            try {
                return getLogEntryFromLogSource(
                    lsn, lastLoggedSize, logSource, invisibleReadAllowed);

            } catch (ChecksumException ce) {

                /*
                 * When using a FileSource, a checksum error indicates a
                 * persistent corruption. An EFE with LOG_CHECKSUM is created
                 * in the catch below and EFE.isCorrupted will return true.
                 */
                if (!(logSource instanceof LogBuffer)) {
                    assert logSource instanceof FileSource;
                    throw ce;
                }

                /*
                 * When using a LogBuffer source, we must try to read the entry
                 * from disk to see if the corruption is persistent.
                 */
                final LogBuffer logBuffer = (LogBuffer) logSource;
                FileHandle fileHandle = null;
                long fileLength = -1;
                try {
                    fileHandle =
                        fileManager.getFileHandle(DbLsn.getFileNumber(lsn));
                    fileLength = fileHandle.getFile().length();
                } catch (IOException ioe) {
                    /* FileNotFound or another IOException was thrown. */
                }

                /*
                 * If the file does not exist (FileNotFoundException is thrown
                 * above) or the firstLsn in the buffer does not appear in the
                 * file (the buffer was not flushed), then the corruption is
                 * not persistent and we throw a EFE for which isCorrupted
                 * will return false.
                 */
                if (fileHandle == null ||
                    fileLength <=
                        DbLsn.getFileOffset(logBuffer.getFirstLsn())) {

                    throw EnvironmentFailureException.unexpectedException(
                        envImpl,
                        "Corruption detected in log buffer, " +
                            "but was not written to disk.",
                        ce);
                }

                /*
                 * The log entry should have been written to the file. Try
                 * getting the log entry from the FileSource. If a
                 * ChecksumException is thrown, the corruption is persistent
                 * and an EFE with LOG_CHECKSUM is thrown below.
                 */
                final FileSource fileSource = new FileHandleSource(
                    fileHandle, readBufferSize, fileManager);
                try {
                    return getLogEntryFromLogSource(
                        lsn, lastLoggedSize, fileSource,
                        invisibleReadAllowed);
                } finally {
                    fileSource.release();
                }
            }

        } catch (ChecksumException e) {
            /*
             * WARNING: EFE with LOG_CHECKSUM indicates a persistent corruption
             * and therefore LogSource.release must not be called until after
             * invalidating the environment (in the finally below). The buffer
             * latch prevents the corrupt buffer from being logged by another
             * thread.
             */
            throw VerifierUtils.createMarkerFileFromException(
                RestoreRequired.FailureType.LOG_CHECKSUM,
                e,
                envImpl,
                EnvironmentFailureReason.LOG_CHECKSUM);

        } catch (Error e) {
            envImpl.invalidate(e);
            throw e;

        } finally {
            if (logSource != null) {
                logSource.release();
            }
        }
    }

    public LogEntry getLogEntryHandleNotFound(long lsn)
        throws DatabaseException {

        try {
            return getLogEntry(lsn);
        } catch (FileNotFoundException e) {
            throw new EnvironmentFailureException(
                envImpl,
                EnvironmentFailureReason.LOG_FILE_NOT_FOUND, e);
        } catch (ErasedException e) {
            throw new EnvironmentFailureException(
                envImpl, EnvironmentFailureReason.LOG_CHECKSUM,
                "Entry is erased unexpectedly, implied corruption", e);
        }
    }

    public WholeEntry getWholeLogEntryHandleNotFound(long lsn)
        throws DatabaseException {

        try {
            return getWholeLogEntry(lsn);
        } catch (FileNotFoundException e) {
            throw new EnvironmentFailureException
                (envImpl,
                    EnvironmentFailureReason.LOG_FILE_NOT_FOUND, e);
        } catch (ErasedException e) {
            throw new EnvironmentFailureException(
                envImpl, EnvironmentFailureReason.LOG_CHECKSUM,
                "Entry is erased unexpectedly, implied corruption", e);
        }
    }

    /**
     * Throws ChecksumException rather than translating it to
     * EnvironmentFailureException and invalidating the environment.  Used
     * instead of getLogEntry when a ChecksumException is handled specially.
     */
    LogEntry getLogEntryAllowChecksumException(long lsn)
        throws ChecksumException, FileNotFoundException, ErasedException,
                DatabaseException {

        final LogSource logSource = getLogSource(lsn);

        try {
            return getLogEntryFromLogSource(
                lsn, 0, logSource, false /*invisibleReadAllowed*/).
                getEntry();
        } finally {
            logSource.release();
        }
    }

    LogEntry getLogEntryAllowChecksumException(long lsn,
                                               RandomAccessFile file,
                                               int logVersion)
        throws ChecksumException, ErasedException, DatabaseException {

        final LogSource logSource = new FileSource(
            file, readBufferSize, fileManager, DbLsn.getFileNumber(lsn),
            logVersion);

        try {
            return getLogEntryFromLogSource(
                lsn, 0, logSource,
                false /*invisibleReadAllowed*/).
                getEntry();
        } finally {
            logSource.release();
        }
    }

    /**
     * Gets log entry from the given source; the caller is responsible for
     * calling logSource.release and handling ChecksumException.
     *
     * Is non-private for unit testing.
     *
     * @param lsn location of entry in log
     * @param lastLoggedSize is the entry size if known, or zero if unknown.
     * @param invisibleReadAllowed if true, we will permit the read of invisible
     * log entries, and we will adjust the invisible bit so that the checksum
     * will validate
     * @return log entry that embodies all the objects in the log entry
     */
    WholeEntry getLogEntryFromLogSource(long lsn,
                                        int lastLoggedSize,
                                        LogSource logSource,
                                        boolean invisibleReadAllowed)
        throws ChecksumException, ErasedException, DatabaseException {

        /*
         * Read the log entry header into a byte buffer. If the
         * lastLoggedSize is available (non-zero), we can use it to avoid a
         * repeat-read further below. Otherwise we use the configured
         * LOG_FAULT_READ_SIZE, and a repeat-read may occur if the log
         * entry is larger than the buffer.
         *
         * Even when lastLoggedSize is non-zero, we do not assume that it
         * is always accurate, because this is not currently guaranteed
         * in corner cases such as transaction aborts. We do the initial
         * read with lastLoggedSize. If lastLoggedSize is larger than the
         * actual size, we will simply read more bytes than needed. If
         * lastLoggedSize is smaller than the actual size, we will do a
         * repeat-read further below.
         */
        long fileOffset = DbLsn.getFileOffset(lsn);

        ByteBuffer entryBuffer = (lastLoggedSize > 0) ?
            logSource.getBytes(fileOffset, lastLoggedSize) :
            logSource.getBytes(fileOffset);

        if (entryBuffer.remaining() < LogEntryHeader.MIN_HEADER_SIZE) {
            throw new ChecksumException(
                "Incomplete log entry header in " + logSource +
                " needed=" + LogEntryHeader.MIN_HEADER_SIZE +
                " remaining=" + entryBuffer.remaining() +
                " lsn=" + DbLsn.getNoFormatString(lsn));
        }

        /* Read the fixed length portion of the header. */
        LogEntryHeader header = new LogEntryHeader(
            entryBuffer, logSource.getLogVersion(), lsn);

        /* Read the variable length portion of the header. */
        if (header.isVariableLength()) {
            if (entryBuffer.remaining() <
                header.getVariablePortionSize()) {
                throw new ChecksumException(
                    "Incomplete log entry header in " + logSource +
                    " needed=" + header.getVariablePortionSize() +
                    " remaining=" + entryBuffer.remaining() +
                    " lsn=" + DbLsn.getNoFormatString(lsn));
            }
            header.readVariablePortion(entryBuffer);
        }

        ChecksumValidator validator = null;
        if (header.hasChecksum() && doChecksumOnRead) {
            int itemStart = entryBuffer.position();

            /*
             * We're about to read an invisible log entry, which has knowingly
             * been left on disk with a bad checksum. Flip the invisible bit in
             * the backing byte buffer now, so the checksum will be valid. The
             * LogEntryHeader object itself still has the invisible bit set,
             * which is useful for debugging.
             */
            if (header.isInvisible()) {
                LogEntryHeader.turnOffInvisible
                    (entryBuffer, itemStart - header.getSize());
            }

            /* Add header to checksum bytes */
            validator = new ChecksumValidator(envImpl);
            int headerSizeMinusChecksum = header.getSizeMinusChecksum();
            entryBuffer.position(itemStart -
                                 headerSizeMinusChecksum);
            validator.update(entryBuffer, headerSizeMinusChecksum);
            entryBuffer.position(itemStart);
        }

        /*
         * Now that we know the size, read the rest of the entry if the first
         * read didn't get enough.
         */
        int itemSize = header.getItemSize();
        if (entryBuffer.remaining() < itemSize) {
            entryBuffer = logSource.getBytes(
                fileOffset + header.getSize(), itemSize);
            if (entryBuffer.remaining() < itemSize) {
                throw new ChecksumException(
                    "Incomplete log entry item in " + logSource +
                    " needed=" + itemSize +
                    " remaining=" + entryBuffer.remaining() +
                    " lsn=" + DbLsn.getNoFormatString(lsn));
            }
            nRepeatFaultReads.increment();
        }

        /*
         * Do entry validation. Run checksum before checking the entry type, it
         * will be the more encompassing error.
         */
        if (validator != null) {
            /* Check the checksum first. */
            validator.update(entryBuffer, itemSize);
            validator.validate(header.getChecksum(), lsn);
        }

        /*
         * If invisibleReadAllowed == false, we should not be fetching an
         * invisible log entry.
         */
        if (header.isInvisible() && !invisibleReadAllowed) {
            throw new EnvironmentFailureException
                (envImpl, EnvironmentFailureReason.LOG_INTEGRITY,
                 "Read invisible log entry at " +
                 DbLsn.getNoFormatString(lsn) + " " + header);
        }

        assert LogEntryType.isValidType(header.getType()):
            "Read non-valid log entry type: " + header.getType();

        /* Read the entry. */
        LogEntry logEntry =
            LogEntryType.findType(header.getType()).getNewLogEntry();
        logEntry.readEntry(envImpl, header, entryBuffer);

        /* For testing only; generate a read io exception. */
        if (readHook != null) {
            try {
                readHook.doIOHook();
            } catch (IOException e) {
                /* Simulate what the FileManager would do. */
                throw new EnvironmentFailureException
                    (envImpl, EnvironmentFailureReason.LOG_READ, e);
            }
        }

        /* Fetching an erased entry is handled via a checked exception. */
        if (header.isErased()) {
            throw new ErasedException(lsn, header);
        }

        return new WholeEntry(header, logEntry);
    }

    /**
     * Fault in the first object in the log entry log entry at this LSN.
     * @param lsn location of object in log
     * @return the object in the log
     */
    public Object getEntry(long lsn)
        throws FileNotFoundException, ErasedException, DatabaseException {

        LogEntry entry = getLogEntry(lsn);
        return entry.getMainItem();
    }

    public Object getEntryHandleNotFound(long lsn) {
        LogEntry entry = getLogEntryHandleNotFound(lsn);
        return entry.getMainItem();
    }

    /**
     * Find the LSN, whether in a file or still in the log buffers.
     * Is public for unit testing.
     */
    public LogSource getLogSource(long lsn)
        throws FileNotFoundException, ChecksumException, DatabaseException {

        /*
         * First look in log to see if this LSN is still in memory.
         */
        LogBuffer logBuffer = logBufferPool.getReadBufferByLsn(lsn);

        if (logBuffer == null) {
            try {
                /* Not in the in-memory log -- read it off disk. */
                long fileNum = DbLsn.getFileNumber(lsn);
                return new FileHandleSource
                    (fileManager.getFileHandle(fileNum),
                     readBufferSize, fileManager);
            } catch (DatabaseException e) {
                /* Add LSN to exception message. */
                e.addErrorMessage("lsn= " + DbLsn.getNoFormatString(lsn));
                throw e;
            }
        }
        return logBuffer;
    }

    /**
     * Reads a log entry using a FileSource, and returns null (rather than
     * throwing a ChecksumException) if the entry exists in a log buffer that
     * was not flushed.
     *
     * Used to check whether an in-memory corruption is persistent.
     *
     * @return WholeEntry null means that this lsn does not exist in the file,
     * either because the file is not found or the entry is erased.
     */
    public WholeEntry getLogEntryDirectFromFile(long lsn)
        throws ChecksumException {

        final LogSource logSource;
        try {
            logSource = getLogSource(lsn);
        } catch (FileNotFoundException fnfe) {
            return null;
        }

        final FileSource fileSource;

        if (logSource instanceof LogBuffer) {

            final FileHandle fileHandle;

            try {
                final LogBuffer logBuffer = (LogBuffer) logSource;
                final long fileLength;
                try {
                    fileHandle =
                        fileManager.getFileHandle(DbLsn.getFileNumber(lsn));
                    fileLength = fileHandle.getFile().length();
                } catch (IOException ioe) {
                    /* FileNotFound or another IOException was thrown. */
                    return null;
                }

                /*
                 * If the file does not exist (FileNotFoundException is thrown
                 * above) or the firstLsn in the buffer does not appear in the
                 * file (the buffer was not flushed), then the corruption is
                 * not persistent and later we will throw a EFE for which
                 * isCorrupted will return false.
                 */
                if (fileLength <=
                        DbLsn.getFileOffset(logBuffer.getFirstLsn())) {
                    return null;
                }
            } finally {
                logSource.release();
            }

            /*
             * The log entry should have been written to the file. Try
             * getting the log entry from the FileSource. If the log entry is
             * incomplete, ChecksumException is thrown below and later we will
             * throw an EFE with LOG_CHECKSUM.
             */
            fileSource = new FileHandleSource(
                fileHandle, readBufferSize, fileManager);
       } else {
            fileSource = (FileSource) logSource;
       }

       try {
           return getLogEntryFromLogSource(
               lsn, 0, fileSource, false /*invisibleReadAllowed*/);
       } catch (ErasedException e) {
            return null;
       } finally {
           fileSource.release();
       }
    }

    /**
     * Return a log buffer locked for reading, or null if no log buffer
     * holds this LSN location.
     */
    public LogBuffer getReadBufferByLsn(long lsn) {

        assert DbLsn.getFileOffset(lsn) != 0 :
             "Read of lsn " + DbLsn.getNoFormatString(lsn)  +
            " is illegal because file header entry is not in the log buffer";

        return logBufferPool.getReadBufferByLsn(lsn);
    }

    /**
     * Flush all log entries to the log and perform an fsync.
     */
    public void flushSync()
        throws DatabaseException {

        if (readOnly) {
            return;
        }

        /* The write queue is flushed by syncLogEnd. */
        flushInternal(false /*flushWriteQueue*/);
        fileManager.syncLogEnd();
    }

    /**
     * Flush all log entries to the log but do not fsync.
     */
    public void flushNoSync()
        throws DatabaseException {

        if (readOnly) {
            return;
        }

        flushInternal(true /*flushWriteQueue*/);
    }

    /**
     * Flush log buffers, but do not flush the write queue. This is used only
     * by FsyncManager, just prior to an fsync. When FsyncManager performs the
     * fsync, the write queue will be flushed by FileManager.fsyncLogEnd.
     */
    void flushBeforeSync()
        throws DatabaseException {

        if (readOnly) {
            return;
        }

        flushInternal(false /*flushWriteQueue*/);
    }

    /**
     * Flush the dirty log buffers, and optionally the write queue as well.
     *
     * Flushing logically means flushing all write buffers to the file system,
     * so flushWriteQueue should be false only when this method is called just
     * before an fsync (FileManager.syncLogEnd will flush the write queue).
     */
    private void flushInternal(boolean flushWriteQueue)
        throws DatabaseException {

        assert !readOnly;

        /*
         * If we cannot bump the current buffer because there are no
         * free buffers, the only recourse is to write all buffers
         * under the LWL.
         */
        synchronized (logWriteMutex) {
            if (!logBufferPool.bumpCurrent(0)) {
                logBufferPool.bumpAndWriteDirty(0, flushWriteQueue);
                return;
            }
        }

        /*
         * We bumped the current buffer but did not write any buffers above.
         * Write the dirty buffers now.  Hopefully this is the common case.
         */
        logBufferPool.writeDirty(flushWriteQueue);
    }

    public StatGroup loadStats(StatsConfig config)
        throws DatabaseException {

        endOfLog.set(fileManager.getLastUsedLsn());

        StatGroup copyStats = stats.cloneGroup(config.getClear());
        copyStats.addAll(logBufferPool.loadStats(config));
        copyStats.addAll(fileManager.loadStats(config));
        copyStats.addAll(grpManager.loadStats(config));

        return copyStats;
    }

    /**
     * Return the current number of cache misses in a lightweight fashion,
     * without incurring the cost of loading all the stats, and without
     * clearing any stats.
     */
    public long getNCacheMiss() {
        return logBufferPool.getNCacheMiss();
    }

    /**
     * For unit testing.
     */
    StatGroup getBufferPoolLatchStats() {
        return logBufferPool.getBufferPoolLatchStats();
    }

    /**
     * Returns a tracked summary for the given file which will not be flushed.
     */
    public TrackedFileSummary getUnflushableTrackedSummary(long file) {
        synchronized (logWriteMutex) {
            return envImpl.getUtilizationTracker().
                    getUnflushableTrackedSummary(file);
        }
    }

    /**
     * Removes the tracked summary for the given file.
     */
    public void removeTrackedFile(TrackedFileSummary tfs) {
        synchronized (logWriteMutex) {
            tfs.reset();
        }
    }

    private void updateObsolete(
        LogParams params,
        UtilizationTracker tracker) {

        if (params.packedObsoleteInfo == null &&
            params.obsoleteWriteLockInfo == null) {
            return;
        }

        synchronized (logWriteMutex) {

            /* Count other obsolete info under the log write latch. */
            if (params.packedObsoleteInfo != null) {
                params.packedObsoleteInfo.countObsoleteInfo(tracker);
            }

            if (params.obsoleteWriteLockInfo != null) {
                for (WriteLockInfo info : params.obsoleteWriteLockInfo) {
                    tracker.countObsoleteNode(info.getAbortLsn(),
                                              null /*type*/,
                                              info.getAbortLogSize());
                }
            }
        }
    }

    /**
     * Count node as obsolete under the log write latch.  This is done here
     * because the log write latch is managed here, and all utilization
     * counting must be performed under the log write latch.
     */
    public void countObsoleteNode(long lsn,
                                  LogEntryType type,
                                  int size,
                                  boolean countExact) {
        synchronized (logWriteMutex) {
            UtilizationTracker tracker = envImpl.getUtilizationTracker();
            if (countExact) {
                tracker.countObsoleteNode(lsn, type, size);
            } else {
                tracker.countObsoleteNodeInexact(lsn, type, size);
            }
        }
    }

    /**
     * A flavor of countObsoleteNode which does not fire an assert if the
     * offset has already been counted. Called through the LogManager so that
     * this incidence of all utilization counting can be performed under the
     * log write latch.
     */
    public void countObsoleteNodeDupsAllowed(long lsn,
                                              LogEntryType type,
                                              int size) {
        synchronized (logWriteMutex) {
            UtilizationTracker tracker = envImpl.getUtilizationTracker();
            tracker.countObsoleteNodeDupsAllowed(lsn, type, size);
        }
    }

    /**
     * @see LocalUtilizationTracker#transferToUtilizationTracker
     */
    public void transferToUtilizationTracker(LocalUtilizationTracker
                                             localTracker)
        throws DatabaseException {
        synchronized (logWriteMutex) {
            UtilizationTracker tracker = envImpl.getUtilizationTracker();
            localTracker.transferToUtilizationTracker(tracker);
        }
    }

    public void incRepeatIteratorReads() {
        nRepeatIteratorReads.increment();
    }

    /* For unit testing only. */
    public void setReadHook(TestHook hook) {
        readHook = hook;
    }

    /* For unit testing only. */
    public void setDelayVLSNRegisterHook(TestHook hook) {
        delayVLSNRegisterHook = hook;
    }

    /* For unit testing only. */
    public void setFlushLogHook(TestHook hook) {
        flushHook = hook;
        grpManager.setFlushLogHook(hook);
    }

    private class LogWriteInfo {
        final LogBufferSegment lbs;
        final VLSN vlsn;
        final long fileOffset;

        LogWriteInfo(final LogBufferSegment bs,
                     final VLSN vlsn,
                     final long fileOffset) {
            lbs = bs;
            this.vlsn = vlsn;
            this.fileOffset = fileOffset;
        }
    }
}