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

src.java.org.objectweb.howl.log.LogFileManager Maven / Gradle / Ivy

There is a newer version: 10.0.0-M3
Show newest version
/*
 * 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: *

    *
  1. locate the entry within fileSet[] that contains the * requested bsn. *
  2. 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"; } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy