org.objectweb.howl.log.LogFileManager 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: LogFileManager.java,v 1.20 2006/04/14 21:13:23 girouxm Exp $
* ------------------------------------------------------------------------------
*/
package org.objectweb.howl.log;
import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
/**
* Manage a set of log files.
*
* This class implements methods that can be called by LogBufferManager to obtain a LogFile for
* logger IO and to signal the LogFileManager when new buffers are being initialized.
* LogFileManager manages log files according to implementation specific policies.
* Some LogFileManagers may use a circular file policy while others may use a set of files.
* The most simple implementations will use a single file and allow it to grow as needed.
*
* QUESTION: do we need multiple implementations, or can we deal with different policies
* in this one class using configuration?
*
* @author Michael Giroux
*
*/
class LogFileManager extends LogObject
{
/**
* maximum number of blocks to store in each LogFile.
*
* controls when logging is switched to a new log file, and/or when a circular
* log is reset to seek address zero.
*
* @see #getLogFileForWrite(LogBuffer)
*/
int maxBlocksPerFile = Integer.MAX_VALUE;
/**
* The log key for the oldest active entry in the log.
*
*
When automark is enabled (true) the activeMark
* is updated after every put() operation.
* When automark is disabled (as should be the case with JOTM)
* the activeMark is updated manually by a call
* to Logger.mark(), or during logOverflowNotification processing
* if a LogEventListener is registered.
*
* @see org.objectweb.howl.log.Logger#mark(long)
*/
long activeMark = 0;
/**
* indicates whether log files will be marked automatically.
*
When automark is false, the mark() method must be
* invoked by the log user. When automark is true, the Logger
* will automatically set the mark at the most recent record.
*/
boolean automark = false;
/**
* The automark value restored during log file initialization.
*
*
if a set of log files exist from a prior execution,
* the value of automark is set based on the log file header
* record processed during the init() processing. The value
* restored from the log file is saved in restartAutoMark
* so it can be reported by getStats().
*/
boolean restartAutoMark = false;
/**
* last key returned by put().
*
*
updated by setCurrentKey(long)
*/
long currentKey = 0;
/**
* the first log key generated by this instance of the Logger.
*
*
Initialized by init() to the block immediately
* following the last block written in the prior execution.
*/
private long initialKey = 0;
/**
* data written to log when autoMark is turned on
*/
final byte[] autoMarkOn = new byte[] { 1 };
/**
* data written to log when autoMark is turned off
*/
final byte[] autoMarkOff = new byte[] { 0 };
/**
* lock controlling access to LogFile.
*/
private final Object fileManagerLock = new Object();
/**
* set of LogFile objects associated with the physical log files.
*
* @see #open()
*/
LogFile[] fileSet = null;
/**
* workerID to current entry in fileSet[]
*/
int lfIndex = 0;
LogFile currentLogFile = null;
/**
* LogFile header record.
*
*
protected by fileManagerLock
*
*
The first record of every LogFile is a FILE_HEADER record containing
* information that is used during recovery to reposition the log file
* and replay records starting from the active mark.
*
* byte[1] autoMark byte[1]
* long activeMark byte[8] global to all log files
* long lowMark byte[8] low mark for current file == high mark for previous file
* long prevSwitchTod byte[8] time of previous file switch
* int fileSet.length byte[4] number of files in fileSet
* int maxBlocksPerFile byte[4]
* byte[2] crlf byte[2]
*/
byte[][] fileHeader = new byte[1][35];
/**
* ByteBuffer wrapper for fileHeader to facilitate conversion of numeric
* information to byte[] format.
*
*
protected by fileManagerLock
*/
ByteBuffer fileHeaderBB = ByteBuffer.wrap(fileHeader[0]);
/**
* MARK control Record.
*
*
A MARK control record containing the current state of the automark mode
* and the active mark is stored at the beginning of every block.
* This strategy allows the logger to read through a log file
* looking only at the BSN for each block to locate the last written block.
* Once the last written block is located, the current state of the MARK
* can be obtained by examining the MARK record at the beginning of the
* block.
*
*
To avokd garbage collection for a byte[] and ByteBuffer wrapper for
* each physical block, the class has a single markRecord member that is
* protected by fileManagerLock.
*
*
protected by fileManagerLock
*
* byte[1] autoMark byte[1]
* long activeMark byte[8] global to all log files
* int fileSet.length byte[4] number of files in fileSet
* int maxBlocksPerFile byte[4]
* byte[2] crlf byte[2]
*
* @see #setMarkData(ByteBuffer)
*/
byte[][] markRecord = new byte[1][19];
/**
* ByteBuffer wrapper for markRecord to facilitate conversion of numeric
* information to byte[] format.
*
*
protected by fileManagerLock
*/
ByteBuffer markRecordBB = ByteBuffer.wrap(markRecord[0]);
/**
* end of line for log records to make logs readable in text editors.
*/
byte[] crlf = "\r\n".getBytes();
/**
* LogBufferManager used by methods that put() records to the log.
*
For example, the mark() method uses bmgr to put()
* mark information to the log.
*/
LogBufferManager bmgr;
/**
* The LogEventListener registered by the application that
* owns the logger. If eventListener is null then
* the application is not notified of log events.
*/
private LogEventListener eventListener = null;
/**
* Monitor used by EventManager thread to wait
* for events that need to be notified.
*/
final Object eventManagerLock = new Object();
/**
* Thread used by LogFileManager to send events
* to the registered LogEventListener.
*/
Thread eventManagerThread = null;
/**
* event to be processed by the eventManagerThread.
*
When a condition is encountered that requires
* the LogEventListener to be notified, the value
* is set to one of the event types.
*/
int event = 0;
/**
* Event types for event manager thread.
*
assign this value to the event
* member when invoking the event manager thread
* to handle a log overflow notification.
*/
final static int LOG_OVERFLOW_EVENT = 0x1;
/**
* The logKey to be used by LogEventListener.logOverflowNotification
* when moving records forward into the current log file.
*/
private long lowestSafeLogKey = 0L;
/**
* Number of times log overflow event was notified.
* @see org.objectweb.howl.log.LogEventListener#logOverflowNotification(long)
*/
private int overflowNotificationCount = 0;
/**
* Indicates that LogFileManager initialization is complete.
*
Prior to being fully initialized, the LogFileManager
* should not attempt to put records to the journal because
* the buffer manager may not be initialized properly.
*/
private boolean initComplete = false; // BUG 300934
/**
* construct LogFileManager with Configuration supplied by caller.
* @param config Configuration object.
*/
LogFileManager(Configuration config)
{
super(config);
// remember that initialization is not yet complete
initComplete = false; // BUG 300934
// light up the event managemer thread
eventManagerThread = new EventManager("LogFileManager.EventManager");
eventManagerThread.setDaemon(true); // so we can shut down while eventManagerThread is running
eventManagerThread.start();
}
/**
* Returns the LogFile that contains the requested mark .
*
Called by LogBufferManager to locate a log file needed
* for a replay() request.
*
* @param mark A log key previously returned by LogBufferManager.put().
* The log key is used to compute the desired file.
*
* @return LogFile containing the requested mark .
*
Returns null if none of the files in fileSet[] contain
* the requested mark.
*/
LogFile getLogFileForMark(long mark)
{
LogFile lf = null;
int requestBsn = bmgr.bsnFromMark(mark);
int fsl = fileSet.length;
// handle request for oldest known mark
if (mark == 0)
{
int minBsn = Integer.MAX_VALUE;
int minIndex = fsl;
for (int i=0; i < fsl; ++i)
{
lf = fileSet[i];
if (!lf.newFile && lf.firstBSN < minBsn) {
minBsn = lf.firstBSN;
minIndex = i;
}
}
if (minIndex < fsl)
return fileSet[minIndex];
else
return null;
}
// handle request for a specific non-zero mark
for (int i=0; i < fsl; ++i)
{
lf = fileSet[i];
if (lf.newFile || requestBsn < lf.firstBSN) continue;
if (mark < lf.highMark) return lf;
}
// requested mark not found
return null;
}
/**
* Called by LogBuffer.init() to obtain the LogFile that will be used
* to write a specific log block.
*
*
The buffer sequence number of the LogBuffer parameter ( lf.bsn )
* represents an implementation specific value that is used to manage log
* file space. As buffers are written to disk the buffer sequence number is
* incremented.
*
* The LogFileManager is able to compute the seek
* address for a buffer as a function of lf.bsn and buffer size
* when using LogBuffer implementations with fixed buffer sizes.
*
*
In all cases, getLogFile records a header record into the buffer
* containing the current state of the automark mode and the current
* active mark.
*
* @param lb LogBuffer that is asking for the LogFile.
* LogFileManager implementations use lf.bsn to determine when to switch
* to a new file, or wrap a circular file back to seek address zero.
*
* @return a LogFile to use for writing the LogBuffer
*/
LogFile getLogFileForWrite(LogBuffer lb) throws LogFileOverflowException
{
try
{
synchronized(fileManagerLock)
{
if (currentLogFile == null || ((lb.bsn - 1) % maxBlocksPerFile) == 0)
{
// BSN is first block of a file.
int fsl = fileSet.length;
lfIndex %= fsl;
// Make sure active mark is not within the next log file.
LogFile nextLogFile = fileSet[lfIndex];
assert nextLogFile != null: "nextLogFile == null";
if (activeMark > 0 && activeMark < nextLogFile.highMark)
throw new LogFileOverflowException(activeMark, nextLogFile.highMark, nextLogFile.file);
++lfIndex;
// remember the TOD we switched to this file
nextLogFile.tod = System.currentTimeMillis();
// remember first BSN in the file
nextLogFile.firstBSN = lb.bsn;
// fabricate log key for beginning of new bsn as high mark for current file
// this value is used to compare with activeMark the next time this object is
// reused.
long highMark = bmgr.markFromBsn(lb.bsn, 0);
// default tod for previous file switch is current file switch tod
long switchTod = nextLogFile.tod;
if (currentLogFile != null)
{
switchTod = currentLogFile.tod;
currentLogFile.highMark = highMark;
}
// indicate that the new file must be rewound before this buffer is written
lb.rewind = true;
short type = LogRecordType.FILE_HEADER;
fileHeaderBB.clear();
fileHeaderBB.put(automark ? autoMarkOn : autoMarkOff);
fileHeaderBB.putLong(activeMark);
fileHeaderBB.putLong(highMark);
fileHeaderBB.putLong(switchTod);
fileHeaderBB.putInt(fileSet.length);
fileHeaderBB.putInt(maxBlocksPerFile);
fileHeaderBB.put(crlf);
assert fileHeader[0].length == fileHeaderBB.position()
: "byte[] fileHeader size error";
lb.lf = nextLogFile;
lb.put(type, fileHeader, false);
currentLogFile = nextLogFile;
}
else
{
// -------------------------------------------------------------------
// initialize next block of current file with a MARKKEY control record
// -------------------------------------------------------------------
short type = LogRecordType.MARKKEY;
setMarkData(markRecordBB);
assert markRecord[0].length == markRecordBB.position()
: "byte[] markRecord size error";
lb.lf = currentLogFile;
lb.put(type, markRecord, false);
// BUG: 300505 issue force for last block of file
lb.forceNow = ((lb.bsn % maxBlocksPerFile) == 0);
// check for log overflow
detectLogOverflow(lb.bsn);
}
} // synchronized(fileManagerLock)
} catch (LogRecordSizeException e) {
// will never happen but use assert to catch during development
assert false : "Unhandled LogRecordSizeException" + e;
}
// update LogFile.highMark just in case someone tries to replay the log while it is active.
currentLogFile.highMark = bmgr.markFromBsn(lb.bsn + 1, 0);
currentLogFile.newFile = false; // it's no longer a new file
return currentLogFile;
}
/**
* Detect pending Log Overflow and notify event listener.
*
if current file is 50% full then we check to
* see if the next file contains the active mark.
* We notify the event listener to move records
* forward to prevent log overflow.
*
Called by: getLogFileForWrite(Logbuffer lb)
*
* @param bsn The block sequence number of the current LogBuffer.
*
* @see #getLogFileForWrite(LogBuffer)
*/
private void detectLogOverflow(int bsn)
{
if ((bsn % maxBlocksPerFile) > (maxBlocksPerFile / 2))
{
// current file is 50% full
LogFile nextLogFile = fileSet[lfIndex % fileSet.length];
assert nextLogFile != null: "nextLogFile == null";
if (activeMark < nextLogFile.highMark)
{
// active mark is somewhere in next file
synchronized(eventManagerLock)
{
// do not notify this event if it is already being processed
if ((event & LOG_OVERFLOW_EVENT) == 0)
{
lowestSafeLogKey = nextLogFile.highMark;
// kick the event manager thread
event |= LOG_OVERFLOW_EVENT;
eventManagerLock.notifyAll();
}
} // synchronized(eventManagerLock)
}
}
}
/**
* generates MARKKEY data record into supplied data parameter.
*
* @param data
* ByteBuffer wrapping the target byte[]
*/
void setMarkData(ByteBuffer data)
{
data.clear();
data.put(automark ? autoMarkOn : autoMarkOff);
data.putLong(activeMark);
data.putInt(fileSet.length);
data.putInt(maxBlocksPerFile);
data.put(crlf);
}
/**
* sets the LogFile's mark.
*
*
mark() provides a generalized method for callers
* to inform the Logger that log space can be released
* for reuse.
*
*
writes a MARKKEY control record to the log.
*
* @param key is an opaque log key returned by a previous call
* to put().
* @param force when set true causes the caller to
* be blocked until the new force record is forced to disk.
*
* @return log key for the MARK record
*
* @throws InvalidLogKeyException if key parameter is out of range.
* key must be greater than current activeMark and less than the most recent
* key returned by put().
*/
long mark(long key, boolean force)
throws InvalidLogKeyException, IOException, InterruptedException
{
if (key < activeMark )
throw new InvalidLogKeyException(
" key: " + Long.toHexString(key) +
" less than activeMark: " + Long.toHexString(activeMark)
);
if (key > currentKey)
throw new InvalidLogKeyException(
" key: " + Long.toHexString(key) +
" greater than currentKey: " + Long.toHexString(currentKey)
);
byte[][] markData = new byte[1][markRecord[0].length];
ByteBuffer markDataBuffer = ByteBuffer.wrap(markData[0]);
short type = LogRecordType.MARKKEY;
setMarkData(markDataBuffer);
long markKey = 0L;
try {
markKey = bmgr.put(type, markData, force);
activeMark = key;
}
catch (LogRecordSizeException e) {
// will never happen but use assert to catch during development
assert false : "Unhandled LogRecordSizeException" + e;
}
catch (LogFileOverflowException e) {
// should not happen since we just gave back some space
// TODO: handle possible overflow here
}
catch (IOException e) {
IOException ioe = new IOException("LogFileManager.mark()" +
" [" + e.getMessage() + "]");;
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
return markKey;
}
/**
* calls mark(key, false)
* @param key a log key as described by {@link #mark(long,boolean)}
* @return log key of the new mark record.
* @throws InvalidLogKeyException
* @throws IOException
* @throws InterruptedException
* @see #mark(long, boolean)
*/
long mark(long key)
throws InvalidLogKeyException, IOException, InterruptedException
{
return mark(key, false);
}
/**
* reads a block of data into LogBuffer lb .
*
Amount of data read is determined by lb.capacity().
*
sets lb.lf with the fileSet[] entry that contains
* the requested BSN.
*
* @param lb LogBuffer to read data into.
* @param bsn block sequence number of the block to be read.
* File position is computed as follows:
*
* - locate the entry within fileSet[] that contains the
* requested bsn.
*
- compute position as (requested bsn - first bsn in file) * block size;
*
* @return block serial number of block read.
* returns -1 if the requested BSN does
* not exist in the current fileSet[].
*/
int read(LogBuffer lb, int bsn) throws IOException, InvalidLogBufferException
{
if (bsn < 0)
throw new IllegalArgumentException("BSN must be >= zero");
long mark = bmgr.markFromBsn(bsn, 0);
// locate the log file that contains the desired block
LogFile lf = getLogFileForMark(mark);
lb.lf = lf;
if (lf == null) {
lb.bsn = -1;
return -1;
}
// compute position of requested block
long position = 0;
if (bsn > 0) {
int blocksToSkip = bsn - lf.firstBSN;
position = blocksToSkip * lb.buffer.capacity();
}
// read the block
lb.read(lf, position); // BUG 300969
return (lb.bsn < bsn) ? -1 : lb.bsn; // BUG 300969
}
/**
* Sets the LogFile marking mode.
*
*
writes an AUTOMARK control record to the log if the log
* is open.
*
* @param automark true to indicate automatic marking.
*
* @return log key for the generated MARK control record.
*/
long setAutoMark(boolean automark)
throws InvalidLogKeyException, IOException, InterruptedException, LogFileOverflowException
{
this.automark = automark;
return mark((automark ? currentKey : activeMark), false);
}
/**
* updates currentKey member variable.
*
*
Method must be synchronized to guarantee that only
* keys with larger values are assigned to currentKey.
*
*
If automark mode is enabled, then the activeMark
* is updated as well.
*
* @param key a log key returned by the buffer manager.
*
*/
synchronized void setCurrentKey(long key)
{
if (key > currentKey) {
currentKey = key;
if (automark) activeMark = currentKey;
}
}
/**
* Returns the highMark from the LogFile that is
* currently being written.
*
Log keys greater than this value are beyond
* the logical end of the journal.
*
* @return highMark from the current LogFile.
*/
long getHighMark()
{
if (currentLogFile == null)
throw new UnsupportedOperationException("LogFileManager.init() required");
return currentLogFile.highMark;
}
/**
* Registers a LogEventListener for log event notifications.
*
* @param eventListener object to be notified of logger events.
*/
void setLogEventListener(LogEventListener eventListener)
{
// QUESTION: does this need to be a list of listeners?
this.eventListener = eventListener;
// TODO: if current log file position is > 50% then notify NOW
}
/**
* Create a JVM wide lock on a File.
*
Feature 30922
*
* The java.nio.channel.tryLock method is not guaranteed to
* respect locks that are set within the JVM by multiple
* threads. Consequently, it is possible for an application
* to create multiple instances of Logger resulting in
* corrupted log files as two separate instances of Logger
* write to the log.
*
* To prevent multiple instances of Logger from allocating
* the same files within a JVM, we create a system property
* @param name File object to be locked
* @return true if requested lock operation is successful.
*/
private boolean setLockOnFile(File name, boolean lock)
{
String property = "org.objectweb.howl." + name.getAbsolutePath() + ".locked";
synchronized(System.class)
{
if (lock && Boolean.getBoolean(property))
{
return false;
}
System.setProperty(property, Boolean.toString(lock));
return true;
}
}
/**
* open pool of LogFile(s).
*
* @throws FileNotFoundException
* @throws LogConfigurationException
* @throws IOException
* @throws InvalidFileSetException
*/
void open()
throws LogConfigurationException, IOException, FileNotFoundException, InvalidFileSetException
{
// retrieve configuration properties for this object
maxBlocksPerFile = config.getMaxBlocksPerFile();
// make sure we have at least two log files
int maxLogFiles = config.getMaxLogFiles();
if (maxLogFiles < 2)
throw new LogConfigurationException("Must configure two or more files");
// get configuration information for log file names.
String logDir = config.getLogFileDir();
String logFileName = config.getLogFileName();
String logFileExt = config.getLogFileExt();
// make sure the directory exists
File dir = new File(logDir);
dir.mkdirs();
int existingFiles = 0;
// allocate the set of log files
fileSet = new LogFile[maxLogFiles];
for (int i=0; i= 0)
{
fileSet[i].close();
setLockOnFile(fileSet[i].file, false);
fileSet[i] = null;
}
// TODO: output log message
System.err.println(this.getClass().getName() + ".open(); " + e);
throw e;
}
}
currentLogFile = null;
}
/**
* validate LogFiles and set member variables.
*
* activeMark set based on last block written during previous execution.
*
currentKey set to key of the last record written to the log.
*
currentLogFile set to the next available LogFile in fileSet[] with
* file position set to resume writing at the next block following the last block
* written.
*
*/
void init(LogBufferManager bmgr)
throws IOException,
LogConfigurationException, InvalidLogBufferException, InterruptedException
{
this.bmgr = bmgr;
short lfIndex = 0;
int bsn = 0;
LogFile lf = null;
LogBuffer lb = null;
try {
lb = bmgr.getLogBuffer(-1);
} catch (ClassNotFoundException e) {
throw new LogConfigurationException("LogBuffer.class not found", e);
}
for (short i = 0; i < fileSet.length; ++i)
{
lf = fileSet[i];
assert lf != null : "LogFile pointer lf is null";
// skip newly created files
if (lf.newFile) continue;
try {
lb.read(lf, 0L);
} catch (IOException e) {
// BUG 303907 -- add message to IOException
IOException ioe = new IOException("LogFileManager.init(): error reading block zero " +
lf.file.getName() + " [" + e.getMessage() + "]");
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
// save BSN of first block in file
lf.firstBSN = lb.bsn;
// locate the last file to be written
if (lb.bsn > bsn)
{
bsn = lb.bsn;
lfIndex = i;
}
}
/*
* If we get this far then all the files are
* properly formatted or new.
*
* We can now set the highMark for each file
* using the firstBSN field of the subsequent file.
*/
int fsl = fileSet.length;
for (int i=0; i < fsl; ++i)
{
if (fileSet[i].newFile) continue;
int next = (i + 1) % fsl;
if (fileSet[next].newFile) continue;
fileSet[i].highMark = bmgr.markFromBsn(fileSet[next].firstBSN, 0);
}
// reposition the last active file
int blockSize = lb.buffer.capacity();
currentLogFile = fileSet[lfIndex];
lf = currentLogFile;
// compare file header with current configuration
validateFileHeader(lb);
long fpos = blockSize;
if (lb.bsn > 0)
{
// locate last block written starting in second block
try {
while(lb.read(lf, fpos).bsn > bsn) {
fpos += blockSize;
bsn = lb.bsn;
}
} catch (InvalidLogBufferException e) {
/*
* Ignore this exception here during restart processing.
* An invalid block marks the end of the log.
*/
System.err.println(e);
} catch (IOException e) {
IOException ioe = new IOException("LogFileManager.init(): " +
"Attempting to loacate last block of file " + lf.file.getName() +
" at position " + fpos + " [" + e.getMessage() + "]");
ioe.setStackTrace(e.getStackTrace());
ioe.printStackTrace();
throw ioe;
}
}
// set this.lfIndex to next file to be used
this.lfIndex = (lf.newFile ? 0 : (lfIndex + 1));
// tell LogBufferManager the last BSN that was written
bmgr.init(this, bsn);
// remember the initial key for this execution
initialKey = bmgr.markFromBsn(bsn + 1, 0);
// initialize high water mark for active LogFile
lf.highMark = initialKey;
// initialize currentKey
currentKey = initialKey;
// process MARK control records in last block
assert fpos > 0 : "Unexpected file postion: " + fpos;
if (bsn > 0)
{
try {
lb.read(lf, fpos-blockSize);
} catch (IOException e) {
// BUG 303907 - add message to IOException
IOException ioe = new IOException("LogFileManager.init(): " +
"process MARK records in last block of file " +
lf.file.getName() +
" at position " + (fpos - blockSize) + " [" + e.getMessage() + "]");
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
LogRecord record = new LogRecord(lb.buffer.capacity());
ByteBuffer dataBuffer = record.dataBuffer;
while (!record.get(lb).isEOB()) {
if (record.type == LogRecordType.MARKKEY) {
dataBuffer.clear();
dataBuffer.getShort(); // not using field size
automark = dataBuffer.get() == 1 ? true : false;
activeMark = dataBuffer.getLong();
}
}
currentKey = record.key;
}
else {
// new files
fpos = 0L;
activeMark = bmgr.markFromBsn(1, 0); // BUG 304331
}
// update activeMark based on automark setting recovered from log
// @see validateFileHeader
if (automark) activeMark = currentKey;
// position current log file for writing
try {
lf.channel.position(fpos);
} catch (IOException e) {
// BUG 303907 - add message text to IOException
IOException ioe = new IOException("LogFileManager.init(): position log file " +
lf.file.getName() + " for writing at position " + fpos + " [" + e.getMessage() + "]");
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
// TODO: move code so it only gets executed for writeable logs
// Write a RESTART record for replay
if (false) {
short type = LogRecordType.RESTART;
try {
bmgr.put(type, new byte[0][0], false);
} catch (InterruptedException e) {
throw e;
} catch (LogException e) {
// should not happen, but if it does, we just ignore it here
assert false : "unhandled LogException" + e.toString();
} catch (IOException e) {
// BUG 303907 - add error message to IOException and rethrow
IOException ioe = new IOException("LogFileManager.init(): error writing RESTART record to file" +
lf.file.getName() + " [" + e.getMessage() + "]");
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
}
// indicate that initialization is complete
initComplete = true; // BUG 300934
// TODO: validate information against state file.
// a. define a state file and update during close
// b. validate current log information against state file
}
/**
* Compares values in log file header record with current configuration.
*
Throws LogConfigurationException if header does not match current
* configuration.
*/
void validateFileHeader(LogBuffer lb)
throws LogConfigurationException, IOException, InvalidLogBufferException
{
LogFile lf = currentLogFile;
lb.read(lf, 0L);
if (lb.bsn == -1) return; // end of file or empty file
LogRecord fh = new LogRecord(fileHeader[0].length);
fh.get(lb);
if (fh.type != LogRecordType.FILE_HEADER)
{
throw new InvalidLogBufferException("HEADER_TYPE: " + Integer.toHexString(fh.type));
}
if (fh.length != fileHeader[0].length + 2)
{
throw new InvalidLogBufferException("HEADER_SIZE: expected length(" +
(fileHeader[0].length + 2) + ") found (" + fh.length + ")");
}
// we have a file header -- validate the data
ByteBuffer dataBuffer = fh.dataBuffer;
dataBuffer.clear();
dataBuffer.getShort(); // field length not used
automark = dataBuffer.get() == 1 ? true : false;
activeMark = dataBuffer.getLong();
dataBuffer.getLong(); // long highMark -- avoid unreferenced field warning
dataBuffer.getLong(); // long switchTod -- avoid unreferenced field warning
/*
* Mak sure we have at least as many files as we had
* when the fileset was created initially.
*/
int nFiles = dataBuffer.getInt();
if (nFiles != fileSet.length)
throw new LogConfigurationException("Current configuration number of files [" +
fileSet.length + "] not equal number of files in set [" + nFiles + "]");
/*
* Make sure the configured file size is the same as
* it was when the fileset was created.
*/
int nBlocks = dataBuffer.getInt();
if (nBlocks != maxBlocksPerFile)
throw new LogConfigurationException("Configured file size [" +
maxBlocksPerFile + "] blocks not equal previous file size [" + nBlocks + "] blocks");
short crlf = dataBuffer.getShort();
if (crlf != 0x0D0A)
throw new InvalidLogBufferException("FILE_HEADER: expecting CRLF found " +
Integer.toHexString(crlf));
assert dataBuffer.capacity() == dataBuffer.position()
: "byte[] fileHeader size error";
}
/**
* Write a CLOSE record and shut down the buffer manager
* if we have one.
*
This routine was originally inline in close(). It
* was refactored into a separate method to improve
* readability of close().
*
* @throws InterruptedException
* @throws IOException
*/
void closeBufferManager() throws InterruptedException, IOException
{
// Wait for all application data to be flushed to disk
bmgr.flushAll();
// save current configuration details to the log
byte[][] closeData = new byte[1][2];
ByteBuffer closeDataBuffer = ByteBuffer.wrap(closeData[0]);
closeDataBuffer.clear();
// TODO: define content of closeData record
closeDataBuffer.put(crlf);
try {
bmgr.put(LogRecordType.CLOSE, closeData, false);
} catch (LogRecordSizeException e) {
// will never happen but use assert to catch during development
assert false : "Unhandled LogRecordSizeException" + e;
} catch (LogFileOverflowException e) {
// ignore -- we will discover this the next time we open the logs
// TODO: write message to system log
} catch (IOException e) {
// BUG 303907 - add message to IOException
IOException ioe = new IOException("LogFileManager.closeBufferManager(): error writing CLOSE record."
+ " [" + e.getMessage() + "]");
ioe.setStackTrace(e.getStackTrace());
throw ioe;
}
// Wait for logger information to flush to disk
bmgr.flushAll();
// Allow buffer manager to shut down any helper threads
bmgr.close();
}
/**
* Gracefully close the log files.
*
* @throws IOException
* If FileChannel.close() encounters an error.
* @see java.nio.channels.FileChannel#close()
*/
void close()
throws IOException, InterruptedException
{
// TODO: record the fact that the LOG was closed
// write a status record at end of current journal.
// logger will have to locate the status record during a restart.
// status should include mark information, number of files and
// max blocks per file.
// alternative: write status to a separate log.status file
boolean interrupted = false;
InterruptedException exception = null;
// BUG 300953 don't close if fileSet[] is null
if (fileSet == null) return;
try {
// BUG 300934 don't close Buffer Manager if initialization is not complete
if (initComplete) closeBufferManager(); // BUG 300934
initComplete = false; // don't close Buffer Manager more than once
} catch (InterruptedException e) {
interrupted = true; // remember and throw it on the way out.
exception = e;
}
// shut down the event manager thread before we close the files
if (eventManagerThread != null)
eventManagerThread.interrupt();
// close the log files
for (int i=0; i < fileSet.length; ++i)
{
if (fileSet[i] != null)
{
fileSet[i].close();
setLockOnFile(fileSet[i].file, false);
}
}
if (interrupted) throw exception;
}
/**
* Returns an XML node containing statistics for the LogFileManager.
*
The nested element contain entries for each
* LogFile object in the set of log files.
*
* @return a String containing statistics as an XML node.
*/
String getStats()
{
String name = this.getClass().getName();
StringBuffer stats = new StringBuffer(
"\n"
);
// Append LogFileManager stats here
stats.append("\n" +
"Initial Log Key" +
" " +
"\n" +
"Current Log Key" +
" " +
"\n" +
"automark value restored from prior log file" +
" " +
"\n" +
"number of times LogEventListener.logOverflowNotification was called" +
" "
);
long totalBytesWritten = 0L;
for (int i=0; i < fileSet.length; ++i)
totalBytesWritten += fileSet[i].bytesWritten;
stats.append("\n");
for (int i = 0; i < fileSet.length; ++i)
stats.append(fileSet[i].getStats());
stats.append("\n ");
stats.append("\n ");
return stats.toString();
}
/**
* helper thread used invoke LogEventListener when
* log overflow (or other event) is about to occur.
*
*/
class EventManager extends Thread
{
EventManager(String name)
{
super(name);
}
/**
* Wait on eventManagerLock until
* LogFileManager needs an event to be
* sent to the registered LogEventListener.
*/
public void run()
{
// provide access to members in containing class
LogFileManager parent = LogFileManager.this;
int event;
long lowestSafeLogKey;
while(true)
{
if (interrupted()) return;
synchronized(eventManagerLock)
{
try {
while ((event = parent.event) == 0)
{
eventManagerLock.wait();
}
} catch (InterruptedException e) {
// we've been shut down.
return;
}
lowestSafeLogKey = parent.lowestSafeLogKey;
}
if ((event & LOG_OVERFLOW_EVENT) != 0)
{
if (eventListener != null)
{
try {
// protect HOWL from RuntimeExceptions in application code
eventListener.logOverflowNotification(lowestSafeLogKey);
} catch (Exception e) {
e.printStackTrace();
}
// update count of notifications
parent.overflowNotificationCount += 1;
}
synchronized(eventManagerLock)
{
parent.lowestSafeLogKey = 0;
parent.event ^= LOG_OVERFLOW_EVENT;
}
}
else
{
assert false :
"unexpected event type [" +
event +
"] in LogFileManager eventManagetThread";
}
}
}
}
}