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

com.sleepycat.je.log.FileReader 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 java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.util.logging.Logger;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
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.utilint.DbLsn;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.VLSN;

/**
 * A FileReader is an abstract class that traverses the log files, reading-in
 * chunks of the file at a time. The class provides a iterator interface, via
 * its readNextEntry() method. Concrete subclasses must (a) provide the public
 * methods that allow their users to examine the contents of the logrec that
 * the iterator is currently positioned at, and (b) implement the (non-public)
 * isTarget() and processEntry() methods that may filter-out logrecs that do
 * not need to be seen by the caller of readNextEntry(), or perform specific
 * actions before each logrec is "returned" to the caller of readNextEntry().
 */
public abstract class FileReader {

    protected final EnvironmentImpl envImpl;

    protected final FileManager fileManager;

    /*
     * The ReadWindow is a data buffer that acts as a sliding window view
     * of the log. It is positioned against the log and filled up with data.
     */
    protected final ReadWindow window;

    /* 
     * For piecing together a log entry that is read from multiple read buffer
     * calls.
     */
    private ByteBuffer saveBuffer;   

    private final boolean singleFile;// if true, do not read across files

    /*
     * true if at end of the log.
     * TODO: assess whether this is redundant  with the EOFException, and
     * could be streamlined.
     */
    protected boolean eof;

    /* if true, we're reading forward; otherwise backwards */
    protected final boolean forward;   

    /* num entries we've seen */
    private int nRead;

    /* The log entry header for the entry that was just read. */
    protected LogEntryHeader currentEntryHeader;

    /*
     * The log entry before the current entry. In general,
     * currentEntryPrevOffset is the same as
     * currentEntryHeader.getPrevOffset(), but it's initialized and used before
     * a header is read. Only used for backward scanning.
     */
    protected long currentEntryPrevOffset;

    /*
     * nextEntryOffset is used to set the currentEntryOffset after we've read
     * an entry. Only used for forward scanning.
     */
    protected long currentEntryOffset;
    protected long nextEntryOffset;

    protected long startLsn;  // We start reading from this LSN.
    private final long finishLsn; // If going backwards, read up to this LSN.

    /* For checking checksum on the read. */
    protected ChecksumValidator cksumValidator;

    private boolean doChecksumOnRead;       // Validate checksums
    private boolean alwaysValidateChecksum; // Validate for all entry types

    protected final Logger logger;

    public FileReader(EnvironmentImpl envImpl,
                      int readBufferSize,
                      boolean forward,
                      long startLsn,
                      Long singleFileNumber,
                      long endOfFileLsn,
                      long finishLsn)
        throws DatabaseException {

        this(envImpl, readBufferSize, forward,
             startLsn, singleFileNumber, endOfFileLsn, finishLsn,
             envImpl.getLogManager().getChecksumOnRead());
    }

    public FileReader(EnvironmentImpl envImpl,
                      int readBufferSize,
                      boolean forward,
                      long startLsn,
                      Long singleFileNumber,
                      long endOfFileLsn,
                      long finishLsn,
                      boolean doChecksumOnRead)
        throws DatabaseException {

        this.envImpl = envImpl;
        this.fileManager = envImpl.getFileManager();
        this.singleFile = (singleFileNumber != null);
        this.forward = forward;

        this.doChecksumOnRead = doChecksumOnRead;
        if (this.doChecksumOnRead) {
            cksumValidator = new ChecksumValidator(envImpl);
        }

        window = makeWindow(readBufferSize);
        saveBuffer = ByteBuffer.allocate(readBufferSize);

        /* stats */
        nRead = 0;

        /* Determine the starting position. */
        this.startLsn = startLsn;
        this.finishLsn = finishLsn;

        logger = envImpl.getLogger();

        initStartingPosition(endOfFileLsn, singleFileNumber);
    }

    /**
     * May be overridden by other FileReaders.
     * @throws DatabaseException 
     */
    protected ReadWindow makeWindow(int readBufferSize) 
        throws DatabaseException {
        
        return new ReadWindow(readBufferSize, envImpl);
    }

    /**
     * Helper for determining the starting position and opening up a file at
     * the desired location.
     */
    protected void initStartingPosition(long endOfFileLsn,
                                        Long ignoreSingleFileNumber) {
        eof = false;
        if (forward) {

            /*
             * Start off at the startLsn. If that's null, start at the
             * beginning of the log. If there are no log files, set eof.
             */
            if (startLsn != DbLsn.NULL_LSN) {
                window.initAtFileStart(startLsn);
            } else {
                Long firstNum = fileManager.getFirstFileNum();
                if (firstNum == null) {
                    eof = true;
                } else {
                    window.initAtFileStart(DbLsn.makeLsn(firstNum, 0));
                }
            }

            /*
             * After we read the first entry, the currentEntry will point here.
             */
            nextEntryOffset = window.getEndOffset();
        } else {

            /*
             * Make the read buffer look like it's positioned off the end of
             * the file. Initialize the first LSN we want to read. When
             * traversing the log backwards, we always start at the very end.
             */
            assert startLsn != DbLsn.NULL_LSN;
            window.initAtFileStart(endOfFileLsn);

            /*
             * currentEntryPrevOffset points to the entry we want to start out
             * reading when going backwards. If it's 0, the entry we want to
             * read is in a different file.
             */
            if (DbLsn.getFileNumber(startLsn) ==
                DbLsn.getFileNumber(endOfFileLsn)) {
                currentEntryPrevOffset = DbLsn.getFileOffset(startLsn);
            } else {
                currentEntryPrevOffset = 0;
            }
            currentEntryOffset = DbLsn.getFileOffset(endOfFileLsn);
        }
    }

    /**
     * Whether to always validate the checksum, even for non-target entries.
     */
    public void setAlwaysValidateChecksum(boolean validate) {
        alwaysValidateChecksum = validate;
    }

    /**
     * @return the number of entries processed by this reader.
     */
    public int getNumRead() {
        return nRead;
    }

    public long getNRepeatIteratorReads() {
        return window.getNRepeatIteratorReads();
    }

    /**
     * Get LSN of the last entry read.
     */
    public long getLastLsn() {
        return DbLsn.makeLsn(window.currentFileNum(), currentEntryOffset);
    }

    /**
     * Returns the VLSN of the last entry read, or null if the entry has no
     * VLSN (not replicated and has no migrated VLSN).
     */
    public VLSN getLastVlsn() {
        return currentEntryHeader.getVLSN();
    }

    /**
     * Returns the total size (including header) of the last entry read.
     */
    public int getLastEntrySize() {
        return currentEntryHeader.getEntrySize();
    }

    /**
     * Returns the log version of the last entry read.
     */
    public int getLogVersion() {
        return currentEntryHeader.getVersion();
    }

    /**
     * Scans the log files until either it has reached the end of the log or
     * has hit an invalid portion.
     *
     * @return true if an element has been read, false at end-of-log.
     *
     * @throws EnvironmentFailureException if a ChecksumException,
     * FileNotFoundException, or another internal problem occurs.
     */
    public boolean readNextEntry() {
        try {
            return readNextEntryAllowExceptions();
        } catch (FileNotFoundException e) {
            throw new EnvironmentFailureException(
                envImpl, EnvironmentFailureReason.LOG_FILE_NOT_FOUND, e);
        } catch (ChecksumException e) {
            throw new EnvironmentFailureException(
                envImpl, EnvironmentFailureReason.LOG_CHECKSUM, e);
        }
    }

    /**
     * Variant of readNextEntry that throws FileNotFoundException and
     * ChecksumException, rather than wrapping them in an
     * EnvironmentFailureException and invalidating the enviornment.  This
     * allows users of this class (see cleaner.FileProcessor), and subclasses
     * that override readNextEntry (see ScavengerFileReader and
     * LastFileReader), to handle these exceptions specially.
     */
    public final boolean readNextEntryAllowExceptions()
        throws FileNotFoundException, ChecksumException {

        boolean foundEntry = false;
        long savedCurrentEntryOffset = currentEntryOffset;
        long savedNextEntryOffset = nextEntryOffset;

        try {
            while ((!eof) && (!foundEntry)) {

                /* Read the invariant portion of the next header. */
                getLogEntryInReadBuffer();
                ByteBuffer dataBuffer =
                    readData(LogEntryHeader.MIN_HEADER_SIZE,
                             true); // collectData

                readBasicHeader(dataBuffer);

                boolean isTarget;
                boolean isChecksumTarget;
                boolean collectData;

                if (currentEntryHeader.isVariableLength()) {

                    /*
                     * For all variable length entries, init the checksum w/the
                     * invariant portion of the header, before we know whether
                     * the entry is a target for this reader.  This has
                     * to be done before we read the variable portion of the
                     * header, because readData() only guarantees that it
                     * returns a dataBuffer that contains the next bytes that
                     * are needed, and has no guarantee that it holds any bytes
                     * that were previously read. The act of calling
                     * readData() to obtain the optional portion may reset the
                     * dataBuffer, and nudge the invariant part of the header
                     * out of the buffer returned by readData()
                     */
                    if (currentEntryHeader.hasChecksum()) {
                        startChecksum(dataBuffer);
                    }

                    int optionalPortionLen =
                        currentEntryHeader.getVariablePortionSize();

                    /* Load the optional part of the header into a buffer. */
                    dataBuffer = readData(optionalPortionLen, true);

                    /*
                     * Add to checksum while the buffer is positioned at
                     * the start of the new bytes.
                     */
                    if (currentEntryHeader.hasChecksum()) {
                        addToChecksum(dataBuffer, optionalPortionLen);
                    }

                    /* Now read the optional bytes. */
                    currentEntryHeader.readVariablePortion(dataBuffer);
                }

                /*
                 * We've read the header of the next logrec. Move up our
                 * offsets if we're moving forward. If we're moving
                 * backwards, we set our offset before we read the header,
                 * because we knew where the entry started.
                 */
                if (forward) {
                    currentEntryOffset = nextEntryOffset;
                    nextEntryOffset +=
                        currentEntryHeader.getSize() +    // header size
                        currentEntryHeader.getItemSize(); // item size
                }
                
                try {
                    isTarget = isTargetEntry();

                    isChecksumTarget = currentEntryHeader.hasChecksum() &&
                        (isTarget || alwaysValidateChecksum);

                    if (!currentEntryHeader.isVariableLength()) {
                        startChecksum(dataBuffer, isChecksumTarget);
                    }

                    collectData =
                        (isChecksumTarget && doChecksumOnRead) || isTarget;

                    /*
                     * Read in the body of the next entry. Note that even if
                     * this isn't a targeted entry, we have to move the buffer
                     * position along.
                     */
                    dataBuffer = readData(currentEntryHeader.getItemSize(),
                                          collectData);
                } catch (Throwable e) {
                    if (forward) {
                        currentEntryOffset = savedCurrentEntryOffset;
                        nextEntryOffset = savedNextEntryOffset;
                    }
                    throw e;
                }

                /* Validate the log entry checksum. */
                validateChecksum(dataBuffer, isChecksumTarget);
                
                if (isTarget) {

                    /*
                     * For a target entry, call the subclass reader's
                     * processEntry method to do whatever we need with the
                     * entry.  It returns true if this entry is one that should
                     * be returned.  Note that some entries, although targeted
                     * and read, are not returned.
                     */
                    if (processEntry(dataBuffer)) {
                        foundEntry = true;
                        nRead++;
                    }
                } else if (collectData) {

                    /*
                     * For a non-target entry that was validated, the buffer is
                     * positioned at the start of the entry; skip over it.
                     */
                    skipEntry(dataBuffer);
                }
            }
        } catch (EOFException e) {
            eof = true;
        } catch (DatabaseException e) {
            eof = true;
            /* Report on error. */
            reportProblem(e);
            throw e;
        }

        return foundEntry;
    }

    /**
     * May be called by processEntry when it determines that the entry does not
     * need to be read/de-serialized.
     */
    protected void skipEntry(ByteBuffer entryBuffer) {
        entryBuffer.position(
            entryBuffer.position() +
            currentEntryHeader.getItemSize());
    }

    private void reportProblem(Exception e) {

        StringBuilder sb = new StringBuilder();
        sb.append("Halted log file reading at file 0x").
            append(Long.toHexString(window.currentFileNum())).
            append(" offset 0x").
            append(Long.toHexString(nextEntryOffset)).
            append(" offset(decimal)=").
            append(nextEntryOffset).
            append(" prev=0x").
            append(Long.toHexString(currentEntryPrevOffset));

            if (currentEntryHeader != null) {
                LogEntryType problemType =
                    LogEntryType.findType(currentEntryHeader.getType());
            sb.append(":\nentry=").
                append(problemType).
                append("type=").
                append(currentEntryHeader.getType()).
                append(",version=").
                append(currentEntryHeader.getVersion()).
                append(")\nprev=0x").
                append(Long.toHexString(currentEntryPrevOffset)).
                append("\nsize=").
                append(currentEntryHeader.getItemSize()).
                append("\nNext entry should be at 0x").
                append(Long.toHexString(nextEntryOffset +
                                               currentEntryHeader.getSize() +
                                        currentEntryHeader.getItemSize()));
        }

        LoggerUtils.traceAndLogException
            (envImpl, "FileReader", "readNextEntry", sb.toString(), e);
    }

    /**
     * Make sure that the start of the target log entry is in the header. 
     */
    private void getLogEntryInReadBuffer()
        throws ChecksumException,
               EOFException,
               FileNotFoundException,
               DatabaseException {

        /*
         * If we're going forward, because we read every byte sequentially,
         * we're always sure the read buffer is positioned at the right spot.
         * If we go backwards, we need to jump the buffer position. These
         * methods may be overridden by subclasses.
         */
        if (forward) {
            setForwardPosition();
        } else {
            setBackwardPosition();
        }
    }

    /**
     * Ensure that the next target is in the window. The default behavior is
     * that the next target is the next, following entry, so we can assume that
     * it's in the window.  All we have to do is to check if we've gone past
     * the specified end point.
     * @throws DatabaseException 
     * @throws FileNotFoundException 
     * @throws ChecksumException 
     */
    protected void setForwardPosition() 
        throws EOFException,
               DatabaseException, 
               ChecksumException, 
               FileNotFoundException {

        if (finishLsn != DbLsn.NULL_LSN) {
            /* The next log entry has passed the end LSN. */
            long nextLsn = DbLsn.makeLsn(window.currentFileNum(),
                                         nextEntryOffset);
            if (DbLsn.compareTo(nextLsn, finishLsn) >= 0) {
                throw new EOFException();
            }
        }
    }

    /**
     * Ensure that the next target is in the window. The default behavior is
     * that the next target is the next previous entry.
     * @throws DatabaseException 
     */
    protected void setBackwardPosition() 
        throws ChecksumException,
               FileNotFoundException,
               EOFException,
               DatabaseException {

        /*
         * currentEntryPrevOffset is the entry before the current entry.
         * currentEntryOffset is the entry we just read (or the end of the
         * file if we're starting out.
         */
        if ((currentEntryPrevOffset != 0) &&
            window.containsOffset(currentEntryPrevOffset)) {

            /* The next log entry has passed the start LSN. */
            long nextLsn = DbLsn.makeLsn(window.currentFileNum(),
                                         currentEntryPrevOffset);
            if (finishLsn != DbLsn.NULL_LSN) {
                if (DbLsn.compareTo(nextLsn, finishLsn) == -1) {
                    throw new EOFException("finish=" + 
                                           DbLsn.getNoFormatString(finishLsn) +
                                           "next=" + 
                                           DbLsn.getNoFormatString(nextLsn));
                }
            }

            /* This log entry starts in this buffer, just reposition. */
            window.positionBuffer(currentEntryPrevOffset);
        } else {

            /*
             * The start of the log entry is not in this read buffer so
             * we must fill the buffer again. 
             *
             * 1) The target log entry is in a different file from the
             * current window's file. Move the window to the previous
             * file and start the read from the target LSN.
             *
             * 2) The target log entry is the same file but the log entry
             * is larger than the read chunk size. Start the next read
             * buffer from the target LSN. It's going to take multiple
             * reads to get the log entry, and we might as well get as
             * much as possible.
             *
             * 3) In the same file, and the log entry fits within one
             * read buffer. Try to position the next buffer chunk so the
             * target entry is held within the buffer, all the way at the
             * end. That way, since we're reading backwards, there will be
             * more buffered data available for following reads.
             */
            long nextFile;
            long nextWindowStart;
            long nextTarget;

            if (currentEntryPrevOffset == 0) {
                /* Case 1: Go to another file. */
                currentEntryPrevOffset = fileManager.getFileHeaderPrevOffset
                    (window.currentFileNum());

                Long prevFileNum =
                    fileManager.getFollowingFileNum(window.currentFileNum(),
                                                    false);
                if (prevFileNum == null) {
                    throw new EOFException("No file following " +
                                           window.currentFileNum());
                }

                /*
                 *  Check finishLSN  before proceeding, in case we should stop
                 *  the search before attempting to set the file reader to a
                 *  position in the previous file. In  [#22407] we threw a
                 *  spurious EFE complaining that we cannot read backwards over
                 *  a cleaned file because the previous file had  been cleaned
                 *  away.
                 */
                if (finishLsn != DbLsn.NULL_LSN &&
                    prevFileNum < DbLsn.getFileNumber(finishLsn)) {
                    throw new EOFException(
                        "finish=" + DbLsn.getNoFormatString(finishLsn) +
                        " nextFile=0x" + Long.toHexString(prevFileNum));
                }

                if (window.currentFileNum() - prevFileNum.longValue() != 1) {
                    handleGapInBackwardsScan(prevFileNum);
                }

                nextFile = prevFileNum;
                nextWindowStart = currentEntryPrevOffset;
                nextTarget = currentEntryPrevOffset;
            } else if ((currentEntryOffset - currentEntryPrevOffset) >
                       window.capacity()) {

                /*
                 * Case 2: The entry is in the same file, but is bigger
                 * than one buffer. Position it at the front of the buffer.
                 */
                nextFile = window.currentFileNum();
                nextWindowStart = currentEntryPrevOffset;
                nextTarget = currentEntryPrevOffset;
            } else {

                /* 
                 * Case 3: In same file, but not in this buffer. The target
                 * entry will fit in one buffer.
                 */
                nextFile = window.currentFileNum();
                long newPosition = currentEntryOffset -
                    window.capacity();
                nextWindowStart = (newPosition < 0) ? 0 : newPosition;
                nextTarget = currentEntryPrevOffset;
            }

            /* The next log entry has passed the start LSN. */
            long nextLsn = DbLsn.makeLsn(nextFile,
                                         currentEntryPrevOffset);
            if (finishLsn != DbLsn.NULL_LSN) {
                if (DbLsn.compareTo(nextLsn, finishLsn) == -1) {
                    throw new EOFException("finish=" + 
                                           DbLsn.getNoFormatString(finishLsn) +
                                           " next=" +
                                           DbLsn.getNoFormatString(nextLsn));
                }
            }

            window.slideAndFill
                (nextFile, nextWindowStart, nextTarget, forward);
        }

        /* The current entry will start at this offset. */
        currentEntryOffset = currentEntryPrevOffset;
    }

    /**
     * Read the basic log entry header, leaving the buffer mark at the
     * beginning of the checksummed header data.
     */
    private void readBasicHeader(ByteBuffer dataBuffer)
        throws ChecksumException, DatabaseException  {

        /* Read the header for this entry. */
        currentEntryHeader = new LogEntryHeader(
            dataBuffer, window.logVersion, window.getCurrentLsn());

        /*
         * currentEntryPrevOffset is a separate field, and is not obtained
         * directly from the currentEntryHeader, because it is initialized and
         * used before any log entry was read.
         */
        currentEntryPrevOffset = currentEntryHeader.getPrevOffset();
    }

    /**
     * Reset the checksum validator and add the new header bytes. Assumes that
     * the data buffer is positioned just past the end of the invariant
     * portion of the log entry header.
     * @throws DatabaseException
     */
    private void startChecksum(ByteBuffer dataBuffer) 
        throws ChecksumException {

        startChecksum(dataBuffer, true  /* isChecksumTarget */);
    }

    private void startChecksum(ByteBuffer dataBuffer,
                               boolean isChecksumTarget)
        throws ChecksumException {

        if (!doChecksumOnRead) {
            return;
        }

        if (!isChecksumTarget) {
            return;
        }

        /* Clear out any previous data. */
        cksumValidator.reset();

        int originalPosition = dataBuffer.position();
        if (currentEntryHeader.isInvisible()) {

            /* 
             * Turn off invisibility so that the checksum will succeed. When
             * entries are made invisible, the checksum is not adjusted. Note
             * that the dataBuffer can leave the invisible bit transformed,
             * because the header has already been initialized, and this data
             * will never be read again.
             */
            LogEntryHeader.turnOffInvisible(dataBuffer, originalPosition -
                                            LogEntryHeader.MIN_HEADER_SIZE);
        }

        /* Position the buffer at the start of the data, after the checksum. */
        int headerSizeMinusChecksum =
            currentEntryHeader.getInvariantSizeMinusChecksum();
        int entryTypeStart = originalPosition - headerSizeMinusChecksum;
        dataBuffer.position(entryTypeStart);

        /* Load the validate with the header bytes. */
        cksumValidator.update(dataBuffer, headerSizeMinusChecksum);

        /* Move the data buffer back to the original position. */
        dataBuffer.position(originalPosition);
    }

    private void addToChecksum(ByteBuffer dataBuffer, int length) 
        throws ChecksumException {

        if (!doChecksumOnRead) {
            return;
        }

        cksumValidator.update(dataBuffer, length);
    }

    /**
     * Add the entry bytes to the checksum and check the value.  This method
     * must be called with the buffer positioned at the start of the entry.
     */
    private void validateChecksum(ByteBuffer dataBuffer, 
                                  boolean isChecksumTarget)
        throws ChecksumException {

        if (!doChecksumOnRead) {
            return;
        }

        if (!isChecksumTarget) {
            return;
        }

        cksumValidator.update(dataBuffer, currentEntryHeader.getItemSize());
        cksumValidator.validate(currentEntryHeader.getChecksum(),
                                window.currentFileNum(),
                                currentEntryOffset);
    }

    /**
     * Try to read a specified number of bytes.
     * @param amountToRead is the number of bytes we need
     * @param collectData is true if we need to actually look at the data.
     *  If false, we know we're skipping this entry, and all we need to
     *  do is to count until we get to the right spot.
     * @return a byte buffer positioned at the head of the desired portion,
     * or null if we reached eof.
     */
    private ByteBuffer readData(int amountToRead, boolean collectData)
        throws ChecksumException,
               EOFException,
               FileNotFoundException,
               DatabaseException {

        int alreadyRead = 0;
        ByteBuffer completeBuffer = null;
        saveBuffer.clear();

        while ((alreadyRead < amountToRead) && !eof) {

            int bytesNeeded = amountToRead - alreadyRead;
            if (window.hasRemaining()) {

                /* There's data in the window, process it. */
                if (collectData) {

                    /*
                     * Save data in a buffer for processing.
                     */
                    if ((alreadyRead > 0) ||
                        (window.remaining() < bytesNeeded)) {

                        /* We need to piece an entry together. */
                        copyToSaveBuffer(bytesNeeded);
                        alreadyRead = saveBuffer.position();
                        completeBuffer = saveBuffer;
                    } else {

                        /* A complete entry is available in this buffer. */
                        completeBuffer = window.getBuffer();
                        alreadyRead = amountToRead;
                    }
                } else {

                    /*
                     * We're not processing the data, so need to save it. just
                     * move buffer positions.
                     */
                    int positionIncrement =
                        (window.remaining() > bytesNeeded) ?
                        bytesNeeded : window.remaining();

                    alreadyRead += positionIncrement;
                    window.incrementBufferPosition(positionIncrement);
                    completeBuffer = window.getBuffer();
                }
            } else {

                /*
                 * Look for more data.
                 */
                if (window.fillNext(singleFile, bytesNeeded)) {
                    /* This call to fillNext slid the window to a new file. */
                    nextEntryOffset = 0;
                }
            }
        }

        /* Flip the save buffer just in case we've been accumulating in it. */
        saveBuffer.flip();

        return completeBuffer;
    }
    
    /* Try to skip over a specified number of bytes. */
    public void skipData(int amountToSkip) 
        throws ChecksumException,
               EOFException,
               FileNotFoundException,
               DatabaseException {
               
        try {
            readData(amountToSkip, false);
        } catch (DatabaseException e) {
            reportProblem(e);
            throw e;
        }    
    }

    /**
     * Copy the required number of bytes into the save buffer.
     */
    private void copyToSaveBuffer(int bytesNeeded) {
        /* How much can we get from this current read buffer? */
        int bytesFromThisBuffer;

        if (bytesNeeded <= window.remaining()) {
            bytesFromThisBuffer = bytesNeeded;
        } else {
            bytesFromThisBuffer = window.remaining();
        }

        /* Gather it all into this save buffer. */
        ByteBuffer temp;

        /* Make sure the save buffer is big enough. */
        if (saveBuffer.capacity() - saveBuffer.position() <
            bytesFromThisBuffer) {
            /* Grow the save buffer. */
            temp = ByteBuffer.allocate(saveBuffer.capacity() +
                                       bytesFromThisBuffer);
            saveBuffer.flip();
            temp.put(saveBuffer);
            saveBuffer = temp;
        }

        /*
         * Bulk copy only the required section from the read buffer into the
         * save buffer. We need from readBuffer.position() to
         * readBuffer.position() + bytesFromThisBuffer
         */
        temp = window.getBuffer().slice();
        temp.limit(bytesFromThisBuffer);
        saveBuffer.put(temp);
        window.incrementBufferPosition(bytesFromThisBuffer);
    }

    /**
     * Returns the number of reads since the last time this method was called.
     */
    public int getAndResetNReads() {
        return window.getAndResetNReads();
    }

    /**
     * This method is called by readNextEntry() after the header of the current
     * logrec has been de-serialized, but not the body. Based on header info
     * only, it may perform some actions and then decide whether the rest of
     * the logrec should be de-serialized or just skipped.
     * 
     * @return true if this reader should process the current logrec further,
     * via the processEntry() method. A logrec must be passed to processEntry
     * if the full logrec (not just the header) must be de-serialized for
     * further processing. Return false if no further processing is needed,
     * in which case the current logrec will be skipped (i.e, not returned
     * to the caller of readNextEntry().
     *
     * @throws DatabaseException from subclasses.
     */
    protected boolean isTargetEntry()
        throws DatabaseException {

        return true;
    }

    /**
     * Each file reader implements this method to process the entry data.
     *
     * @param entryBuffer A ByteBuffer that the logrec data and is positioned
     * at the start of the logrec body (i.e., just after the logrec header).
     *
     * @return true if this entry should be returned to the caller of
     * readNextEntry().
     */
    protected abstract boolean processEntry(ByteBuffer entryBuffer)
        throws DatabaseException;

    /**
     * Never seen by user, used to indicate that the file reader should stop.
     */
    @SuppressWarnings("serial")
    public static class EOFException extends Exception {
        public EOFException() {
            super();
        }

        /* 
         * @param message The message is used to hold debugging 
         * information.
         */
        public EOFException(String message) {
            super(message);
        }
    }

    /**
     * @return true if the current entry is part of replication stream.
     */
    public boolean entryIsReplicated() {

        if (currentEntryHeader == null) {
            throw EnvironmentFailureException.unexpectedState
                ("entryIsReplicated should not be used before reader is " +
                 "initialized");
        } 
        return currentEntryHeader.getReplicated();
    }

    /**
     * TBW
     */
    protected void handleGapInBackwardsScan(long prevFileNum) {
        throw new EnvironmentFailureException
            (envImpl,
             EnvironmentFailureReason.LOG_INTEGRITY,
             "Cannot read backward over cleaned file" +
             " from 0x" + Long.toHexString(window.currentFileNum()) +
             " to 0x" + Long.toHexString(prevFileNum));
    }

    /**
     * A ReadWindow provides a swath of data read from the JE log. 
     */
    protected static class ReadWindow {

        /*
         * fileNum, startOffset and endOffset indicate how the read buffer maps
         * to the JE log. For example, if the read buffer size is 200 and the
         * read buffer was filled from file 9, starting at byte 100, then:
         *          fileNum = 9
         *          startOffset = 100
         *          endOffset = 300
         * Note that the end point is not inclusive; endOffset is > the
         * readBuffer's end.
         */
        private long fileNum;      // file number we're pointing to
        private int logVersion;    // log version for fileNum/readBuffer
        protected long startOffset;// file offset that maps to buf start
        protected long endOffset;  // file offset that maps to buf end
        protected ByteBuffer readBuffer;   // buffer for reading from the file

        /* read buffer can't grow larger than this */
        private final int maxReadBufferSize;   

        protected final EnvironmentImpl envImpl;
        protected final FileManager fileManager;

        /*
         * The number of times we've tried to read in a log entry that was too
         * large for the read buffer.
         */
        private long nRepeatIteratorReads;

        /* Number of reads since the last time getAndResetNReads was called. */
        private int nReadOperations;

        protected ReadWindow(int readBufferSize, EnvironmentImpl envImpl) {
            DbConfigManager configManager = envImpl.getConfigManager();
            maxReadBufferSize =
                configManager.getInt(EnvironmentParams.LOG_ITERATOR_MAX_SIZE);
            this.envImpl = envImpl;
            fileManager = envImpl.getFileManager();

            readBuffer = ByteBuffer.allocate(readBufferSize);
            readBuffer.flip();
        }

        /* 
         * Position this window at this LSN, but leave it empty, it has no data
         * yet.
         */
        public void initAtFileStart(long startLsn) {
            setFileNum(DbLsn.getFileNumber(startLsn),
                       LogEntryType.UNKNOWN_FILE_HEADER_VERSION);
            startOffset = DbLsn.getFileOffset(startLsn);
            endOffset = startOffset;
        }

        public long getEndOffset() {
            return endOffset;
        }        

        /**
         * Ensure that whenever we change the fileNum, the logVersion is also
         * updated.  The fileNum and logVersion fields should be kept private.
         */
        protected void setFileNum(final long fileNum, final int logVersion) {
            this.fileNum = fileNum;
            this.logVersion = logVersion;
        }

        public long currentFileNum() {
            return fileNum;
        }

        /* Return true if this offset is contained with the readBuffer. */
        boolean containsOffset(long targetOffset) {
            return (targetOffset >= startOffset) &&
                (targetOffset < endOffset);
        }

        /* Return true if this lsn  is contained with the readBuffer. */
        public boolean containsLsn(long targetFileNumber, long targetOffset) {
            return ((fileNum == targetFileNumber) &&
                    containsOffset(targetOffset));
        }

        /* Position the readBuffer to the targetOffset. */
        public void positionBuffer(long targetOffset) {

            assert containsOffset(targetOffset) : this + " doesn't contain " +
                DbLsn.getNoFormatString(targetOffset);

            readBuffer.position((int) (targetOffset - startOffset));
        }

        /* Move the readBuffer position up by the given increment. */
        void incrementBufferPosition(int increment) {
            int currentPosition = readBuffer.position();
            readBuffer.position(currentPosition + increment);
        }

        /* 
         * Reposition to the specified file, and fill starting at
         * startOffset. Position the window's buffer to point at the log entry
         * indicated by targetOffset
         */
        public void slideAndFill(long windowfileNum, 
                                 long windowStartOffset, 
                                 long targetOffset,
                                 boolean forward)
            throws ChecksumException,
                   FileNotFoundException,
                   DatabaseException {

            FileHandle fileHandle = fileManager.getFileHandle(windowfileNum);
            try {
                startOffset = windowStartOffset;
                setFileNum(windowfileNum, fileHandle.getLogVersion());
                boolean foundData = fillFromFile(fileHandle, targetOffset);

                /*
                 * When reading backwards, we need to guarantee there is no log
                 * gap, throws out an EnvironmentFailreException if it exists.
                 */
                if (!foundData && !forward) {
                    throw EnvironmentFailureException.unexpectedState
                        ("Detected a log file gap when reading backwards. " + 
                         "Target position = " + DbLsn.getNoFormatString
                         (DbLsn.makeLsn(windowfileNum, targetOffset)) +
                         " starting position = " + DbLsn.getNoFormatString
                         (DbLsn.makeLsn(windowfileNum, windowStartOffset)) +
                         " end position = " + DbLsn.getNoFormatString
                         (DbLsn.makeLsn(windowfileNum, endOffset)));
                }
            } finally {
                fileHandle.release();
            }
        }

        /**
         * Fill up the read buffer with more data, moving along to the
         * following file (next largest number) if needed.
         * @return true if the fill moved us to a new file.
         */
        protected boolean fillNext(boolean singleFile, int bytesNeeded)
            throws ChecksumException,
                   FileNotFoundException,
                   EOFException,
                   DatabaseException {

            adjustReadBufferSize(bytesNeeded);

            FileHandle fileHandle = null;
            try {
                /* Get a file handle to read in more log. */
                fileHandle = fileManager.getFileHandle(fileNum);

                /*
                 * Check to see if we've come to the end of the file.  If so,
                 * get the next file.
                 */
                startOffset = endOffset;
                if (fillFromFile(fileHandle, startOffset)) {
                    /* 
                     * Successfully filled the read buffer, but didn't move to 
                     * a new file. 
                     */
                    return false; 
                }

                /* This file is done -- can we read in the next file? */
                if (singleFile) {
                    throw new EOFException("Single file only");
                }

                Long nextFile = 
                    fileManager.getFollowingFileNum(fileNum, 
                                                    true /* forward */);

                if (nextFile == null) {
                    throw new EOFException();
                }

                fileHandle.release();
                fileHandle = null;
                /*
                 * TODO: This doesn't work if file is deleted after calling
                 * getFollowingFileNum above and calling getFileHandle below.
                 * That can happen to due to deletion of reserved files by the
                 * cleaner or eraser. We need a retry loop here that catches
                 * FileNotFoundException and calls getFollowingFileNum again.
                 * The retry method probably belongs in FileManager.
                 */
                fileHandle = fileManager.getFileHandle(nextFile);
                setFileNum(nextFile, fileHandle.getLogVersion());
                startOffset = 0;
                fillFromFile(fileHandle, 0);
                return true;
            } finally {
                if (fileHandle != null) {
                    fileHandle.release();
                }
            }
        }

        /* 
         * Assume that the window is properly positioned. Try to fill the read
         * buffer with data from this file handle, starting at the location
         * indicated by the starting offset field. If this file contains more
         * data, return true. If this file doesn't contain more data, return
         * false.
         *
         * In all cases, leave the the read buffer pointing at the target
         * offset and in a state that's ready to support reads, even if there
         * is nothing in the buffer. Note that the target offset, which may not
         * be the same as starting offset.
         * @return true if more data was read, false if not.         
         */
        protected boolean fillFromFile(FileHandle fileHandle, 
                                       long targetOffset) 
            throws DatabaseException {

            boolean foundData = false;
            readBuffer.clear();
            if (fileManager.readFromFile(fileHandle.getFile(), 
                                         readBuffer,
                                         startOffset,
                                         fileHandle.getFileNum(),
                                         false /* dataKnownToBeInFile */)) {
                foundData = true;
                nReadOperations += 1;
                /*
                 * Ensure that fileNum and logVersion are in sync.  setFileNum
                 * handles changes in the file number.  But we must also update
                 * the logVersion here to handle the first read after we
                 * initialize fileNum and logVersion is unknown.
                 */
                logVersion = fileHandle.getLogVersion();
            }

            /* 
             * In all cases, setup read buffer for valid reading. If the buffer
             * has no data, it will be positioned at the beginning, and will be
             * able to correctly return the fact that there is no data present.
             */

            endOffset = startOffset + readBuffer.position();
            readBuffer.flip();
            readBuffer.position((int) (targetOffset - startOffset));
            return foundData;
        }

        /**
         * Change the read buffer size if we start hitting large log entries so
         * we don't get into an expensive cycle of multiple reads and piecing
         * together of log entries.  
         */
        protected void adjustReadBufferSize(int amountToRead) {

            int readBufferSize = readBuffer.capacity();

            /* 
             * We need to read something larger than the current buffer
             * size. 
             */
            if (amountToRead > readBufferSize) {

                /* We're not at the max yet. */
                if (readBufferSize < maxReadBufferSize) {

                    /*
                     * Make the buffer the minimum of amountToRead or a
                     * maxReadBufferSize.
                     */
                    if (amountToRead < maxReadBufferSize) {
                        readBufferSize = amountToRead;
                        /* Make it a multiple of 1K. */
                        int remainder = readBufferSize % 1024;
                        readBufferSize += 1024 - remainder;
                        readBufferSize = Math.min(readBufferSize,
                                                  maxReadBufferSize);
                    } else {
                        readBufferSize = maxReadBufferSize;
                    }
                    readBuffer = ByteBuffer.allocate(readBufferSize);
                }

                if (amountToRead > readBuffer.capacity()) {
                    nRepeatIteratorReads++;
                    envImpl.getLogManager().incRepeatIteratorReads();
                }
            }
        }

        int capacity() {
            return readBuffer.capacity();
        }

        int remaining() {
            return readBuffer.remaining();
        }

        boolean hasRemaining() {
            return readBuffer.hasRemaining();
        }

        ByteBuffer getBuffer() {
            return readBuffer;
        }

        /**
         * Returns the number of reads since the last time this method was
         * called.
         */
        int getAndResetNReads() {
            int tmp = nReadOperations;
            nReadOperations = 0;
            return tmp;
        }

        long getNRepeatIteratorReads() {
            return nRepeatIteratorReads;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            long start = DbLsn.makeLsn(fileNum, startOffset);
            long end = DbLsn.makeLsn(fileNum, endOffset);
            sb.append("window covers ");
            sb.append(DbLsn.getNoFormatString(start)).append(" to ");
            sb.append(DbLsn.getNoFormatString(end));
            sb.append(" positioned at ");
            long target = getCurrentLsn();
            sb.append(DbLsn.getNoFormatString(target));
            return sb.toString();
        }

        long getCurrentLsn() {
            return DbLsn.makeLsn(fileNum, startOffset + readBuffer.position());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy