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

org.objectweb.howl.log.xa.XALogger 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: XALogger.java,v 1.19 2005/11/16 23:09:59 girouxm Exp $
 * ------------------------------------------------------------------------------
 */
package org.objectweb.howl.log.xa;

import java.io.IOException;
import java.nio.ByteBuffer;

import java.util.HashMap;

import org.objectweb.howl.log.Configuration;
import org.objectweb.howl.log.InvalidFileSetException;
import org.objectweb.howl.log.InvalidLogBufferException;
import org.objectweb.howl.log.InvalidLogKeyException;
import org.objectweb.howl.log.LogClosedException;
import org.objectweb.howl.log.LogConfigurationException;
import org.objectweb.howl.log.LogException;
import org.objectweb.howl.log.LogFileOverflowException;
import org.objectweb.howl.log.LogRecord;
import org.objectweb.howl.log.LogRecordSizeException;
import org.objectweb.howl.log.LogRecordType;
import org.objectweb.howl.log.Logger;
import org.objectweb.howl.log.LogEventListener;
import org.objectweb.howl.log.ReplayListener;

/**
 * A specialized subclass of {@link org.objectweb.howl.log.Logger}
 * intended to provide functionality required by any XA Transaction Manager.
 * 
 * 

This class provides wrappers for all public methods of Logger * to allow enforcement of policies that are in place for an XA TM log. * *

This class implements {@link org.objectweb.howl.log.LogEventListener}. * During logOverflowNotification processing, entries in the activeTx[] * are re-logged and the active mark is moved. * *

If the application has called setLogEventListener, then * Log events are forwarded to the application after they are * processed by this class. * * @author Michael Giroux * */ /* * @history *

 * 2004-09-01 FEATURE 300768
 *            open(ReplayListener) accepts null
 *            OpenReplayListener constructor accepts null
 *            replayActiveTx(ReplayListener) method added
 * 
*/ public class XALogger extends Logger implements LogEventListener { /** * array of transactions in COMMIT phase and * waiting for DONE. *

Array is grown as needed to accomodate * larger number of transactions in COMMITING state. *

When the Logger detects that a log file overflow * condition is about to occur * {@link org.objectweb.howl.log.LogEventListener#logOverflowNotification(long)} * is invoked to allow the application to move older records * forward in the log. This approach avoides the need to * move records forward every time a log file switch occurs. *

During the logOverflowNotification, activeTx[] is scanned * for entries with log keys older than the key specified * on the notification. Since this processing only needs to * occur when a log file is about to overflow, the array is * used as an alternative to a linked list to eliminate the * overhead of managing the linked list for every * log record. */ XACommittingTx[] activeTx = null; /** * array of available XACommittingTx objects. *

Each entry in the array is associated with * an entry in activeTx[]. *

    *
  • Entries are obtained using the atxGet member.
  • *
  • Entries are returned to the list using the atxPut member.
  • *
  • The activeTx and availableTx arrays are grown as needed.
  • *
*/ XACommittingTx[] availableTx = null; /** * workerID into availableTx[] used to remove an * entry. */ int atxGet = 0; /** * workerID into availableTx[] used to return (add) * an entry. */ int atxPut = 0; /** * number of used entries in activeTx table. *

table is grown when atxUsed is * equal to the size of the table and we are * trying to add one more entry. */ int atxUsed = 0; /** * maximum number of used entries. */ int maxAtxUsed = 0; /** * lock used to synchronize access to the * availableTx array, atxGet and atxPut * members. */ final Object activeTxLock = new Object(); /** * number of times the activeTx table was resized * to accomodate a larger number of transactions * in the COMMITTING state. */ int growActiveTxArrayCount = 0; /** * number of times log overflow notification event was called. */ int overflowNotificationCount = 0; /** * number of records moved by log overflow notification processor. */ int movedRecordCount = 0; /** * number of ms that threads waited for overflow processing to complete. */ long totalWaitForThis = 0L; /** * number of times threads waited for overflow processing to complete. */ int waitForThisCount = 0; /** * log key below which COMMIT records will be copied forward * by logOverflowNotification to avoid log overflow exceptions. * *

synchronized on this */ long overflowFence = 0L; /** * LogEventListener registered by TM that instantiated * this XALogger. * *

XALogger may pass some notifications on to the * TM via this.eventListener. */ LogEventListener eventListener = null; /** * Set true in open() to indicate that * replay is required prior to allowing any * calls to put() methods. * *

Calls to any put() method while * replayNeeded is true will cause * an exception to be thrown. */ boolean replayNeeded = true; /** * Resize the activeTx and availableTx tables * to accomodate larger number of transactions * in the COMMITTING state. * *

PRECONDITION: thread has activeTxLock shut */ private void growActiveTxArray() { // allocate new arrays XACommittingTx[] newActiveTx = new XACommittingTx[activeTx.length + 50]; XACommittingTx[] newAvailableTx = new XACommittingTx[newActiveTx.length]; // initialize new elements for (int i = activeTx.length; i < newActiveTx.length; ++ i) { newActiveTx[i] = null; newAvailableTx[i] = new XACommittingTx(i); } // calling thread already holds the lock, so this is really not necessary synchronized(activeTxLock) { // copy existing entries to new tables for (int i = 0; i < activeTx.length; ++ i) { newActiveTx[i] = activeTx[i]; newAvailableTx[i] = availableTx[i]; } // update pointers atxPut = 0; atxGet = activeTx.length; // activate the new tables activeTx = newActiveTx; availableTx = newAvailableTx; } ++growActiveTxArrayCount; } /** * Common initialization for all constructors. */ private void init() { // allocate a table of active transaction objects activeTx = new XACommittingTx[50]; for (int i=0; i < activeTx.length; ++i) activeTx[i] = null; // allocate and initialize the table of indexes // for available entries in activeTx. // initially, all entries are available. availableTx = new XACommittingTx[activeTx.length]; for (int i=0; i < activeTx.length; ++i) availableTx[i] = new XACommittingTx(i); // register the event listener super.setLogEventListener(this); } /** * Construct a Logger using default Configuration object. * @throws IOException */ public XALogger() throws IOException { super(new Configuration()); init(); } /** * Construct a Logger using a Configuration supplied * by the caller. * @param config Configuration object * @throws IOException */ public XALogger(Configuration config) throws IOException { super(config); init(); } private void checkPutEnabled() throws LogClosedException { if (replayNeeded) throw new LogClosedException("replay() must be called prior to put(); activeMark [0x" + Long.toHexString(super.getActiveMark()) + "]"); } /** * add a USER record consisting of byte[] to the log. *

waits for overflow notification processing to complete * prior to putting the data to the log. * * @throws LogClosedException * If the TM has called open() but has not called replay(). * Also thrown if log is actually closed. * Check the toString() for details. * * @see org.objectweb.howl.log.Logger#put(byte[], boolean) */ public long put(byte[] data, boolean sync) throws LogClosedException, LogRecordSizeException, LogFileOverflowException, InterruptedException, IOException { checkPutEnabled(); // wait for overflow notification processor to finish. onpWait(); return put(LogRecordType.USER, new byte[][]{data}, sync); } /** * add a USER record consisting of byte[][] to the log. *

waits for overflow notification processing to complete * prior to putting the data to the log. * * @throws LogClosedException * If the TM has called open() but has not called replay(). * Also thrown if log is actually closed. * Check the toString() for details. * * @see org.objectweb.howl.log.Logger#put(byte[][], boolean) */ public long put(byte[][] data, boolean sync) throws LogClosedException, LogRecordSizeException, LogFileOverflowException, InterruptedException, IOException { checkPutEnabled(); // wait for overflow notification processor to finish. onpWait(); return put(LogRecordType.USER, data, sync); } /** * wait for overflow notification processor to finish. * *

failure to do might cause the overflow processor itself to * get LogFileOverflowException. * * @throws InterruptedException */ private void onpWait() throws InterruptedException { long beginWait = System.currentTimeMillis(); synchronized(this) { if (this.overflowFence != 0L) ++waitForThisCount; while (this.overflowFence != 0L) wait(); totalWaitForThis += (System.currentTimeMillis() - beginWait); } } /** * Write a begin COMMIT record to the log. *

Call blocks until the data is forced to disk. * * @param record byte[][] containing data to be logged * * @return XACommittingTx object to be used whe putting the DONE record. * * @throws LogClosedException * If the TM has called open() but has not called replay(). * Also thrown if log is actually closed. * Check the toString() for details. * * @throws IOException * @throws InterruptedException * @throws LogFileOverflowException * @throws LogRecordSizeException */ public XACommittingTx putCommit(byte[][] record) throws LogClosedException, LogRecordSizeException, LogFileOverflowException, InterruptedException, IOException { long key = 0L; long overflowFence = 0L; checkPutEnabled(); // wait for overflow notification processor to finish. onpWait(); /* * The following loop handles the (hopefully very rare) case * where the call to put() returns a key that is lower than * the current overflowFence. This should not happen, but * just in case we have a situation in which a thread is not * dispatched for a very long time after the record is * written to the log file, it is theoretically possible that * the log key returned by put() could be lower than the * overflowFence. If this happens, we just rewrite the * record. * * Note: A very obnoxious test case using extremely small files * might actually put this into an infinate loop. */ do { key = put(LogRecordType.XACOMMIT, record, true); synchronized(this) { overflowFence = this.overflowFence; } } while (key < overflowFence); return activeTxAdd(key, record); } /** * Used by putCommit() and by OpenReplayListener#onRecord() * to add entries to the activeTx table. * * @param key log key for the XACOMMIT record * @param record byte[][] of record data * * @return XACommitting object representing the XACOMMIT * data record in the log. */ private XACommittingTx activeTxAdd(long key, byte[][] record) { XACommittingTx tx = null; synchronized(activeTxLock) { // resize activeTx[] if necessary if (atxUsed == activeTx.length) growActiveTxArray(); // get an available entry workerID and save reference tx = availableTx[atxGet]; assert tx != null : "availableTx[" + atxGet +"] is null"; availableTx[atxGet] = null; atxGet = (atxGet + 1) % activeTx.length; /* * update XACommittingTx with values for this COMMIT * * DEBUG NOTE: to test tx for null, we could put a * breakpoint on any of the following statements and * set a breakpoint condition for tx == null. However, this uses * a lot of CPU time and makes the debug session run * very slow. It seems to be faster to wrap the code * it a try/catch and set a breakpoint in the catch. */ try { tx.setLogKey(key); tx.setRecord(record); tx.setDone(false); tx.setMoving(false); } catch (NullPointerException npe) { throw npe; } int index = tx.getIndex(); activeTx[index] = tx; // maintain statistics ++atxUsed; maxAtxUsed = Math.max(atxUsed, maxAtxUsed); } return tx; } /** * Write a DONE record to the log. *

Remove XACommittingTx object from the list of * active transactions. * * @param record byte[][] containing data to be logged. *

If record is null then * no user data is written to the journal. * @param tx the XACommittingTx that was returned * by the putCommit() routine for this transaction. * * @return long log key for the record byte[][]. *

If record is null, then return is 0L. * * @throws IOException * @throws InterruptedException * @throws LogFileOverflowException * @throws LogRecordSizeException * * @throws LogClosedException * If the TM has called open() but has not called replay(). * Also thrown if log is actually closed. * Check the toString() for details. * * */ public long putDone(byte[][] record, XACommittingTx tx) throws LogClosedException, LogRecordSizeException, LogFileOverflowException, InterruptedException, IOException { checkPutEnabled(); assert tx != null : "XACommitingTX is null"; // remove this entry from the activeTx table so overflow processor does not see it synchronized(activeTxLock) { int index = tx.getIndex(); if (activeTx[index] != tx) throw new IllegalArgumentException(); activeTx[index] = null; } // mark entry as DONE and wait (if necessary) for move to complete synchronized(tx) { // let logOverflowNotification know that this entry does not have to be moved tx.setDone(true); // in case logOverflowNotification got into the object first while (tx.isMoving()) tx.wait(); } long doneKey = 0L; if (record != null) { // write the DONE record do { try { doneKey = put(LogRecordType.USER, record, false); } catch (LogFileOverflowException e) { Thread.sleep(10); } } while (doneKey == 0L); } // record the XADONE record with logKey from XACOMMIT record long xadoneKey = 0L; do { try { xadoneKey = put(LogRecordType.XADONE, tx.logKeyData, false); } catch (LogFileOverflowException e) { Thread.sleep(10); } } while (xadoneKey == 0L); // make entry available for re-use. synchronized(activeTxLock) { tx.setLogKey(0); // prevent duplicate entries in log BUG: 303907 availableTx[atxPut] = tx; atxPut = (atxPut + 1) % activeTx.length; --atxUsed; assert atxUsed >= 0 : "Negative atxUsed (" + atxUsed + ")"; } return doneKey; } /** * implements LogEventListener#isLoggable(). * @param level a logging level defined by LogEventListener */ public boolean isLoggable(int level) { boolean loggable = false; // cannot log events if caller did not provide a listener if (this.eventListener != null) loggable = eventListener.isLoggable(level); return loggable; } /** * delegates log request to the callers event listener. * @param level log level * @param message text to be logged */ public void log(int level, String message) { if (this.eventListener != null) eventListener.log(level, message); } /** * delegates log request to the callers event listener. * @param level log level * @param message text to be logged * @param thrown Throwable related to event being logged */ public void log(int level, String message, Throwable thrown) { if (this.eventListener != null) eventListener.log(level, message, thrown); } /** * called by Logger when log file is about to overflow. * *

copies XACommittingTx records forward to allow reuse * of older log file space. * *

calls Logger.mark() to set the new active mark * at the completion of the scan. * *

Exceptions are ignored. Hopefully they will be * reported to the TM when a transaction thread attempts * to write a log record. * *

While we are processing activeTx[] we publish * the overflowFence so putCommit knows that any record * it stores with a key below the fence must be re-put. * *

This notification is not forwarded to the LogEventListener * eventListener that was registered by the TM. * * @param overflowFence COMMIT records with * log keys lower than fence must * be moved. All others can be ignored * for now. */ public void logOverflowNotification(long overflowFence) { boolean logClosed = false; // BUG 300799 long newMark = Long.MAX_VALUE; XACommittingTx tx = null; long txKey = 0L; if (overflowFence == 0) throw new IllegalArgumentException("overflowFence == 0"); // increment number of times we are notified ++overflowNotificationCount; synchronized(this) { // putCommit will re-put a record if it is below the fence this.overflowFence = overflowFence; } // allow application to process this event if (eventListener != null) eventListener.logOverflowNotification(overflowFence); /* * walk through the activeTx table and move any * records that are below the overflowFence. */ for (int i=0; i < activeTx.length; ++i) { synchronized(activeTxLock) { tx = activeTx[i]; if (tx == null) continue; } synchronized(tx) { // gaurd against the possibility that putDone got into the object first if (tx.isDone()) continue; txKey = tx.getLogKey(); if (txKey > overflowFence) { // we are not moving this entry, but it might be the new active mark if (txKey < newMark) newMark = txKey; // remember the oldest active transaction continue; } // make putDone wait till we get this record moved tx.setMoving(true); } try { // get reference to original record byte[][] record = tx.getRecord(); // construct an XACOMMITMOVED record // with room for original log key. // // original log key is used during replay to locate and // update the activeTx table that is maintained by this XALogger. // // NOTE: inserted key must be removed during replay // before returning record to TM. byte[][] movedRecord = new byte[record.length + 1][]; movedRecord[0] = tx.logKeyData[0]; System.arraycopy(record, 0, movedRecord, 1, record.length); // move the COMMIT record data // we force all the moved records later when we set the new mark. // NOTE: be careful not to use the put() methods that will // cause this thread to wait in onpWait(). txKey = put(LogRecordType.XACOMMITMOVED, movedRecord, false); // keep track of the number of records moved. ++movedRecordCount; synchronized(tx) { tx.setLogKey(txKey); tx.setMoving(false); tx.notifyAll(); // in case putDone is waiting } } catch (LogClosedException e1) { // We can safely ignore the exception. // Restart may see moved records multiple times, but this is not a problem. logClosed = true; // BUG 300799 break; // BUG 300799 - remove assert } catch (LogRecordSizeException e1) { // ignore assert false : "unexpected LogRecordSizeException"; } catch (LogFileOverflowException e1) { // ignore assert false : "unexpected LogFileOverflowException"; } catch (InterruptedException e1) { // ignore } catch (IOException e1) { // ignore } } // set new mark using oldest log key encountered during scan of activeTx[] try { // if we have not moved any records, set new mark at the beginning of next file. if (newMark == Long.MAX_VALUE) newMark = overflowFence; // BUG 300799 if we got a LogClosedException while moving records // then we cannot update the mark. if (!logClosed) mark(newMark, true); // force = true } catch (InvalidLogKeyException e) { // should never happen System.err.println(e.toString()); Thread.yield(); } catch (LogClosedException e) { // should never happen assert false : "Log closed during logOverflowNotification processing"; } catch (IOException e) { // ignore here - it will get caught by a transaction thread } catch (InterruptedException e) { // ignore here - it will get caught by a transaction thread } // let putCommit know that overflow processing is idle synchronized(this) { this.overflowFence = 0L; notifyAll(); } } /** * return an XML node containing statistics for * this object along with the base Logger, * the LogFile pool and the LogBuffer pool. * * @return String contiining XML document. */ public String getStats() { String name = this.getClass().getName(); StringBuffer stats = new StringBuffer( "\n" ); stats.append( "\nNumber of times activeTx table was resized to accomodate " + "a larger number of transactions in COMMITTING state" + "" + "\nMaximum number of active TX entries used" + "" + "\nNumber of records moved during log overflow notification processing" + "" + "\nnumber of times log overflow notification event was called." + "" + "\nNumber of times threads waited for overflow processing to complete" + "" + "\nTotal time (ms) threads waited for overflow processing to complete" + "" ); stats.append(super.getStats()); stats.append("\n" + "\n"); return stats.toString(); } /** * Provide synchronized access to waitForThisCount. * @return waitForThisCount */ private final synchronized int getWaitForThisCount() { return waitForThisCount; } /** * Provide synchronized access to totalWaitForThis. * @return totalWaitForThis */ private final synchronized long getTotalWaitForThis() { return totalWaitForThis; } /** * Saves a reference to callers LogEventListener. * *

Some LogEventListener notifications may be * passed onto the callers eventListener. * Refer to javadocs in this source * for individual notifications to determine if * the notification is passed on to the TM. * * @param eventListener object to be notified of logger events. */ public void setLogEventListener(LogEventListener eventListener) { this.eventListener = eventListener; } /** * Not supported for XALogger. *

XALogger must rebuild the activeTx table prior to allowing * any calls to put() methods. To enforce this, callers must * use the {@link #open(ReplayListener)} method. * * @throws UnsupportedOperationException */ public void open() { throw new UnsupportedOperationException("Use open(ReplayListener) instead"); } /** * calls super.open() to perform standard open functionality then * replays the log to rebuild the activeTx table. * *

Sets replayNeeded flag to block calls to put() methods * until the replay is complete. (This may be unnecessary because * the replay blocks until it is complete, but we do this to * prevent the TM from trying to make log entries on another * thread before the log is fully open.) * *

During the replay, the XALogger calls the TM's * listener.onRecord() with an XALogRecord object. * The TM is expected to call getTx() to obtain * a reference to the XACommittingTx that will be * used to complete the transaction usin putDone() * later. * *

Note that XALogger does not call the TM's * listener.getLogRecord(). * * @param listener * The TM can receive replay events by * providing a ReplayListener. * TMs that do not wish to see all records during * the open replay should pass a null ReplayListener. * @see #replayActiveTx(ReplayListener) */ public void open(ReplayListener listener) throws InvalidFileSetException, LogConfigurationException, InvalidLogBufferException, LogClosedException, ClassNotFoundException, IOException, InterruptedException { this.replayNeeded = true; // block put() methods until replay completes super.open(); OpenReplayListener xaListener = new OpenReplayListener(listener); try { super.replay(xaListener, getActiveMark(), true); // replay CTRL records also } catch (InvalidLogKeyException e) { throw new LogClosedException(e); } // something very wrong if we come back from replay and we have not cleared the flag. if (replayNeeded) { LogClosedException lce = new LogClosedException(xaListener.replayException); throw lce; } } public void close() throws IOException, InterruptedException { if (isClosed) return; long newMark = Long.MAX_VALUE; XACommittingTx tx = null; // set new log mark at oldest activeTx entry synchronized(activeTxLock) { for (int i=0; i < activeTx.length; ++i) { tx = activeTx[i]; if (tx == null) continue; long key = tx.getLogKey(); if (key < newMark) newMark = key; } if (newMark < Long.MAX_VALUE) { try { this.mark(newMark, true); } catch (InvalidLogKeyException e) { // will not happen } catch (LogClosedException e) { // will not happen } } } super.close(); } /** * Used by TM and test cases to obtain the * number of entries in the activeTx table. *

TIP: if value does not agree with expected * value in TM or test case, we probably have * a bug somewhere. * * @return current number of used entries in activeTx table */ public int getActiveTxUsed() { return this.atxUsed; } /** * displays entries in the activeTx table. *

useful for debug. */ public void activeTxDisplay() { for (int i=0; i < activeTx.length; ++i) { if (activeTx[i] == null) continue; synchronized(activeTx) { XACommittingTx tx = activeTx[i]; byte[][] record = tx.getRecord(); System.out.println("activeTx[" + i + "] key=" + Long.toHexString(tx.getLogKey()) + "\n Fields: " + record.length ); for (int j = 0; j < record.length; ++j) { byte[] field = record[j]; System.out.println(" [" + j + "] len=" + field.length + ": " + new String(field)); } } } } /** * Wrapp Logger#replay(ReplayListener) so we can * intercept onRecord() notifications to process * XACOMMIT and XACOMMITMOVED records. */ public void replay(ReplayListener listener) throws LogConfigurationException { try { this.replay(listener, getActiveMark()); } catch (InvalidLogKeyException e) { // should not happen assert false : "Unhandled InvalidLogKeyException" + e.toString(); } } /** * Wrapp Logger#replay(ReplayListener, long) so we can * intercept onRecord() notifications to process * XACOMMIT and XACOMMITMOVED records. */ public void replay(ReplayListener listener, long key) throws InvalidLogKeyException, LogConfigurationException { XAReplayListener replayListener = new XAReplayListener(listener); super.replay(replayListener, key, true); } /** * Called by the TM to receive copies of the * active transaction entries. * *

TMs can use this method as an alternative * to passing a ReplayListener to open. The * advantage is that only active transaction * entries are returned to the ReplayListener.onRecord() * method. * * @param listener The activeTx entries are passed * to the TM through this ReplayListener. */ public void replayActiveTx(ReplayListener listener) { XACommittingTx tx = null; XALogRecord lr = null; // scan activeTx[] for non-null entries for (int i=0; i < activeTx.length; ++i) { tx = activeTx[i]; if (tx == null) continue; // found an active entry -- rebuild the XALogRecord entries byte[][] record = tx.getRecord(); short recordSize = 0; for(int j=0; j < record.length; ++j) { // calculate size of byte[] needed for current entry recordSize += (2 + record[j].length); } lr = new XALogRecord(recordSize); // populate the LogRecord lr.length = recordSize; lr.dataBuffer.clear(); for (int j=0; j < record.length; ++j) { lr.dataBuffer.putShort((short)record[j].length); lr.dataBuffer.put(record[j]); } lr.key = tx.getLogKey(); lr.tod = 0L; lr.setTx(tx); lr.type = LogRecordType.XACOMMIT; // give the LogRecord to TM for processing listener.onRecord(lr); } // signal end of table lr = new XALogRecord(0); lr.type = LogRecordType.END_OF_LOG; listener.onRecord(lr); } /** * private class used by XALogger to replay the log during * log open processing. * *

As log records are replayed through onRecord method, * the HashMap activeTxHashMap is updated. XACOMMIT type * records are added to the activeTxHashMap, and XADONE type records * remove an entry. * *

When the END_OF_LOG record is encountered, the * activeTx table is up to date and ready for * new entries to be added by the TM. * * @author Michael Giroux */ private class OpenReplayListener implements ReplayListener { LogRecord lr = new XALogRecord(80); final XALogger parent = XALogger.this; // set by onError() LogException replayException = null; /** * ReplayListener registered by TM that instantiated * this XALogger. * *

During replay, non-CTRL records are returned * to the TM's replayListener. */ final ReplayListener tmListener; /** * Used to keep track of XACOMMIT records encountered * during replay. An entry is added to the activeTxHashMap when * XACOMMIT type record is replayed, and removed * when XADONE type record is replayed. */ final HashMap activeTxHashMap; // counters that might be interesting to a test case public int unmatchedDoneCount = 0; public int commitCount = 0; public int doneCount = 0; public int movedCount = 0; OpenReplayListener(ReplayListener tmListener) { // FEATURE 300768 - allow null ReplayListener during open() this.tmListener = tmListener; activeTxHashMap = new HashMap(256); activeTxHashMap.clear(); } public void onRecord(LogRecord lr) { XACommittingTx tx = null; int fldSize = 0; long xacommitKey = 0L; // see XACOMMITMOVED and XADONE ((XALogRecord)lr).setTx(tx); // pass all non-CTRL records onto TM if (!lr.isCTRL() && tmListener != null) { tmListener.onRecord(lr); return; } ByteBuffer b = lr.dataBuffer; // process XACOMMIT and XADONE CTRL records switch(lr.type) { case LogRecordType.END_OF_LOG: // signal that we are done synchronized(this) { parent.replayNeeded = false; notifyAll(); } // let TM look at this LogRecord if (tmListener != null) tmListener.onRecord(lr); break; case LogRecordType.XACOMMITMOVED: ++movedCount; int sz = b.getShort(); if (sz != 8) { throw new IllegalArgumentException("Expected 8 found " + sz); } // QUESTION: is there a better way to handle this here? // get log key for the record that was moved xacommitKey = b.getLong(); // get existing XACommitingTx entry from HashMap if it exists tx = (XACommittingTx)activeTxHashMap.remove(new Long(xacommitKey)); if (tx != null) { // update existing entry tx.setLogKey(lr.key); // TM already has seen this entry, so we do not pass this record break; } /* * This is the first time we have seen this XACOMMIT * record, so we remove the original xacommitKey * from the data[] buffer and drop through into * normal XACOMMIT processing. */ int len = b.remaining(); int start = b.position(); System.arraycopy(lr.data, start, lr.data, 0, len); b.rewind(); b.limit(len); // FALL THROUGH and process as XACOMMIT case LogRecordType.XACOMMIT: ++commitCount; tx = activeTxAdd(lr.key, lr.getFields()); // remember this entry in the HashMap activeTxHashMap.put(new Long(lr.key), tx); // give XACommittingTx to TM so it can be passed to putDone ((XALogRecord)lr).setTx(tx); // pass commit record on to calling TM if (tmListener != null) tmListener.onRecord(lr); break; /* * Remove corresponding XACOMMIT from the activeTx * table and return. * * The record is not passed on to the TM. */ case LogRecordType.XADONE: ++doneCount; fldSize = b.getShort(); if (fldSize != 8) throw new IllegalArgumentException("expected 8 found " + fldSize + " at record " + Long.toHexString(lr.key)); // QUESTION: is there a better way to handle this here? xacommitKey = b.getLong(); // HOWL_0_1_10 and later generate an extra bit of data for JOTM // Journal files created by older versions will not have this field. if (b.remaining() > 0) { fldSize = b.getShort(); // BUG 303907 added index to XADONE for diagnostics if (fldSize != 4) throw new IllegalArgumentException("expected 4 found " + fldSize + " at record " + Long.toHexString(lr.key)); b.getInt(); // discard index } assert b.remaining() == 0 : "Unexpected data in XADONE record"; /* * If the XACOMMIT record was recorded to a different file * it is possible we will not see the XACOMMIT, but we * may see the XADONE. This is not an error, so just * ignore this XADONE if it occurs. */ tx = (XACommittingTx)activeTxHashMap.remove(new Long(xacommitKey)); if (tx == null) { ++unmatchedDoneCount; break; } // remove this entry from the activeTx table synchronized(activeTxLock) { int index = tx.getIndex(); if (activeTx[index] != tx) throw new IllegalArgumentException(); activeTx[index] = null; availableTx[atxPut] = tx; atxPut = (atxPut + 1) % activeTx.length; --atxUsed; assert atxUsed >= 0 : "Negative atxUsed (" + atxUsed + ")"; } // this is an XALogger private record, so no need to pass it on to TM break; } } public void onError(LogException e) { System.err.println("onError: " + e); replayException = e; // QUESTION - mark log unusable? // pass the error onto the TM if (tmListener != null) tmListener.onError(e); } public LogRecord getLogRecord() { return lr; } } /** * private class used by XALogger.replay() methods. * * Used by replay(ReplayListener) and replay(ReplayListener, long) wrapper * methods to intercept XACOMMIT and XACOMMITMOVED records * so they can be passed to caller. * * During TM invoked replay, log records are returned to the * caller, but the activeTx table is not updated. * * @author Michael Giroux */ private static class XAReplayListener implements ReplayListener { final LogRecord lr; /** * ReplayListener registered by TM that instantiated * this XALogger. * *

During replay, non-CTRL records are returned * to the TM's replayListener. */ final ReplayListener tmListener; XAReplayListener(ReplayListener tmListener) { assert tmListener != null: "ReplayListener must be non-null"; this.tmListener = tmListener; lr = tmListener.getLogRecord(); } public void onRecord(LogRecord lr) { // set LogRecord.tx = null for TM invoked replay. ((XALogRecord)lr).setTx(null); // pass all non-CTRL records onto TM if (! lr.isCTRL()) { tmListener.onRecord(lr); return; } // process CTRL records switch(lr.type) { case LogRecordType.END_OF_LOG: tmListener.onRecord(lr); break; case LogRecordType.XACOMMITMOVED: ByteBuffer b = lr.dataBuffer; if (b.getShort() != 8) throw new IllegalArgumentException(); // QUESTION: is there a better way to handle this here? // discard the log key for the record that was moved b.getLong(); // during a TM invoked replay(), all XACOMMIT and XACOMMITMOVED // records are returned to the TM after removing the xacommitKey // that was inserted by logOverflowNotification. int len = b.remaining(); int pos = b.position(); System.arraycopy(lr.data, pos, lr.data, 0, len); b.rewind(); b.limit(len); // FALL THROUGH and process as XACOMMIT case LogRecordType.XACOMMIT: // pass commit record on to calling TM tmListener.onRecord(lr); break; // All other control records, including XADONE are discarded default: break; } } public void onError(LogException e) { // pass the error onto the TM tmListener.onError(e); } public LogRecord getLogRecord() { return lr; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy