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

com.sleepycat.je.log.LogEntryHeader 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.nio.ByteBuffer;
import java.util.zip.Checksum;

import com.sleepycat.je.log.entry.LogEntry;
import com.sleepycat.je.utilint.Adler32;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.VLSN;

/**
 * A LogEntryHeader embodies the header information at the beginning of each
 * log entry file.
 */
public class LogEntryHeader {

    /**
     * Persistent fields. Layout on disk is
     * (invariant) checksum - 4 bytes
     * (invariant) entry type - 1 byte
     * (invariant) entry flags - 1 byte
     * (invariant) offset of previous log entry - 4 bytes
     * (invariant) item size (not counting header size) - 4 bytes
     * (optional) vlsn - 8 bytes
     *
     * Flags:
     * The provisional bit can be set for any log type in the log. It's an
     * indication to recovery that the entry shouldn't be processed when
     * rebuilding the tree. See com.sleepycat.je.log.Provisional.java for
     * the reasons why it's set.
     *
     * The replicated bit is set when this particular log entry is
     * part of the replication stream and contains a VLSN in the header.
     *
     * The invisible bit is set when this log entry has been rolled back as
     * part of replication syncup. The ensuing log entry has not been
     * checksum-corrected, and to read it, the invisible bit must be cloaked.
     *
     * The VLSN_PRESENT bit is set when a VLSN is present for log version 8+,
     * and is set when the replicated bit is *not* set in the case of a cleaner
     * migrated LN.  Prior to version 8, the replicated bit alone indicates
     * that a VLSN is present.  For all versions, if the replicated bit is set
     * then a VLSN is always present. [#19476]
     *
     *                    first version of        migrated LN
     *                    a replicated LN
     *                    ---------------         -----------
     * log version 7-     replicated = true       replicated = false
     * (JE 4.1 and        vlsn present = false    vlsn present = false
     *  earlier)          vlsn exists in header   no vlsn in header
     *
     * log version 8+     replicated = true       replicated = false
     * preserve record    vlsn present = true     vlsn present = false
     * version = false    vlsn exists in header   no vlsn in header
     *
     * log version 8+     replicated = true       replicated = false
     * preserve record    vlsn present = true     vlsn present = true
     * version = true     vlsn exists in header   vlsn exists in header
     */

    /* The invariant size of the log entry header. */
    public static final int MIN_HEADER_SIZE = 14;

    /* Only used for tests and asserts. */
    public static final int MAX_HEADER_SIZE = MIN_HEADER_SIZE + VLSN.LOG_SIZE;

    public static final int CHECKSUM_BYTES = 4;

    public static final int ENTRYTYPE_OFFSET = 4;
    static final int FLAGS_OFFSET = 5;
    private static final int PREV_OFFSET = 6;
    private static final int ITEMSIZE_OFFSET = 10;
    public static final int VLSN_OFFSET = MIN_HEADER_SIZE;

    /*
     * Flags defined in the entry header.
     *
     * WARNING: Flags may not be defined or used in the entry header of the
     * FileHeader.  All flags defined here may only be used in log entries
     * other then the FileHeader. [#16939]
     */
    private static final byte PROVISIONAL_ALWAYS_MASK = (byte) 0x80;
    private static final byte PROVISIONAL_BEFORE_CKPT_END_MASK = (byte) 0x40;
    private static final byte REPLICATED_MASK = (byte) 0x20;
    private static final byte INVISIBLE = (byte) 0x10;
    private static final byte IGNORE_INVISIBLE = ~INVISIBLE;
    private static final byte VLSN_PRESENT = (byte) 0x08;
    /* Flags stored in version byte for logVersion 6 and below.*/
    private static final byte VERSION_6_FLAGS =
        PROVISIONAL_ALWAYS_MASK |
        PROVISIONAL_BEFORE_CKPT_END_MASK |
        REPLICATED_MASK;
    private static final byte IGNORE_VERSION_6_FLAGS = ~VERSION_6_FLAGS;

    private static final byte FILE_HEADER_TYPE_NUM =
        LogEntryType.LOG_FILE_HEADER.getTypeNum();

    private long checksumVal;   // stored in 4 bytes as an unsigned int
    private final byte entryType;
    private long prevOffset;
    private final int itemSize;
    private VLSN vlsn;

    /*
     * Prior to log version 6, a type-specific version was stored in each
     * entry, and was packed together with the flags in a single byte.
     *
     * For version 6, we changed to use a global version (not type specific),
     * but it was stored in each entry, packed with the flags as in earlier
     * versions, as well as being stored redundantly in the FileHeader.  The
     * entry header and file header versions are always the same for all
     * entries in a file.  We flip the log file to guarantee this, when running
     * for the first time with an upgraded JE with a new log version.
     *
     * For version 7 and above, the version is stored only in the FileHeader,
     * freeing the space formerly taken by the version in each entry for use
     * by flag bits.  The version is not stored in each entry; however, the
     * version is still maintained in this in-memory object for two reasons:
     *
     * 1. When reading log files prior to version 6, each entry potentially has
     *    a different version.
     * 2. Convenience of access to the version when processing log entries.
     *
     * [#16939]
     */
    private int entryVersion;

    /* Version flag fields */
    private Provisional provisional;
    private boolean replicated;
    private boolean invisible;
    private boolean vlsnPresent;

    /**
     * For reading a log entry.
     *
     * @param entryBuffer the buffer containing at least the first
     * MIN_HEADER_SIZE bytes of the entry header.
     *
     * @param logVersion is the log version of the file that contains the given
     * buffer, and is obtained from the file header.  Note that for the file
     * header entry itself, UNKNOWN_FILE_HEADER_VERSION may be passed.
     *
     * @param lsn is the LSN of the entry, for exception reporting.
     */
    public LogEntryHeader(ByteBuffer entryBuffer, int logVersion, long lsn)
        throws ChecksumException {

        assert logVersion == LogEntryType.UNKNOWN_FILE_HEADER_VERSION ||
            (logVersion >= LogEntryType.FIRST_LOG_VERSION &&
             logVersion <= LogEntryType.LOG_VERSION) : logVersion;

        checksumVal = LogUtils.readUnsignedInt(entryBuffer);
        entryType = entryBuffer.get();
        if (!LogEntryType.isValidType(entryType)) {
            throw new ChecksumException(
                "Invalid log entry type: " + entryType +
                " lsn=" + DbLsn.getNoFormatString(lsn) +
                " bufPosition=" + entryBuffer.position() +
                " bufRemaining=" + entryBuffer.remaining());
        }

        if (entryType == FILE_HEADER_TYPE_NUM) {
            /* Actual version will be set by setFileHeaderVersion. */
            entryVersion = LogEntryType.UNKNOWN_FILE_HEADER_VERSION;
            /* Discard flags byte: none are allowed for the file header. */
            entryBuffer.get();
            initFlags(0);
        } else {
            if (logVersion == LogEntryType.UNKNOWN_FILE_HEADER_VERSION ) {
                /*
                 * If we are reading a log header the type should be
                 * FILE_HEADER_TYPE_NUM.
                 */
                throw new ChecksumException(
                    "Wrong entry type for header: " + entryType +
                    " lsn=" + DbLsn.getNoFormatString(lsn) +
                    " bufPosition=" + entryBuffer.position() +
                    " bufRemaining=" + entryBuffer.remaining());
            } else if (logVersion <= 6) {
                /* Before version 7, flags and version were packed together. */
                entryVersion = entryBuffer.get();
                initFlags(entryVersion & VERSION_6_FLAGS);
                entryVersion &= IGNORE_VERSION_6_FLAGS;
                /* For log version 6, the entry version should always match. */
                assert (logVersion == 6) ? (entryVersion == 6) : true;
            } else {
                /* For log version 7+, only flags are stored in the entry. */
                entryVersion = logVersion;
                initFlags(entryBuffer.get());
            }
        }
        prevOffset = LogUtils.readUnsignedInt(entryBuffer);
        itemSize = LogUtils.readInt(entryBuffer);
        if (itemSize < 0) {
            throw new ChecksumException(
                "Invalid log entry size: " + itemSize +
                " lsn=" + DbLsn.getNoFormatString(lsn) +
                " bufPosition=" + entryBuffer.position() +
                " bufRemaining=" + entryBuffer.remaining());
        }
    }

    /**
     * For writing a log header. public for unit tests.
     */
    public LogEntryHeader(LogEntry entry,
                          Provisional provisional,
                          ReplicationContext repContext) {

        LogEntryType logEntryType = entry.getLogType();
        entryType = logEntryType.getTypeNum();
        entryVersion = LogEntryType.LOG_VERSION;
        this.itemSize = entry.getSize();
        this.provisional = provisional;

        assert (!((!logEntryType.isReplicationPossible()) &&
                  repContext.inReplicationStream())) :
               logEntryType + " should never be replicated.";

        if (logEntryType.isReplicationPossible()) {
            this.replicated = repContext.inReplicationStream();
        } else {
            this.replicated = false;
        }
        invisible = false;

        /*
         * If we about to write a new replicated entry, the VLSN will be null
         * and mustGenerateVLSN will return true.  For a cleaner migrated LN
         * that was replicated, the VLSN will be non-null and mustGenerateVLSN
         * will return false.  [#19476]
         */
        vlsnPresent = repContext.getClientVLSN() != null ||
            repContext.mustGenerateVLSN();
    }

    /**
     * For reading a replication message. The node-specific parts of the header
     * are not needed.
     */
    public LogEntryHeader(byte entryType,
                          int entryVersion,
                          int itemSize,
                          VLSN vlsn) {

        assert ((vlsn != null) && !vlsn.isNull()) :
               "vlsn = " + vlsn;

        this.entryType = entryType;
        this.entryVersion = entryVersion;
        this.itemSize = itemSize;
        this.vlsn = vlsn;
        replicated = true;
        vlsnPresent = true;
        provisional = Provisional.NO;
    }

    private void initFlags(int entryFlags) {
        if ((entryFlags & PROVISIONAL_ALWAYS_MASK) != 0) {
            provisional = Provisional.YES;
        } else if ((entryFlags & PROVISIONAL_BEFORE_CKPT_END_MASK) != 0) {
            provisional = Provisional.BEFORE_CKPT_END;
        } else {
            provisional = Provisional.NO;
        }
        replicated = ((entryFlags & REPLICATED_MASK) != 0);
        invisible = ((entryFlags & INVISIBLE) != 0);
        vlsnPresent = ((entryFlags & VLSN_PRESENT) != 0) || replicated;
    }

    /**
     * Called to set the version for a file header entry after reading the
     * version from the item data.  See FileHeaderEntry.readEntry.  [#16939]
     */
    public void setFileHeaderVersion(final int logVersion) {
        entryVersion = logVersion;
    }

    public long getChecksum() {
        return checksumVal;
    }

    public byte getType() {
        return entryType;
    }

    public int getVersion() {
        return entryVersion;
    }

    public long getPrevOffset() {
        return prevOffset;
    }

    public int getItemSize() {
        return itemSize;
    }

    public int getEntrySize() {
        return getSize() + getItemSize();
    }

    public VLSN getVLSN() {
        return vlsn;
    }

    public boolean getReplicated() {
        return replicated;
    }

    public Provisional getProvisional() {
        return provisional;
    }

    public boolean isInvisible() {
        return invisible;
    }

    /**
     * Returns whether the type of this entry has been changed to LOG_ERASED.
     *
     * @see com.sleepycat.je.log.entry.ErasedLogEntry
     * @see com.sleepycat.je.cleaner.DataEraser
     */
    public boolean isErased() {
        return entryType == LogEntryType.LOG_ERASED.getTypeNum();
    }

    public boolean hasChecksum() {
        return !isErased();
    }

    public int getVariablePortionSize() {
        return VLSN.LOG_SIZE;
    }

    /**
     * @return number of bytes used to store this header
     */
    public int getSize() {
        if (vlsnPresent) {
            return MIN_HEADER_SIZE + VLSN.LOG_SIZE;
        }
        return MIN_HEADER_SIZE;
    }

    /**
     * @return the number of bytes used to store the header, excepting
     * the checksum field.
     */
    int getSizeMinusChecksum() {
        return getSize()- CHECKSUM_BYTES;
    }

    /**
     * @return the number of bytes used to store the header, excepting
     * the checksum field.
     */
    int getInvariantSizeMinusChecksum() {
        return MIN_HEADER_SIZE - CHECKSUM_BYTES;
    }

    /**
     * Assumes this is called directly after the constructor, and that the
     * entryBuffer is positioned right before the VLSN.
     */
    public void readVariablePortion(ByteBuffer entryBuffer) {
        if (vlsnPresent) {
            vlsn = new VLSN();
            vlsn.readFromLog(entryBuffer, entryVersion);
        }
    }

    /**
     * Serialize this object into the buffer and leave the buffer positioned in
     * the right place to write the following item.  The checksum, prevEntry,
     * and vlsn values will filled in later on.
     *
     * public for unit tests.
     */
    public void writeToLog(ByteBuffer entryBuffer) {

        /* Skip over the checksumVal, proceed to the entry type. */
        entryBuffer.position(ENTRYTYPE_OFFSET);
        entryBuffer.put(entryType);

        /* Flags */
        byte flags = 0;
        if (provisional == Provisional.YES) {
            flags |= PROVISIONAL_ALWAYS_MASK;
        } else if (provisional == Provisional.BEFORE_CKPT_END) {
            flags |= PROVISIONAL_BEFORE_CKPT_END_MASK;
        }
        if (replicated) {
            flags |= REPLICATED_MASK;
        }
        if (vlsnPresent) {
            flags |= VLSN_PRESENT;
        }
        entryBuffer.put(flags);

        /*
         * Leave room for the prev offset, which must be added under
         * the log write latch. Proceed to write the item size.
         */
        entryBuffer.position(ITEMSIZE_OFFSET);
        LogUtils.writeInt(entryBuffer, itemSize);

        /*
         * Leave room for a VLSN if needed, must also be generated
         * under the log write latch.
         */
        if (vlsnPresent) {
            entryBuffer.position(entryBuffer.position() + VLSN.LOG_SIZE);
        }
    }

    /**
     * Add those parts of the header that must be calculated later to the
     * entryBuffer, and also assign the fields in this class.
     * That's
     * - the prev offset, which must be done within the log write latch to
     *   be sure what that lsn is
     * - the VLSN, for the same reason
     * - the checksumVal, which must be added last, after all other
     *   fields are marshalled.
     * (public for unit tests)
     */
    public ByteBuffer addPostMarshallingInfo(ByteBuffer entryBuffer,
                                             long lastOffset,
                                             VLSN vlsn) {

        /* Add the prev pointer */
        prevOffset = lastOffset;
        entryBuffer.position(PREV_OFFSET);
        LogUtils.writeUnsignedInt(entryBuffer, prevOffset);

        if (vlsn != null) {
            this.vlsn = vlsn;
            entryBuffer.position(VLSN_OFFSET);

            vlsn.writeToLog(entryBuffer);
        }

        /*
         * Now calculate the checksumVal and write it into the buffer.  Be sure
         * to set the field in this instance, for use later when printing or
         * debugging the header.
         */
        Checksum checksum = Adler32.makeChecksum();
        checksum.update(entryBuffer.array(),
                        entryBuffer.arrayOffset() + CHECKSUM_BYTES,
                        entryBuffer.limit() - CHECKSUM_BYTES);
        entryBuffer.position(0);
        checksumVal = checksum.getValue();
        LogUtils.writeUnsignedInt(entryBuffer, checksumVal);

        /* Leave this buffer ready for copying into another buffer. */
        entryBuffer.position(0);

        return entryBuffer;
    }

    /**
     * @param sb destination string buffer
     * @param verbose if true, dump the full, verbose version
     */
    public void dumpLog(StringBuilder sb, boolean verbose) {
        sb.append("");
    }

    /**
     * Dump the header without enclosing 
tags. Used for * DbPrintLog, to make the header attributes in the tag, for * a more compact rendering. * @param sb destination string buffer * @param verbose if true, dump the full, verbose version */ void dumpLogNoTag(StringBuilder sb, boolean verbose) { LogEntryType lastEntryType = LogEntryType.findType(entryType); sb.append("type=\"").append(lastEntryType.toStringNoVersion()). append("/").append(entryVersion); if (provisional != Provisional.NO) { sb.append("\" prov=\""); sb.append(provisional); } if (vlsn != null) { sb.append("\" "); vlsn.dumpLog(sb, verbose); } else { sb.append("\""); } if (getReplicated()) { sb.append(" isReplicated=\"1\""); } if (isInvisible()) { sb.append(" isInvisible=\"1\""); } sb.append(" prev=\"0x").append(Long.toHexString(prevOffset)); if (verbose) { sb.append("\" size=\"").append(itemSize); sb.append("\" cksum=\"").append(checksumVal); } } /** * For use in special case where commits are transformed to aborts because * of i/o errors during a logBuffer flush. See [11271]. * Assumes that the entryBuffer is positioned at the start of the item. * Return with the entryBuffer positioned to the end of the log entry. */ void convertCommitToAbort(ByteBuffer entryBuffer) { assert (entryType == LogEntryType.LOG_TXN_COMMIT.getTypeNum()); /* Remember the start of the entry item. */ int itemStart = entryBuffer.position(); /* Back up to where the type is stored and change the type. */ int entryTypePosition = itemStart - (getSize() - ENTRYTYPE_OFFSET); entryBuffer.position(entryTypePosition); entryBuffer.put(LogEntryType.LOG_TXN_ABORT.getTypeNum()); /* * Recalculate the checksum. This byte buffer could be large, * so don't just turn the whole buffer into an array to pass * into the checksum object. */ Checksum checksum = Adler32.makeChecksum(); int checksumSize = itemSize + (getSize() - CHECKSUM_BYTES); checksum.update(entryBuffer.array(), entryTypePosition + entryBuffer.arrayOffset(), checksumSize); entryBuffer.position(itemStart - getSize()); checksumVal = checksum.getValue(); LogUtils.writeUnsignedInt(entryBuffer, checksumVal); } @Override public String toString() { StringBuilder sb = new StringBuilder(); dumpLog(sb, true /* verbose */); return sb.toString(); } /* * Dump only the parts of the header that apply for replicated entries. */ public void dumpRep(StringBuilder sb) { LogEntryType lastEntryType = LogEntryType.findType(entryType); sb.append(lastEntryType.toStringNoVersion()). append("/").append(entryVersion); if (vlsn != null) { sb.append(" vlsn=" ).append(vlsn); } else { sb.append("\""); } if (getReplicated()) { sb.append(" isReplicated=\"1\""); } if (isInvisible()) { sb.append(" isInvisible=\"1\""); } } /** * @return true if two log headers are logically the same. This check will * ignore the log version. * * Used by replication. */ public boolean logicalEqualsIgnoreVersion(LogEntryHeader other) { /* * Note that item size is not part of the logical equality, because * on-disk compression can make itemSize vary if the entry has VLSNs * that were packed differently. */ return ((getVLSN().equals(other.getVLSN())) && (getReplicated() == other.getReplicated()) && (isInvisible() == other.isInvisible()) && (LogEntryType.compareTypeAndVersion(getVersion(), getType(), other.getVersion(), other.getType()))); } /** * May be called after reading MIN_HEADER_SIZE bytes to determine * whether more bytes (getVariablePortionSize) should be read. */ public boolean isVariableLength() { /* Currently only entries with VLSNs are variable length. */ return vlsnPresent; } /** * Set the invisible bit in the given log entry flags. */ static byte makeInvisible(byte flags) { return (byte) (flags | INVISIBLE); } /** * Turn off the invisible bit in the byte buffer which backs this log entry * header. * @param logHeaderStartPosition the byte position of the start of the log * entry header. */ public static void turnOffInvisible(ByteBuffer buffer, int logHeaderStartPosition) { int flagsPosition = logHeaderStartPosition + FLAGS_OFFSET; byte flags = buffer.get(flagsPosition); flags &= IGNORE_INVISIBLE; buffer.put(flagsPosition, flags); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy