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

org.objectweb.howl.log.LogBufferManager Maven / Gradle / Ivy

The 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: LogBufferManager.java,v 1.28 2005/11/29 23:09:48 girouxm Exp $
 * ------------------------------------------------------------------------------
 */
package org.objectweb.howl.log;


import java.io.IOException;

import java.lang.InterruptedException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Provides a generalized buffer manager for journals and loggers.
 *
 * 

log records are written to disk as blocks * of data. Block size is a multiple of 512 data * to assure optimum disk performance. */ class LogBufferManager extends LogObject { /** * @param config Configuration object */ LogBufferManager(Configuration config) { super(config); threadsWaitingForceThreshold = config.getThreadsWaitingForceThreshold(); forceRequired = config.getLogFileMode().equals("rw"); flushPartialBuffers = config.isFlushPartialBuffers(); flushManager = new FlushManager(flushManagerName); flushManager.setDaemon(true); // so we can shutdown while flushManager is running flushManager.start(); // BUG 303659 } /** * @see Configuration#flushPartialBuffers */ private final boolean flushPartialBuffers; /** * boolean is set true when an IOException is returned * by a write or force to a log file. *

Any attempt to write or force after haveIOException * becomes true should result in an IOException being returned * to the caller. */ private boolean haveIOException = false; // BUG 300803 /** * The last IOException returned to the logger */ private IOException ioexception = null; // BUG 300803 /** * mutex for synchronizing access to buffers list. *

also synchronizes access to fqPut in routines * that put LogBuffers into the forceQueue[]. */ private final Object bufferManagerLock = new Object(); /** * mutex for synchronizing threads through the * portion of force() that forces the channel. */ private final Object forceManagerLock = new Object(); /** * reference to LogFileManager that owns this Buffer Manager instance. * * @see LogFileManager#getLogFileForWrite(LogBuffer) */ private LogFileManager lfm = null; /** * indicates if a force() must be called in the flush() method. *

Set false in constructor if config.getLogFileMode() is "rwd". */ final boolean forceRequired; /** * The LogBuffer that is currently being filled. */ private LogBuffer fillBuffer = null; /** * array of LogBuffer objects available for filling */ private LogBuffer[] freeBuffer = null; /** * array of all LogBuffer objects allocated. *

Used to find and debug buffers that are not in the * freeBuffer list if logger hangs waiting * for buffers to be returned to the freeBuffer pool. */ private LogBuffer[] bufferList = null; /** * workerID into freeBuffer list maintained in getBuffer. */ short nextIndex = 0; /** * number of times there were no buffers available. * *

The FlushManager thread monitors this field * to determine if the buffer pool needs to be * grown. */ private long waitForBuffer = 0; /** * number of times buffer was forced because it is full. */ private long noRoomInBuffer = 0; /** * number of times buffer size was increased because * of threads waiting for buffers. */ private int growPoolCounter = 0; /** * next block sequence number for fillBuffer. */ int nextFillBSN = 1; /** * next BSN to be written to log. *

synchronized by forceManagerLock */ int nextWriteBSN = 1; /** * LogBuffer.tod from previous buffer written. *

maintained in force() method. Used to * check against decrement in TOD field. * Added to help investigate BUG 303907 */ long prevWriteTOD = 0; /** * number of buffers waiting to be forced. *

synchronized by bufferManagerLock. *

incremented in put() and decremented in releaseBuffer(). * When a thread calls put() with sync parameter set true, * and buffersWaitingForce is also zero, then put() causes * the buffer to be forced immediately. This strategy * minimizes latency in situations of low load, such as * a single thread running. */ int buffersWaitingForce = 0; /** * last BSN forced to log. *

synchronized by forceManagerLock */ int lastForceBSN = 0; /** * number of times channel.force() called. */ private long forceCount = 0; /** * number of times channel.write() called. */ private long writeCount = 0; /** * minimum number of buffers forced by channel.force(). */ private int minBuffersForced = Integer.MAX_VALUE; /** * maximum number of buffers forced by channel.force() */ private int maxBuffersForced = Integer.MIN_VALUE; /** * total amount of time spent in channel.force(); */ private long totalForceTime = 0; /** * total amount of time spent in channel.write(); */ private long totalWriteTime = 0; /** * maximum time (ms) for any single write */ private long maxWriteTime = 0; /** * total amount of time (ms) spent waiting for the forceMangerLock */ private long totalWaitForWriteLockTime = 0; /** * total time between channel.force() calls */ private long totalTimeBetweenForce = 0; private long minTimeBetweenForce = Long.MAX_VALUE; private long maxTimeBetweenForce = Long.MIN_VALUE; /** * time of last force used to compute totalTimeBetweenForce */ private long lastForceTOD = 0; /** * number of threads waiting for a force */ private int threadsWaitingForce = 0; private int maxThreadsWaitingForce = 0; private long totalThreadsWaitingForce = 0; private int threadsWaitingForceThreshold = 0; // reasons for doing force long forceOnTimeout = 0; long forceNoWaitingThreads = 0; long forceHalfOfBuffers = 0; long forceMaxWaitingThreads = 0; long forceOnFileSwitch = 0; /** * thread used to flush long waiting buffers */ final FlushManager flushManager; // BUG 303659 change type from Thread to FlushManager so we can access isClosed /** * name of flush manager thread */ private static final String flushManagerName = "FlushManager"; /** * queue of buffers waiting to be written. The queue guarantees that * buffers are written to disk in BSN order. Buffers are placed into * the forceQueue using the fqPut workerID, and removed from the forceQueue * using the fqGet workerID. Access to these two workerID members is * synchronized using separate objects to allow most threads to be * storing log records while a single thread is blocked waiting for * a physical force. * *

Buffers are added to the queue when put() detects the buffer is full, * and when the FlushManager thread detects that a buffer has waited * too long to be written. The fqPut member is the workerID of * the next location in forceQueue to put a LogBuffer that is to * be written. fqPut is protected by bufferManagerLock . * *

Buffers are removed from the queue in force() and written to disk. * The fqGet member is an workerID to the next buffer to remove * from the forceQueue. fqGet is protected by * forceManagerLock . * *

The size of forceQueue[] is one larger than the size of freeBuffer[] * so that fqPut == fqGet always means the queue is empty. */ private LogBuffer[] forceQueue = null; /** * next put workerID into forceQueue . *

synchronized by bufferManagerLock. */ private int fqPut = 0; /** * next get workerID from forceQueue . *

synchronized by forceManagerLock. */ private int fqGet = 0; /** * compute elapsed time for an event * @param startTime time event began * @return elapsed time (System.currentTimeMillis() - startTime) */ final long elapsedTime(long startTime) { return System.currentTimeMillis() - startTime; } /** * forces buffer to disk. * *

batches multiple buffers into a single force * when possible. * *

Design Note:
* It was suggested that using forceManagerLock to * control writes from the forceQueue[] and forces * would reduce overlap due to the amount of time * that forceManagerLock is shut while channel.force() * is active. *

Experimented with using two separate locks to * manage the channel.write() and the channel.force() calls, * but it appears that thread calling channel.force() * will block another thread trying to call channel.write() * so both locks end up being shut anyway. * Since two locks did not provide any measurable benefit, * it seems best to use a single forceManagerLock * to keep the code simple. */ private void force(boolean timeout) throws IOException, InterruptedException { LogBuffer logBuffer = null; long startWait = System.currentTimeMillis(); synchronized(forceManagerLock) // write buffers in ascending BSN sequence { totalWaitForWriteLockTime += elapsedTime(startWait); logBuffer = forceQueue[fqGet]; // logBuffer stuffed into forceQ forceQueue[fqGet] = null; // so someone using debug doesn't think it is in the queue fqGet = (fqGet + 1) % forceQueue.length; if (haveIOException) { // BUG 300803 - do not try the write if we already have an error // but we have to increment count of waitingThreads so count // does not go negative synchronized(logBuffer.waitingThreadsLock) { logBuffer.waitingThreads += 1; } } else { // write the logBuffer to disk (hopefully non-blocking) try { assert logBuffer.bsn == nextWriteBSN : "BSN error expecting " + nextWriteBSN + " found " + logBuffer.bsn; assert logBuffer.tod > prevWriteTOD : "TOD error at BSN: " + logBuffer.bsn; long startWrite = System.currentTimeMillis(); logBuffer.write(); long writeTime = elapsedTime(startWrite); totalWriteTime += writeTime; if (writeTime > maxWriteTime) maxWriteTime = writeTime; ++writeCount; nextWriteBSN = logBuffer.bsn + 1; } catch (IOException ioe) { // BUG 300803 - remember that we had an error // BUG 303907 add a message to the IOException ioexception = new IOException("LogBufferManager.force(): writing " + logBuffer.lf.file.getName() + "[" + ioe.getMessage() + "]"); ioexception.setStackTrace(ioe.getStackTrace()); haveIOException = true; } } threadsWaitingForce += logBuffer.getWaitingThreads(); // NOTE: following is not synchronized so the stats may be inaccurate. if (threadsWaitingForce > maxThreadsWaitingForce) maxThreadsWaitingForce = threadsWaitingForce; /* * The lastForceBSN member is updated by the thread * that actually does a force(). All threads * waiting for the force will detect the change * in lastForceBSN and notify any waiting threads. */ // force() is guaranteed to have forced everything that // has been written prior to the force, so get the // bsn for the last known write prior to the force. int forcebsn = nextWriteBSN - 1; boolean doforce = true; /* * 2004-06-25 Michael Giroux * Remove test for logBuffer.bsn < forcebsn. This cannot * happen now that we stay in the forceManagerLock. * * Rearranged tests to improve the accuracy of the counters. * * 2004-09-09 Michael Giroux * BUG 300803 - Add test for IOException */ if (haveIOException) { doforce = false; } else if (timeout) { ++forceOnTimeout; } else if ((forcebsn - lastForceBSN) > (freeBuffer.length/2)) { // one half of the buffers are waiting on the force ++forceHalfOfBuffers; } else if (threadsWaitingForce > threadsWaitingForceThreshold) { // number of waiting threads exceeds configured limit ++forceMaxWaitingThreads; } else if (logBuffer.forceNow) { // number of times we forced due to switch to next log file ++forceOnFileSwitch; } else if (fqGet == fqPut) { // no other logBuffers waiting in forceQueue ++forceNoWaitingThreads; } else { doforce = false; } if (doforce) { ++forceCount; long startForce = System.currentTimeMillis(); try { logBuffer.lf.force(false); } catch (IOException ioe) { // BUG 303907 add a message to the IOException ioexception = new IOException("LogBufferManager.force(): error attempting to force " + logBuffer.lf.file.getName() + "[" + ioe.getMessage() + "]"); ioexception.setStackTrace(ioe.getStackTrace()); haveIOException = true; logBuffer.ioexception = ioe; } totalForceTime += elapsedTime(startForce); if (lastForceTOD > 0) { long timeBetweenForce = startForce - lastForceTOD; totalTimeBetweenForce += timeBetweenForce; minTimeBetweenForce = Math.min(minTimeBetweenForce, timeBetweenForce); if (!timeout) { maxTimeBetweenForce = Math.max(maxTimeBetweenForce, timeBetweenForce); } } lastForceTOD = System.currentTimeMillis(); if (lastForceBSN > 0) { int buffersForced = forcebsn - lastForceBSN; maxBuffersForced = Math.max(maxBuffersForced, buffersForced); minBuffersForced = Math.min(minBuffersForced, buffersForced); } totalThreadsWaitingForce += threadsWaitingForce; threadsWaitingForce = 0; lastForceBSN = forcebsn; } // notify everyone who is waiting for the force if (doforce || haveIOException) forceManagerLock.notifyAll(); // wait for thisLogBuffer's write to be forced while (!haveIOException && lastForceBSN < logBuffer.bsn) { forceManagerLock.wait(); } } // synchronized(forceManagerLock) // notify threads waiting for this buffer to force synchronized(logBuffer) { // BUG: 300613 must synchronize the update of iostatus if (logBuffer.iostatus == LogBufferStatus.WRITING) logBuffer.iostatus = LogBufferStatus.COMPLETE; // BUG: 300803 report error to threads that are waiting if (haveIOException) { logBuffer.iostatus = LogBufferStatus.ERROR; logBuffer.ioexception = ioexception; } logBuffer.notifyAll(); } releaseBuffer(logBuffer); // BUG 300803 report error to our caller if (haveIOException) throw ioexception; } /** * Waits for logBuffer to be forced to disk. * *

No monitors are owned when routine is entered. *

Prior to calling sync(), the thread called put() * with sync param set true to register * the fact that the thread would wait for the force. */ private void sync(LogBuffer logBuffer) throws IOException, InterruptedException { try { logBuffer.sync(); } finally { releaseBuffer(logBuffer); } } /** * decrements count of threads waiting on this buffer. *

If count goes to zero, buffer is returned to * the freeBuffer list, and any threads waiting for * a free buffer are notified. * @param buffer LogBuffer to be released * @see #buffersWaitingForce */ private void releaseBuffer(LogBuffer buffer) { if (buffer.release() == 0) { synchronized(bufferManagerLock) { freeBuffer[buffer.index] = buffer; bufferManagerLock.notifyAll(); --buffersWaitingForce; assert buffersWaitingForce >= 0 : "buffersWaitingForce (" + buffersWaitingForce + ") < 0"; } } } /** * returns a LogBuffer to be filled. * *

PRECONDITION: caller holds bufferManagerLock monitor. * * @return a LogBuffer to be filled. */ private LogBuffer getFillBuffer() throws LogFileOverflowException { if (fillBuffer == null) // slight optimization when fillBuffer != null { int fbl = freeBuffer.length; for(int i=0; fillBuffer == null && i < fbl; ++i) { nextIndex %= fbl; if (freeBuffer[nextIndex] != null) { LogBuffer b = freeBuffer[nextIndex]; freeBuffer[nextIndex] = null; try { fillBuffer = b.init(nextFillBSN, lfm); } catch (LogFileOverflowException e) { // BUG 300956 - return buffer to free list to prevent hang in close. freeBuffer[nextIndex] = b; throw e; } ++nextFillBSN; } ++nextIndex; } } return fillBuffer; } /** * return a new instance of LogBuffer. *

Actual LogBuffer implementation class is specified by * configuration. * * @return a new instance of LogBuffer */ LogBuffer getLogBuffer(int index) throws ClassNotFoundException { LogBuffer lb = null; Class lbcls = this.getClass().getClassLoader().loadClass(config.getBufferClassName()); try { Constructor lbCtor = lbcls.getDeclaredConstructor(new Class[] { Configuration.class } ); lb = (LogBuffer)lbCtor.newInstance(new Object[] {config}); lb.index = index; } catch (InstantiationException e) { throw new ClassNotFoundException(e.toString()); } catch (IllegalAccessException e) { throw new ClassNotFoundException(e.toString()); } catch (NoSuchMethodException e) { throw new ClassNotFoundException(e.toString()); } catch (IllegalArgumentException e) { throw new ClassNotFoundException(e.toString()); } catch (InvocationTargetException e) { throw new ClassNotFoundException(e.toString()); } return lb; } /** * Add a buffer to the forceQueue. *

PRECONDITION: bufferManagerLock owned by caller * @param buffer LogBuffer to be added to the forceQueue */ void fqAdd(LogBuffer buffer) { fillBuffer = null; try { forceQueue[fqPut] = buffer; } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); throw e; } fqPut = (fqPut + 1) % forceQueue.length; ++buffersWaitingForce; // BUG 303660 } /** * writes data byte[][] to log and returns a log key. *

waits for IO to complete if sync is true. * *

MG 27/Jan/05 modified code to force buffer if caller * has set sync == true, and there are no buffers waiting * to be written. This causes buffers to be written * immediately in a single threaded and/or low volume * situation. Change suggested by developers at ApacheCon * and at ObjectWebCon. This feature is disabled by default * and is enabled by setting the log configuration property * XXX to true. * * @return token reference (log key) for record just written * @throws LogRecordSizeException * when size of byte[] is larger than the maximum possible * record for the configured buffer size. * * @see #buffersWaitingForce */ long put(short type, byte[][] data, boolean sync) throws LogRecordSizeException, LogFileOverflowException, InterruptedException, IOException { long token = 0; LogBuffer currentBuffer = null; boolean forceNow = false; do { // allocate the current fillBuffer synchronized(bufferManagerLock) { while((currentBuffer = getFillBuffer()) == null) { ++waitForBuffer; bufferManagerLock.wait(); } token = currentBuffer.put(type, data, sync); if (sync && buffersWaitingForce == 0) { forceNow = flushPartialBuffers; // TODO: log this event level DEBUG } if (token == 0 || forceNow) { fqAdd(currentBuffer); } } if (token == 0) { // force current buffer if there was no room for data ++noRoomInBuffer; force(false); } else if (forceNow) { force(true); releaseBuffer(currentBuffer); } else if (sync) // otherwise sync as requested by caller { sync(currentBuffer); } } while (token == 0); return token; } /** * Force the current buffer to disk * before starting a replay(). */ void forceCurrentBuffer() throws IOException { LogBuffer buffer = null; synchronized(bufferManagerLock) { if (fillBuffer != null) { buffer = fillBuffer; fqAdd(buffer); } } // release bufferManagerLock before we issue a force. if (buffer != null) { try { force(true); } catch (InterruptedException e) { ; // ignore } } } /** * Replays log from requested mark forward to end of log. * *

Blocks caller until replay completes due to end of log, * or an exception is passed to listener.onError(). * * @param listener ReplayListener to receive notifications for each log record. * @param mark log key for the first record to be replayed. *

If mark is zero then the entire active log is replayed. * @param replayCtrlRecords indicates whether to return control records. *

used by utility routines such as CopyLog. * * @throws InvalidLogKeyException * if the requested key is not found in the log. * * @see org.objectweb.howl.log.Logger#replay(ReplayListener, long) */ void replay(ReplayListener listener, long mark, boolean replayCtrlRecords) throws LogConfigurationException, InvalidLogKeyException { int bsn = bsnFromMark(mark); if (mark < 0 || (bsn == 0 && mark != 0)) throw new InvalidLogKeyException(Long.toHexString(mark)); LogBuffer buffer = null; // get a LogBuffer for reading try { buffer = getLogBuffer(-1); } catch (ClassNotFoundException e) { throw new LogConfigurationException(e.toString()); } // get a LogRecord from caller LogRecord record = listener.getLogRecord(); record.buffer = buffer; // read block containing requested mark try { forceCurrentBuffer(); lfm.read(buffer, bsn); } catch (IOException e) { String msg = "Error reading " + buffer.lf.file + " @ position [" + buffer.lf.position + "]"; listener.onError(new LogException(msg + e.toString())); return; } catch (InvalidLogBufferException e) { listener.onError(new LogException(e.toString())); return; } if (buffer.bsn == -1) { // BUG 300733 if mark is 0L then we must have new // files so return END_OF_LOG. // otherwise throw an InvalidLogKeyException if (mark == 0 || mark == lfm.getHighMark()) { record.type = LogRecordType.END_OF_LOG; listener.onRecord(record); return; } else { String msg = "The mark [" + Long.toHexString(mark) + "] requested for replay was not found in the log. " + "activeMark is [" + Long.toHexString(lfm.activeMark) + "]"; throw new InvalidLogKeyException(msg); } } // verify we have the desired block // if requested mark == 0 then we start with the oldest block available int markBSN = (mark == 0) ? buffer.bsn : bsnFromMark(mark); if (markBSN != buffer.bsn) { InvalidLogBufferException lbe = new InvalidLogBufferException( "block read [" + buffer.bsn + "] not block requested: " + markBSN); listener.onError(lbe); return; } /* * position buffer to requested mark. * * Although the mark contains a buffer offset, we search forward * through the buffer to guarantee that we have the start * of a record. This protects against using marks that were * not generated by the current Logger. */ try { record.get(buffer); // BUG 300720 - active mark might be set to offset zero // by XALogger.logOverflowNotification if (mark > 0 && mark > markFromBsn(markBSN,0)) { while(record.key < mark) { record.get(buffer); } if (record.key != mark) { String msg = "The initial mark [" + Long.toHexString(mark) + "] requested for replay was not found in the log."; // BUG 300733 following line changed to throw an exception throw new InvalidLogKeyException(msg); } } } catch (InvalidLogBufferException e) { listener.onError(new LogException(e.toString())); return; } /* * If we get this far then we have found the requested mark. * Replay the log starting at the requested mark through the end of log. */ long nrecs = 0; int nextBSN = 0; while (true) { if (record.isEOB()) { // read next block from log nextBSN = buffer.bsn + 1; try { lfm.read(buffer, nextBSN); } catch (IOException e) { listener.onError(new LogException(e.toString())); return; } catch (InvalidLogBufferException e) { listener.onError(new LogException(e.toString())); return; } // return end of log indicator if (buffer.bsn == -1 || buffer.bsn < nextBSN) { record.type = LogRecordType.END_OF_LOG; listener.onRecord(record); return; } } else if (!record.isCTRL() || replayCtrlRecords) { listener.onRecord(record); } ++nrecs; // get next record try { record.get(buffer); } catch (InvalidLogBufferException e) { listener.onError(e); return; } } } /** * Allocate pool of IO buffers for Logger. * *

The LogBufferManager class is a generalized manager for any * type of LogBuffer. The class name for the type of LogBuffer * to use is specified by configuration parameters. * * @throws ClassNotFoundException * if the configured LogBuffer class cannot be found. */ void open() throws ClassNotFoundException { int bufferPoolSize = config.getMinBuffers(); freeBuffer = new LogBuffer[bufferPoolSize]; bufferList = new LogBuffer[bufferPoolSize]; for (short i=0; i< bufferPoolSize; ++i) { freeBuffer[i] = getLogBuffer(i); bufferList[i] = freeBuffer[i]; // bufferList used to debug } synchronized(forceManagerLock) { // one larger than bufferPoolSize to guarantee we never overrun this queue forceQueue = new LogBuffer[bufferPoolSize + 1]; fqPut = 0; // BUG 304299 fqGet = 0; // BUG 304299 } // inform flushManager that LogBufferManager is ready for operation if (flushManager != null) { flushManager.isClosed = false; // BUG 303659 } } /** * Shutdown any threads that are started by this LogBufferManager instance. */ void close() { // inform the flush manager thread if (flushManager != null) flushManager.isClosed = true; // BUG 303659 } /** * perform initialization following reposition of LogFileManager. * * @param lfm LogFileManager used by the buffer manager to obtain * log files for writing buffers. * @param bsn last Block Sequence Number written by Logger. */ void init(LogFileManager lfm, int bsn) { assert lfm != null : "LogFileManager parameter is null"; this.lfm = lfm; nextFillBSN = bsn + 1; synchronized(forceManagerLock) { nextWriteBSN = nextFillBSN; } } /** * flush active buffers to disk and wait for all LogBuffers to * be returned to the freeBuffer pool. * *

May be called multiple times. */ void flushAll() throws IOException { LogBuffer buffer = null; try { // BUG 303659 prevent hang if FlushManager thread has stopped // move current fillBuffer to forceQueue synchronized(bufferManagerLock) { if (fillBuffer != null) { buffer = fillBuffer; fqAdd(buffer); } } // release bufferManagerLock before we issue a force. if (buffer != null) { force(true); } // wait until all buffers are returned to the freeBuffer pool for (int i=0; i < freeBuffer.length; ++i) { synchronized(bufferManagerLock) { while(freeBuffer[i] == null) { bufferManagerLock.wait(100); // wait 100 ms at a time to avoid risk of missing a notify } } } } catch (InterruptedException e) { // ignore it } } /** * convert a double to String with fixed number of decimal places * @param val double to be converted * @param decimalPlaces number of decimal places in output * @return String result of conversion */ private String doubleToString(double val, int decimalPlaces) { String s = "" + val; int dp = s.indexOf('.') + 1; // include the decimal point if (s.length() > dp + decimalPlaces) { s = s.substring(0, dp + decimalPlaces); } return s; } /** * Returns an XML node containing statistics for the LogBufferManager. *

The nested element contains entries for each * LogBuffer object in the buffer pool. * * @return a String containing statistics. */ String getStats() { String avgThreadsWaitingForce = doubleToString((totalThreadsWaitingForce / (double)forceCount), 2); String avgForceTime = doubleToString((totalForceTime / (double)forceCount), 2); String avgTimeBetweenForce = doubleToString((totalTimeBetweenForce / (double)forceCount), 2); String avgBuffersPerForce = doubleToString((writeCount / (double) forceCount), 2); String avgWriteTime = doubleToString((totalWriteTime / (double)writeCount), 2); String avgWaitForWriteLockTime = doubleToString((totalWaitForWriteLockTime / (double)writeCount), 2); String name = this.getClass().getName(); StringBuffer stats = new StringBuffer( "\n" + "\n Buffer Size (in bytes)" + /* BUG 300957 */ "\n Number of buffers in the pool" + "\n Initial number of buffers in the pool" + "\n Number of times buffer pool was grown" + "\n Wait for available buffer" + "\n Buffer full" + "\n " + "\n " + "\n Number of channel.write() calls" + "\n Total time (ms) spent in channel.write" + "\n Average channel.write() time (ms)" + "\n Maximum channel.write() time (ms)" + "\n Total time (ms) spent waiting for forceManagerLock to issue a write" + "\n Average time (ms) spent waiting for forceManagerLock to issue a write" + "\n " + "\n " + "\n Number of channel.force() calls" + "\n Total time (ms) spent in channel.force" + "\n Average channel.force() time (ms)" + "\n Total time (ms) between calls to channel.force()" + "\n Minimum time (ms) between calls to channel.force()" + "\n Maximum time (ms) between calls to channel.force()" + "\n Average time (ms) between calls to channel.force()" + "\n Average number of buffers per force" + "\n Minimum number of buffers forced" + "\n Maximum number of buffers forced" + "\n maximum threads waiting" + "\n Avg threads waiting force" + "\n " + "\n " + "\n " + "\n force because no other threads waiting on force" + "\n force due to 1/2 of buffers waiting" + "\n force due to max waiting threads" + "\n force last block prior to switching to next file" + "\n " + "\n " + "\n" ); /* * collect stats for each buffer that is in the freeBuffer list. * If log is active one or more buffers will not be in the freeBuffer list. * The only time we can be sure that all buffers are in the list is * when the log is closed. */ for (int i=0; i < freeBuffer.length; ++i) { if (freeBuffer[i] != null) stats.append(freeBuffer[i].getStats()); } stats.append( "\n" + "\n" + "\n" ); return stats.toString(); } /* ------------------------------------------------------------------ * MBean interfaces and methods * ------------------------------------------------------------------ */ public interface BufferPoolStatsMBean { public abstract int getBufferPoolCurrentSize(); public abstract int getBufferPoolInitialSize(); public abstract int getBufferPoolGrowCount(); } public class BufferPoolStats implements BufferPoolStatsMBean { public final int getBufferPoolCurrentSize() { return freeBuffer.length; } public final int getBufferPoolInitialSize() { return config.getMinBuffers(); } public final int getBufferPoolGrowCount() { return growPoolCounter; } } public interface ForceStatsMBean { public abstract double getAverageThreadsWaitingForce(); public abstract long getForceCount(); public abstract double getAverageForceTime(); public abstract int getMinTimeBetweenForce(); public abstract int getMaxTimeBetweenForce(); public abstract double getAverageTimeBetweenForce(); public abstract double getAverageBuffersPerForce(); public abstract int getMinBuffersForced(); public abstract int getMaxBuffersForced(); public abstract int getMaxThreadsWaitingForce(); } public class ForceStats implements ForceStatsMBean { public final double getAverageThreadsWaitingForce() { return totalThreadsWaitingForce / (double)forceCount; } public final long getForceCount() { return forceCount; } public final double getAverageForceTime() { return totalForceTime / (double)forceCount; } public final int getMinTimeBetweenForce() { return (int)minTimeBetweenForce; } public final int getMaxTimeBetweenForce() { return (int)maxTimeBetweenForce; } public final double getAverageTimeBetweenForce() { return totalTimeBetweenForce / (double)forceCount; } public final double getAverageBuffersPerForce() { return writeCount / (double) forceCount; } public final int getMinBuffersForced() { return minBuffersForced; } public final int getMaxBuffersForced() { return maxBuffersForced; } public final int getMaxThreadsWaitingForce() { return maxThreadsWaitingForce; } } public interface WriteStatsMBean { public abstract long getWriteCount(); public abstract double getAverageWriteTime(); public abstract double getMaximumWriteTime(); public abstract double getAverageWaitForWriteLockTime(); public abstract long getWaitForBuffer(); } public class WriteStats implements WriteStatsMBean { public final long getWriteCount() { return writeCount; } public final double getAverageWriteTime() { return totalWriteTime / (double)writeCount; } public final double getMaximumWriteTime() { return maxWriteTime / 1000.0; } public final double getAverageWaitForWriteLockTime() { return totalWaitForWriteLockTime / (double)writeCount; } public final long getWaitForBuffer() { LogBufferManager parent = LogBufferManager.this; return parent.getWaitForBuffer(); } } /** * returns the BSN value portion of a log key mark . * * @param mark log key or log mark to extract BSN from. * * @return BSN portion of mark */ int bsnFromMark(long mark) { return (int) (mark >> 24); } /** * generate a log mark (log key). * @param bsn Block Sequence Number. * @param offset offset within block. *

May be zero to allow access to the beginning of a block. * @return a log key. */ long markFromBsn(int bsn, int offset) { return ((long)bsn << 24) | offset; } /** * provides synchronized access to waitForBuffer * @return the current value of waitForBuffer */ public final long getWaitForBuffer() { synchronized(bufferManagerLock) { return waitForBuffer; } } /** * helper thread to flush buffers that have threads waiting * longer than configured maximum. * *

This thread is shut down by #close(). * @see #close() */ class FlushManager extends Thread { /** * prevents FlushManager from flushing buffers when true. *

Managed by setClosed() and tested by isClosed().

*

Initially true to prevent flush manager thread * from doing anything while open processing is going on.

*/ boolean isClosed = true; // BUG 303659 FlushManager(String name) { super(name); } public void run() { LogBuffer buffer = null; LogBufferManager parent = LogBufferManager.this; int flushSleepTime = config.getFlushSleepTime(); long waitForBuffer = parent.getWaitForBuffer(); for (;;) { if (interrupted()) return; try { sleep(flushSleepTime); // check for timeout every 50 ms if (isClosed) continue; // BUG 303659 - do nothing while LogBufferManager is closed /* * Dynamically grow buffer pool until number of waits * for a buffer is less than 1/2 the pool size. */ long bufferWaits = parent.getWaitForBuffer() - waitForBuffer; int maxBuffers = config.getMaxBuffers(); int increment = freeBuffer.length / 2; if (maxBuffers > 0) { // make sure max is larger than current (min) maxBuffers = Math.max(maxBuffers, freeBuffer.length); increment = Math.min(increment, maxBuffers - freeBuffer.length); } if ((increment > 0) && (bufferWaits > increment)) { // increase size of buffer pool if number of waits > 1/2 buffer pool size LogBuffer[] fb = new LogBuffer[freeBuffer.length + increment]; LogBuffer[] bl = new LogBuffer[fb.length]; // increase size of bufferList also. ++growPoolCounter; // initialize the new slots boolean haveNewArray = true; for(int i=freeBuffer.length; i < fb.length; ++i) { try { fb[i] = getLogBuffer(i); bl[i] = fb[i]; } catch (ClassNotFoundException e) { haveNewArray = false; break; } } if (haveNewArray) { LogBuffer[] fq = new LogBuffer[fb.length + 1]; // new forceQueue synchronized(bufferManagerLock) { // copy bufferList to new array for(int i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy