src.java.org.objectweb.howl.log.BlockLogBuffer Maven / Gradle / Ivy
/*
* JOnAS: Java(TM) Open Application Server
* Copyright (C) 2004 Bull S.A.
* All rights reserved.
*
* Contact: [email protected]
*
* This software is licensed under the BSD license.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ------------------------------------------------------------------------------
* $Id: BlockLogBuffer.java,v 1.13 2005/11/16 23:09:59 girouxm Exp $
* ------------------------------------------------------------------------------
*/
package org.objectweb.howl.log;
import org.objectweb.howl.log.LogBufferStatus;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.InvalidMarkException;
/**
* An implementation of LogBuffer that
* provides features necessary for a reliable Transaction Monitor
* journal.
*
* Each block contains a header, zero or more log records,
* and a footer. The header and footer contain enough
* information to allow recovery operations to validate
* the integrity of each log block.
*/
class BlockLogBuffer extends LogBuffer
{
/**
* currentTimeMillis that last record was added.
*
This field is used by shouldForce() to determine if the buffer
* should be forced.
*/
long todPut = 0;
/**
* number of times this buffer was used.
*
In general, should be about the same for all buffers in a pool.
*/
int initCounter = 0;
/**
* size of a buffer header.
*
* buffer Header format
* byte[] HEADER_ID [4] "HOWL"
* int block_sequence_number [4]
* int block_size [4] in bytes
* int bytes used [4]
* int checkSum [4]
* long currentTimeMillis [8]
* byte[] CRLF [2] to make it easier to read buffers in an editor
*
*/
// private final static int bufferHeaderSize = 30;
/**
* Offset within the block header of the bytes_used field.
*/
private int bytesUsedOffset = 0;
/**
* The number of bytes to reserve for block footer information.
*/
private final static int bufferFooterSize = 18;
/*
* byte FOOTER_ID [4] "LOWH"
* int block_sequence_number [4]
* long currentTimeMillis [8] same value as header
* byte CRLF [2] to make it easier to read buffers in an editor
*/
/**
* Size of the header for each data record in the block.
*
* Record header format:
* short record type [2] see LogRecordType
* short record length of user data [2]
*/
private int recordHeaderSize = 4;
// block header & footer fields
/**
* Carriage Return Line Feed sequence used for debugging purposes.
*/
private byte[] CRLF = "\r\n".getBytes();
private byte[] crlf = new byte[CRLF.length];
/**
* Signature for each logical block header.
*/
private byte[] HEADER_ID = "HOWL".getBytes();
private byte[] headerId = new byte[HEADER_ID.length];
/**
* Signature for each logical block footer.
*/
private byte[] FOOTER_ID = "LWOH".getBytes();
private byte[] footerId = new byte[FOOTER_ID.length];
/**
* switch to disable writes.
*
Used to measure performance of implementation sans physical writes.
* Subclass defines doWrite to be false to eliminate IO.
*/
boolean doWrite = true;
/**
* maximum size of user data record.
*
*
Although this member is local to a LogBuffer instance,
* it is assumed to have the same value in all instances.
*/
private int maxRecordSize;
/**
* end-of-block string stuffed into buffers
* to help identify end of used portion of buffer
* in a hex dump.
*
*
This string is only stored if there is
* room, and it is not accounted for in
* the length field of the buffer header.
*/
private final static int EOB = 0x454F420A; // "EOB\n"
/**
* default constructor calls super class constructor.
*/
BlockLogBuffer(Configuration config)
{
super(config);
}
/**
* constructs instance of BlockLogBuffer with file IO disabled.
*
*
use this constructor when doing performance measurements
* on the implementation sans file IO.
*
* @param doWrite false to disable IO for performance measurements.
*
When set to false, the write() method does not issue writes
* to the file. This reduces the elapse time of a force() to
* zero and allows performance of the log logic (sans IO) to be
* measured.
*/
BlockLogBuffer(Configuration config, boolean doWrite)
{
super(config);
this.doWrite = doWrite;
}
/**
* puts a data record into the buffer and returns a token for record.
*
Each record consists of zero or more byte[] fields.
* Each field is preceded by a short (2 bytes) containing the
* length of the field to allow for subsequent unpacking.
* @see LogBuffer#put(short, byte[][], boolean)
*/
long put(short type, byte[][] data, boolean sync) throws LogRecordSizeException
{
long logKey = 0L;
int dataSize = 0;
int recordSize = recordHeaderSize;
for (int i=0; i < data.length; ++i)
dataSize += data[i].length + 2; // field size + short length
recordSize += dataSize;
/*
* The following synchronized() statement might be unnecessary.
* All calls to this put() method are synchronized by the
* LogBufferManager.bufferManagerLock.
*
* It does not seem to degrade performance any, so we leave
* it to improve clarity of the code.
*/
synchronized(buffer)
{
if (recordSize > maxRecordSize)
throw new LogRecordSizeException(maxRecordSize);
// TODO: improve exception message w/ text: configured max xxx, size yyy
if (recordSize <= buffer.remaining())
{
// first 8 bits available to Logger -- possibly to carry file rotation number
logKey = ((long)bsn << 24) | buffer.position();
// put a new record into the buffer
buffer.putShort(type).putShort((short)dataSize);
for (int i=0; i < data.length; ++i)
{
buffer.putShort((short)data[i].length);
buffer.put(data[i]);
}
todPut = System.currentTimeMillis();
if (sync)
{
synchronized(waitingThreadsLock)
{
++waitingThreads;
}
}
}
}
return logKey;
}
/**
* write ByteBuffer to the log file.
*/
void write() throws IOException
{
assert lf != null: "LogFile lf is null";
synchronized(this)
{
// guard against gating errors that might allow
// multiple threads to be writing this buffer.
if (iostatus == LogBufferStatus.WRITING) {
// BUG 303907
throw new IOException("BlockLogBuffer.write(): LogBufferStatus.WRITING");
}
}
// increment count of threads waiting for IO to complete
synchronized (waitingThreadsLock)
{
++waitingThreads;
}
// Update bytesUsed in the buffer header
buffer.putInt(bytesUsedOffset, buffer.position());
// Try to stuff an End-Of-Block (EOB) marker
// so we can find end of data in a hex dump
// EOB\n
// 2004-09-27 mg check buffer.remaining to avoid overhead of the
// BufferOverflowException
if (buffer.remaining() >= 4)
{
try {
buffer.putInt(EOB); // "EOB\n".getBytes()
} catch (BufferOverflowException e) {
/* ignore it -- we do not care if it does not get written */
}
}
// update checksum
int checksumOffset = bytesUsedOffset + 4;
buffer.putInt(checksumOffset, 0);
if (doChecksum) {
int checksum = checksum();
buffer.putInt(checksumOffset, checksum);
}
try
{
synchronized(this)
{
// BUG 300613 - update of iostatus needs to be synchronized
iostatus = LogBufferStatus.WRITING;
}
buffer.clear();
if (doWrite) lf.write(this);
// iostatus is updated to COMPLETE by the LogBufferManage after force() is done.
}
catch (IOException e)
{
ioexception = e;
iostatus = LogBufferStatus.ERROR;
throw e;
}
}
/**
* initialize members for buffer reuse.
*
* @param bsn Logic Block Sequence Number of the buffer.
* LogBufferManager maintains a list of block sequence numbers
* to ensure correct order of writes to disk. Some implementations
* of LogBuffer may include the BSN as part of a record or
* block header.
*/
LogBuffer init(int bsn, LogFileManager lfm) throws LogFileOverflowException
{
this.bsn = bsn;
tod = todPut = System.currentTimeMillis();
iostatus = LogBufferStatus.FILLING;
++initCounter;
// initialize the logical block footer
int bufferSize = buffer.capacity();
buffer.clear();
buffer.position(bufferSize - bufferFooterSize);
buffer.put(FOOTER_ID).putInt(bsn).putLong(tod).put(CRLF);
// initialize the logical block header
buffer.clear();
buffer.put(HEADER_ID);
buffer.putInt(bsn);
buffer.putInt(bufferSize);
bytesUsedOffset = buffer.position();
buffer.putInt( 0 ); // holding place for data used
buffer.putInt( 0 ); // holding place for checkSum
buffer.putLong( tod );
buffer.put(CRLF);
// reserve room for buffer footer
buffer.limit(bufferSize - bufferFooterSize);
// set maxRecordSize now so LogFileManager can put records
maxRecordSize = buffer.remaining();
/*
* obtain LogFile from the LogFileManager
* LogFileManager will put a header record into this buffer
* so we must make this call after all other initialization is complete.
*/
lf = lfm.getLogFileForWrite(this);
assert lf != null: "LogFileManager returned null LogFile pointer";
// set maxRecordSize again for user records
maxRecordSize = buffer.remaining();
return this;
}
/**
* internal routine used to compare two byte[] objects.
*
* @param val byte[] containing data to be validated
* @param expected byte[] containing expected sequence of data
*
* @return true if the two byte[] objects are equal, otherwise false.
*/
private boolean compareBytes(byte[] val, byte[] expected)
{
for (int i=0; i lf and validates
* header and footer information.
*
* @see LogBuffer#read(LogFile, long)
* @throws IOException
* if anything goes wrong during the file read.
* @throws InvalidLogBufferException
* if any of the block header or footer fields are invalid.
*/
LogBuffer read(LogFile lf, long position)
throws IOException, InvalidLogBufferException, InvalidMarkException
{
assert lf != null : "LogFile reference lf is null";
assert buffer != null : "ByteBuffer reference is null";
this.lf = lf;
// fill our ByteBuffer with a block of data from the file
// NOTE: file position is not changed by following call
buffer.clear();
int bytesRead = -1;
try {
if (lf.channel.size() > position) // BUG 300986 JRockit throws IOException
bytesRead = lf.channel.read(buffer, position);
} catch (IOException e) {
// BUG 303907 add a message to the IOException
IOException ioe = new IOException("BlockLogBuffer.read(): file " +
lf.file.getName() + " position " + position +
"[" + e.getMessage() + "]");
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
if (bytesRead == -1)
{
// end of file
this.bsn = -1;
return this;
}
if (bytesRead != buffer.capacity())
throw new InvalidLogBufferException("FILESIZE Error: bytesRead=" + bytesRead);
// verify header
buffer.clear();
buffer.get(headerId);
if (!compareBytes(headerId, HEADER_ID))
throw new InvalidLogBufferException("HEADER_ID" + bufferInfo());
// get bsn (int)
this.bsn = buffer.getInt();
// get buffer size (int) compare with buffer capacity
int bufferSize = buffer.getInt();
if (bufferSize != buffer.capacity())
throw new InvalidLogBufferException("bufferSize" + bufferInfo());
// get data used (int)
bytesUsed = buffer.getInt();
if (bytesUsed < 0 || bytesUsed >= buffer.capacity())
throw new InvalidLogBufferException("data used: " + bytesUsed + bufferInfo());
// verify checkSum if it is non-zero
int checksumOffset = buffer.position();
int checkSum = buffer.getInt();
if (checkSum != 0)
{
buffer.putInt(checksumOffset, 0);
int expectedChecksum = checksum();
buffer.clear().position(checksumOffset);
buffer.putInt(checkSum); // put the original value back
if (checkSum != expectedChecksum)
throw new InvalidLogBufferException("CHECKSUM expected: " + Integer.toHexString(expectedChecksum) + bufferInfo());
}
// get tod
this.tod = buffer.getLong();
// get CRLF
buffer.get(crlf);
if (!compareBytes(crlf, CRLF))
throw new InvalidLogBufferException("HEADER_CRLF" + bufferInfo());
// mark start of first data record
buffer.mark();
// get FOOTER_ID and compare
buffer.position(bufferSize - bufferFooterSize);
buffer.get(footerId);
if (!compareBytes(footerId, FOOTER_ID))
throw new InvalidLogBufferException("FOOTER_ID" + bufferInfo());
// compare FOOTER_BSN field with HEADER_BSN
int bsn = buffer.getInt();
if (bsn != this.bsn)
throw new InvalidLogBufferException("FOOTER_BSN" + bufferInfo());
// compare FOOTER_TOD field with HEADER_TOD
long tod = buffer.getLong();
if (tod != this.tod)
throw new InvalidLogBufferException("FOOTER_TOD" + bufferInfo());
// get FOOTER_CRLF
buffer.get(crlf);
if (!compareBytes(crlf, CRLF))
throw new InvalidLogBufferException("FOOTER_CRLF" + bufferInfo());
// reset position to first data record
buffer.reset();
return this;
}
/**
* determines if buffer should be forced to disk.
*
* If there are any waiting threads, then buffer
* is forced when it is 50 ms old. Otherwise, if there
* are no waiting threads, we wait 1/4 second before we
* force.
*
* @return true if buffer should be forced now.
*/
boolean shouldForce()
{
int forceDelta = getWaitingThreads() > 0 ? 50 : 250;
long now = System.currentTimeMillis();
return ((todPut + forceDelta) < now);
}
/**
* return statistics for this buffer.
*
* @return String containing statistics as an XML node
*/
String getStats()
{
String name = this.getClass().getName();
String result = "" +
"\n Number of times this buffer was initialized for use " +
"\n Physical writes " + (doWrite ? "enabled" : "disabled" ) + " " +
"\n Checksum Calculations " + (doChecksum ? "enabled" : "disabled" ) + " " +
"\n " +
"\n";
return result;
}
/**
* generate a String that represents the state of this LogBuffer object
*/
public String bufferInfo()
{
buffer.clear();
StringBuffer sb = new StringBuffer(
"\nClass: " + getClass().getName() +
"\n workerID: " + Integer.toHexString(index) +
"\n LogFile: " + lf.file.getPath() +
"\n HEADER" +
"\n HEADER_ID: 0x" + Integer.toHexString(buffer.getInt()) +
"\n bsn: 0x" + Integer.toHexString(buffer.getInt()) +
"\n size: 0x" + Integer.toHexString(buffer.getInt()) +
" should be: 0x" + Integer.toHexString(buffer.capacity()) +
"\n data used: 0x" + Integer.toHexString(buffer.getInt()) +
"\n checkSum: 0x" + Integer.toHexString(buffer.getInt()) +
"\n tod: 0x" + Long.toHexString(buffer.getLong()) +
"\n crlf: 0x" + Integer.toHexString(buffer.getShort()) +
"" );
buffer.position(buffer.capacity() - bufferFooterSize);
sb.append(
"\n FOOTER" +
"\n FOOTER_ID: 0x" + Integer.toHexString(buffer.getInt()) +
"\n bsn: 0x" + Integer.toHexString(buffer.getInt()) +
"\n tod: 0x" + Long.toHexString(buffer.getLong()) +
"\n crlf: 0x" + Integer.toHexString(buffer.getShort()) +
"" );
return sb.toString();
}
}