/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Plugged In Software Pty Ltd. All Rights Reserved. * * Contributor(s): N/A. * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.store.xa; import java.io.*; // Java 2 standard packages import java.lang.ref.*; import java.util.*; // Third party packages import org.apache.log4j.Logger; import org.mulgara.util.IntFile; import org.mulgara.util.StackTrace; import org.mulgara.util.functional.C; /** * A fifo of integer items. A list of "phases" is maintained where each phase * represents a snapshot of the state of the free list at a point in time. Items * added to the free list will not be returned by {@link #allocate} until all * current phases have been closed (and at least one new phase created). * * @created 2001-09-20 * * @author Paula Gearon * @author David Makepeace * * @version $Revision: 1.13 $ * * @modified $Date: 2005/07/21 19:13:48 $ * * @maintenanceAuthor $Author: pgearon $ * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @copyright ©2001-2004 <a href="http://www.pisoftware.com/">Plugged In * Software Pty Ltd</a> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ public final class FreeList { /** Logger. */ private final static Logger logger = Logger.getLogger(FreeList.class); /** The name extension of the file used for the freelist. */ private final static String INTFILE_EXT = "_ph"; /** * The size of a block of free items in bytes. The free list file is always a * multiple of this size. */ private final static int BLOCK_SIZE_B = 32768; /** The size of a block of free items in longs. This includes the header. */ private final static int BLOCK_SIZE = BLOCK_SIZE_B / 8; /** The default IOType to use for the BlockFile. */ private final static BlockFile.IOType DEF_IO_TYPE = BlockFile.IOType.EXPLICIT; /** * The index of the pointer to the next block of free items. Blocks of free * items are chained into a doubly-linked circular list. */ private final static int IDX_NEXT = 0; /** * The index of the pointer to the previous block of free items. Blocks of * free items are chained into a doubly-linked circular list. */ private final static int IDX_PREV = 1; /** The size of the block header in ints. */ private final static int HEADER_SIZE_I = 2; /** The size of the block header in longs. */ private final static int HEADER_SIZE = (HEADER_SIZE_I + 1) / 2; /** The free list file. */ private File file; /** The free list BlockFile. */ private BlockFile blockFile; /** A persistent map of items to their corresponding phases. */ private IntFile itemToPhaseSeqMap; /** * This is used to prevent items that are still in use from being returned by * {@link #allocate}. Items between firstHead and {@link Phase#head} are * reserved. This is a copy of the head of the first Phase in the {@link * #phases} list. */ private long firstHead; /** * <code>true</code> if {@link #headBlock} has been modified. */ private boolean headBlockDirty = false; /** * This buffer used for reading and writing the block which contains the item * pointed to by {@link Phase#head}. */ private Block headBlock = null; /** * The pointer to the first item which is not a possible candidate for * reallocation. Items between firstFree (inclusive) and reallocate * (noninclusive) are possible candidates for reallocation. */ private long reallocate; /** * The pointer to the start of the list of items in the free list which were * freed during the current phase. */ private long firstFree; /** Set to <code>true</code> if the {@link #reallocateBlock} is dirty. */ private boolean reallocateBlockDirty = false; /** The buffer used for reading and writing the block containing the {@link #reallocate} item. */ private Block reallocateBlock = null; /** * This is used to protect items in the free list from being reused as the * list grows so that the state can be safely rolled back to a previous phase * after a crash. Items between firstTail and {@link Phase#tail} can be * returned by {@link #allocate} but they are protected from being overwritten * until the phases which contain them are closed. */ private long firstTail; /** * This buffer used for reading and writing the block which contains the item * pointed to by {@link Phase#tail}. */ private Block tailBlock = null; /** The list of Phases from oldest to newest. */ private LinkedList<Phase> phases = new LinkedList<Phase>(); /** The newest (writing) phase. */ private Phase currentPhase = null; /** * The index of the next block to insert into the chain of blocks that * constitute the free list. This is the index of the block after the last * block in the file. */ private int nextBlockId; /** * Constructs a FreeList which uses the specified file (if it exists) or * creates a new file (if it doesn't already exist). * * @param file the file. * @param ioType the IOType to use for the BlockFile. * @throws IOException if an I/O error occurs. */ private FreeList(File file, BlockFile.IOType ioType) throws IOException { this.file = file; blockFile = AbstractBlockFile.openBlockFile(file, BLOCK_SIZE_B, ioType); itemToPhaseSeqMap = IntFile.open(file + INTFILE_EXT); itemToPhaseSeqMap.clear(); } /** * Factory method for a FreeList instance which uses the specified file * (if it exists) or creates a new file (if it doesn't already exist). * * @param file the file. * @param ioType the IOType to use for the BlockFile. * @return The new FreeList instance. * @throws IOException if an I/O error occurs. */ public static FreeList openFreeList(File file, BlockFile.IOType ioType) throws IOException { return new FreeList(file, ioType); } /** * Creates a FreeList instance which uses the specified file (if it exists) or * creates a new file (if it doesn't already exist). Uses the default IO file type. * * @param file the file. * @return The new FreeList instance. * @throws IOException if an I/O error occurs. */ public static FreeList openFreeList(File file) throws IOException { return openFreeList(file, DEF_IO_TYPE); } /** * Creates a FreeList instance which uses the file with the specified file * name (if the file exists) or creates a new file (if it doesn't already * exist). * * @param fileName the file name of the file. * @return The new FreeList instance. * @throws IOException if an I/O error occurs. */ public static FreeList openFreeList(String fileName) throws IOException { return openFreeList(new File(fileName)); } /** * Initializes a FreeList block file or repairs the back-pointers in * an existing file. This is called from Phase.init(llll). */ private void initFreeListFile(boolean clear) throws IOException { if (clear || blockFile.getNrBlocks() < 2) { // File is too small to be valid. Reset to two blocks. nextBlockId = 2; blockFile.setNrBlocks(nextBlockId); allocateHeadBlock(0); headBlock.putInt(IDX_NEXT, 1); headBlock.putInt(IDX_PREV, 1); headBlockDirty = true; allocateHeadBlock(1); headBlock.putInt(IDX_NEXT, 0); headBlock.putInt(IDX_PREV, 0); headBlockDirty = true; force(); } else { nextBlockId = findMaxBlockId() + 1; blockFile.setNrBlocks(nextBlockId); } } /** * The next item to allocate if the free list is empty or there are no items * between {@link Phase#tail} and {@link #firstHead}. This is also one * greater than the largest item in use. * * @return the next item to allocate if the free list is empty or there are * no items between {@link Phase#tail} and {@link #firstHead}. */ public long getNextItem() { return currentPhase.getNextItem(); } /** * Gets the SharedItem attribute of the FreeList object. This indicates that * the item appears in more than one phase. * * @param item An item in the free list. * @return The SharedItem value */ public synchronized boolean isSharedItem(long item) { return itemToPhaseSeqMap.getInt(item) != currentPhase.getSequenceNumber(); } /** * Truncates the free list file to a single block. This should only be called * immediately after constructing the free list and adding a single * initialized phase. * * @throws IOException if an I/O error occurs. * @throws IllegalStateException if it is inappropriate to call clear because * the free list hasn't been correctly initialized. */ public synchronized void clear() throws IOException { if ( (phases.size() != 1) || (currentPhase.getHead() != HEADER_SIZE) || (currentPhase.getTail() != HEADER_SIZE)) { throw new IllegalStateException( "FreeList does not have a single initialized phase." ); } releaseTailBlock(); releaseReallocateBlock(); releaseHeadBlock(); nextBlockId = 2; blockFile.setNrBlocks(nextBlockId); allocateHeadBlock(0); headBlock.putInt(IDX_NEXT, 1); headBlock.putInt(IDX_PREV, 1); headBlockDirty = true; allocateHeadBlock(1); headBlock.putInt(IDX_NEXT, 0); headBlock.putInt(IDX_PREV, 0); headBlockDirty = true; force(); } /** * Releases all mapped resources for the file, allowing the VM to unmap the file. * No operations are possible after calling this method without first * reinitializing the object. */ public synchronized void unmap() { if (itemToPhaseSeqMap != null) { itemToPhaseSeqMap.unmap(); } if (blockFile != null) { try { releaseHeadBlock(); releaseReallocateBlock(); releaseTailBlock(); } catch (IOException ex) { logger.warn("IOException in unmap()", ex); } blockFile.unmap(); } } /** * Closes the file. * * @throws IOException if an I/O error occurs. */ public synchronized void close() throws IOException { try { unmap(); } finally { IOException ex = null; try { if (itemToPhaseSeqMap != null) itemToPhaseSeqMap.delete(); } catch (IOException e) { ex = e; } finally { itemToPhaseSeqMap = null; } try { blockFile.close(); } catch (IOException e) { if (ex == null) ex = e; else logger.info("Suppressing exception deleting file for failed FreeList", e); } if (ex != null) throw ex; } } /** * Closes and deletes the file. * * @throws IOException if an I/O error occurs. */ public synchronized void delete() throws IOException { try { unmap(); } finally { IOException ex = null; try { if (itemToPhaseSeqMap != null) itemToPhaseSeqMap.delete(); } catch (IOException e) { ex = e; } finally { itemToPhaseSeqMap = null; } try { blockFile.delete(); } catch (IOException e) { if (ex == null) ex = e; else logger.info("Suppressing exception deleting file for failed FreeList", e); } if (ex != null) throw ex; } } /** * Ensures that all data associated with the free list has been written to * persistent storage. * * @throws IOException if an I/O error occurs. */ public void force() throws IOException { writeHeadBlock(); writeReallocateBlock(); blockFile.force(); itemToPhaseSeqMap.force(); } /** * Adds an item to the free list. * * @param item the item to add to the free list. * @throws IOException if an I/O error occurs. * @throws IllegalStateException if there are no phases. */ public synchronized void free(long item) throws IOException { removeClosedPhases(); if (currentPhase == null) throw new IllegalStateException("FreeList has no phases."); if ( (item < 0) || (item >= currentPhase.getNextItem())) { throw new IllegalArgumentException("Trying to free item that was never allocated: " + item); } // if (!isValid(item)) throw new AssertionError("Attempt to free an invalid item: " + item); long head = currentPhase.getHead(); readHeadBlock(getBlockId(head)); int offset = getBlockOffset(head++); headBlock.putLong(offset, item); headBlockDirty = true; if (!isSharedItem(item)) reallocate = head; if (getBlockOffset(head) == 0) { // Go to the next block. int nextHeadBlockId = headBlock.getInt(IDX_NEXT); if (nextHeadBlockId == getBlockId(firstTail)) { // Move the current head buffer aside so that we can update the next // pointer later. Block prevHeadBlock = headBlock; headBlock = null; headBlockDirty = false; // Allocate new buffer at the end of the file and set its next pointer // to point to the block which contains firstTail. int newHeadBlockId = nextBlockId++; blockFile.setNrBlocks(nextBlockId); allocateHeadBlock(newHeadBlockId); headBlock.putInt(IDX_NEXT, nextHeadBlockId); headBlock.putInt(IDX_PREV, (int) prevHeadBlock.getBlockId()); headBlockDirty = true; force(); // Update the next pointer of the block which contained head to point // to the new block. prevHeadBlock.putInt(IDX_NEXT, newHeadBlockId); prevHeadBlock.write(); // Update the prev pointer of the next block to point back to the new // block. Block nextHeadBlock = findBlock(nextHeadBlockId); if (nextHeadBlock == null) { nextHeadBlock = blockFile.readBlock(nextHeadBlockId); } nextHeadBlock.putInt(IDX_PREV, newHeadBlockId); nextHeadBlock.write(); nextHeadBlockId = newHeadBlockId; } head = ((long)nextHeadBlockId * BLOCK_SIZE) + HEADER_SIZE; } if (phases.size() == 1) firstHead = head; currentPhase.setHead(head); currentPhase.decNrValidItems(); } /** * Allocates an item from the free list or, if there are no available items, * allocates a new item with the value of nextItem and increments nextItem. * * @return the allocated item. * @throws IOException if an I/O error occurs. * @throws IllegalStateException if there are no phases. */ public synchronized long allocate() throws IOException { removeClosedPhases(); if (currentPhase == null) throw new IllegalStateException("FreeList has no phases."); long item; if (phases.size() > 1) { // First try to reallocate an item that was freed during the current // phase. Only items that were originally allocated during the current // phase (and then freed) can be reallocated. long head = currentPhase.getHead(); while (reallocate != firstFree) { if (getBlockOffset(reallocate) == HEADER_SIZE) { // Change reallocate to point to last item of previous block. readReallocateBlock(getBlockId(reallocate)); int blockId = reallocateBlock.getInt(IDX_PREV); reallocate = ((blockId * BLOCK_SIZE) + BLOCK_SIZE) - 1; } else { --reallocate; } // Fetch the candidate item. readReallocateBlock(getBlockId(reallocate)); int offset = getBlockOffset(reallocate); item = reallocateBlock.getLong(offset); // Return the item if it was originally allocated during the current // phase. if (!isSharedItem(item)) { // Remove this item by moving the last free item to this item's // position. if (getBlockOffset(head) == HEADER_SIZE) { readHeadBlock(getBlockId(head)); int blockId = headBlock.getInt(IDX_PREV); head = ( ( (long) blockId * BLOCK_SIZE) + BLOCK_SIZE) - 1; } else { --head; } currentPhase.setHead(head); if (reallocate != head) { readHeadBlock(getBlockId(head)); long tItem = headBlock.getLong(getBlockOffset(head)); readReallocateBlock(getBlockId(reallocate)); reallocateBlock.putLong(getBlockOffset(reallocate), tItem); reallocateBlockDirty = true; } currentPhase.incNrValidItems(); return item; } } // Don't search this part of the free list again. reallocate = head; firstFree = head; } long tail = currentPhase.getTail(); if (tail == firstHead) { // No available free items on the free list. long nextItem = currentPhase.getNextItem(); item = nextItem++; currentPhase.setNextItem(nextItem); } else { readTailBlock(getBlockId(tail)); int offset = getBlockOffset(tail++); item = tailBlock.getLong(offset); if (getBlockOffset(tail) == 0) { // Go to the next block. int blockId = tailBlock.getInt(IDX_NEXT); tail = ( (long) blockId * BLOCK_SIZE) + HEADER_SIZE; } if (phases.size() == 1) firstTail = tail; currentPhase.setTail(tail); } itemToPhaseSeqMap.putInt(item, currentPhase.getSequenceNumber()); currentPhase.incNrValidItems(); return item; } /** * Gets the Valid attribute of the FreeList object. * Checks if the item is out of the possible range for nodes, which would * make it invalid. Then it checks the current free list. If the item * is in the free list, then it is invalid. If the item cannot be found * in the free list then it must still be in use, and therefor valid. * * @param item The item to check. * @return <code>true</code> if the item is valid.. */ public synchronized boolean isValid(long item) { removeClosedPhases(); if (currentPhase == null) throw new IllegalStateException("FreeList has no phases."); if ( (item < 0) || (item >= currentPhase.getNextItem())) return false; long index = currentPhase.getTail(); long head = currentPhase.getHead(); try { while (index != head) { readTailBlock(getBlockId(index)); int offset = getBlockOffset(index++); long blockItem; blockItem = tailBlock.getLong(offset); if (item == blockItem) return false; if (getBlockOffset(index) == 0) { // Go to the next block. int blockId = tailBlock.getInt(IDX_NEXT); index = ( (long) blockId * BLOCK_SIZE) + HEADER_SIZE; } } } catch (IOException ex) { throw new Error(ex); } return true; } /** * Returns the ID of the block which contains the item with the specified * index. * * @param index the index of the item. * @return the block ID. */ private int getBlockId(long index) { return (int)(index / BLOCK_SIZE); } /** * Returns the offset (in ints) of the item with the specified index within * the block that contains it. * * @param index the index of the item. * @return the offset (in ints). */ private int getBlockOffset(long index) { return (int)(index % BLOCK_SIZE); } /** * Follows the chain of free list blocks and returns the highest block ID * encountered. * * @return the highest block ID. * @throws IOException if an I/O error occurs. */ private int findMaxBlockId() throws IOException { int maxBlockId = 0; int nextBlockId = 0; int prevBlockId = -1; do { Block block = blockFile.readBlock(nextBlockId); if ( (prevBlockId >= 0) && (block.getInt(IDX_PREV) != prevBlockId)) { // Bad previous block pointer. Fix it. logger.warn("Free list back pointer does not agree with forward pointer. Fixed."); block.putInt(IDX_PREV, prevBlockId); block.write(); } prevBlockId = nextBlockId; nextBlockId = block.getInt(IDX_NEXT); if (nextBlockId > maxBlockId) maxBlockId = nextBlockId; } while (nextBlockId != 0); if (prevBlockId < 0) throw new AssertionError("prevBlockId is negative."); // Check the previous block pointer in block 0. Block block = blockFile.readBlock(0); if (block.getInt(IDX_PREV) != prevBlockId) { // Bad previous block pointer. Fix it. logger.warn("Free list back pointer does not agree with forward pointer. Fixed."); block.putInt(IDX_PREV, prevBlockId); block.write(); } return maxBlockId; } /** * Removes any unused phases from the phase list. * * {@link #firstHead} and {@link #firstTail} will * be updated to the oldest phase still in use. */ private void removeClosedPhases() { // Caller must be synchronized. assert Thread.holdsLock(this); Phase phase = phases.getFirst(); while (!phase.isInUse() && (phases.size() > 1)) { phases.removeFirst(); phase = phases.getFirst(); firstHead = phase.getHead(); firstTail = phase.getTail(); } } /** * Allocates a buffer from blockFile to hold the head block. If the current * head buffer already holds the head block then nothing is done. If the head * buffer contains a different block then it is released first (writing the * head block to the file). This method uses a previous version of the * block, unless no block with that ID exists yet, in which case a new block * is read in, or allocated if it does not exist in the file either. If the * block existed in the file, then its contents will be ignored. Contrast * this to {@link #readHeadBlock} where the contents of the block on the disk * are always read. * * @param blockId The block number of the head block. * @throws IOException if an I/O error occurs. */ private void allocateHeadBlock(int blockId) throws IOException { if (headBlock != null) { if (blockId == (int) headBlock.getBlockId()) { headBlockDirty = false; return; } releaseHeadBlock(); } headBlock = findBlock(blockId); if (headBlock == null) headBlock = blockFile.allocateBlock(blockId); headBlockDirty = false; } /** * Reads the head block from blockFile. If the current head buffer already * holds the head block then nothing is done. If the head buffer contains a * different block then it is freed first (writing it to the file). This is * similar to {@link #allocateHeadBlock} only it will not allocate a new * block from the file, and always reads the contents of the existing block * in the file. * * @param blockId The block number of the head block. * @throws IOException if an I/O error occurs. */ private void readHeadBlock(int blockId) throws IOException { if (headBlock != null) { if (blockId == (int) headBlock.getBlockId()) return; releaseHeadBlock(); } headBlock = findBlock(blockId); if (headBlock == null) headBlock = blockFile.readBlock(blockId); headBlockDirty = false; } /** * Writes the current head buffer back to the block file. The write is only * done if the buffer is valid and is dirty. The dirty flag will be set to * false at the end of this operation. * * @throws IOException if an I/O error occurs. */ private void writeHeadBlock() throws IOException { if ( (headBlock != null) && headBlockDirty) { headBlock.write(); headBlockDirty = false; } } /** * Releases the current head buffer. After releasing, the head buffer is made * invalid. If the buffer is dirty and valid then it is written to the * blockFile first. * * @throws IOException if an I/O error occurs. */ private void releaseHeadBlock() throws IOException { if (headBlock != null) { if (headBlockDirty) writeHeadBlock(); headBlock = null; } } /** * Reads the reallocate block from blockFile. If the current reallocate buffer * already holds the reallocate block then nothing is done. If the reallocate * buffer contains a different block then it is released first. * * @param blockId The block number of the reallocate block. * @throws IOException if an I/O error occurs. */ private void readReallocateBlock(int blockId) throws IOException { if (reallocateBlock != null) { if (blockId == (int) reallocateBlock.getBlockId()) return; releaseReallocateBlock(); } reallocateBlock = findBlock(blockId); if (reallocateBlock == null) reallocateBlock = blockFile.readBlock(blockId); reallocateBlockDirty = false; } /** * Writes the current reallocate buffer back to the block file. The write is * only done if the buffer is valid and is dirty. The dirty flag will be set * to false at the end of this operation. * * @throws IOException if an I/O error occurs. */ private void writeReallocateBlock() throws IOException { if ((reallocateBlock != null) && reallocateBlockDirty) { reallocateBlock.write(); reallocateBlockDirty = false; } } /** * Releases the current reallocate buffer. After releasing, the reallocate * buffer is made invalid. If the buffer is dirty and valid then it is written * to the blockFile first. * * @throws IOException if an I/O error occurs. */ private void releaseReallocateBlock() throws IOException { if (reallocateBlock != null) { if (reallocateBlockDirty) writeReallocateBlock(); reallocateBlock = null; } } /** * Reads the tail block from blockFile. If the current tail buffer already * holds the tail block then nothing is done. If the tail buffer contains a * different block then it is freed first. * * @param blockId The block number to use as the tail buffer. * @throws IOException if an I/O error occurs. */ private void readTailBlock(int blockId) throws IOException { if (tailBlock != null) { if (blockId == (int) tailBlock.getBlockId()) return; releaseTailBlock(); } tailBlock = findBlock(blockId); if (tailBlock == null) tailBlock = blockFile.readBlock(blockId); } /** * Releases the current tail buffer. After releasing, the tail buffer is made invalid. */ private void releaseTailBlock() { if (tailBlock != null) tailBlock = null; } /** * Retrieve the block matching a given ID from one of the matching blocks. * This only finds blocks that are currently the head, tail, or reallocation * block. Once a block is found, it gets written to disk and the reference * that was using that block is cleared, so there will be only a single * reference to the block. * * @param blockId ID of the block to retrieve. * @return The block requested. * @throws IOException If an I/O error occurs. */ private Block findBlock(int blockId) throws IOException { if ( (headBlock != null) && ( (int) headBlock.getBlockId() == blockId)) { writeHeadBlock(); Block block = headBlock; headBlock = null; headBlockDirty = false; return block; } if ((reallocateBlock != null) && ((int)reallocateBlock.getBlockId() == blockId)) { writeReallocateBlock(); Block block = reallocateBlock; reallocateBlock = null; reallocateBlockDirty = false; return block; } if ( (tailBlock != null) && ( (int) tailBlock.getBlockId() == blockId)) { Block block = tailBlock; tailBlock = null; return block; } return null; } /** * This class represents the freed items in a single transaction phase. */ public final class Phase implements PersistableMetaRoot { /** The size of an on-disk phase in longs. */ public final static int RECORD_SIZE = 4; /** The index of the head pointer in the on-disk phase. */ final static int IDX_HEAD = 0; /** The index of the tail pointer in the on-disk phase. */ final static int IDX_TAIL = 1; /** The index of the next item field in the on-disk phase. */ final static int IDX_NEXT_ITEM = 2; /** The index of the number of valid items field in the on-disk phase. */ final static int IDX_NR_VALID_ITEMS = 3; /** * The number of the current transaction phase. Older phases have smaller * numbers. */ private int sequenceNumber; /** * The head of the freed items for this phase. This moves as the phase frees * more items. This value is an index into the free list file. */ private long head; /** * The tail of the freed items for this phase. This value is an index into * the free list file. */ private long tail; /** The next item available for allocation for this phase. */ private long nextItem; /** * The number of valid items in this phase. This is the number of * allocated items that have not yet been freed. */ private long nrValidItems; /** * The number of references to this phase. The phase is no longer in use * when this is decremented to 0. */ private int refCount = 0; /** * Reference to the token for this phase or <code>null</code> if there is no * current token. This is collectable by the GC and can be used to indicate * that the phase is no longer in use. */ private Reference<Token> tokenRef = null; /** Holds stack traces of use so we can tell where problems occurred. */ private List<StackTrace> stack = null; /** * Default constructor. This creates a new phase as a duplicate of the most * recent phase, and then adds it to the phase list of the enclosing * FreeList object. If no previous phases exist, then this new phase will * have its head, tail and nextItem valules all appropriately initialized. * * @throws IOException if an I/O error occurs. */ public Phase() throws IOException { synchronized (FreeList.this) { if (currentPhase != null) { removeClosedPhases(); sequenceNumber = currentPhase.sequenceNumber + 1; head = currentPhase.head; tail = currentPhase.tail; nextItem = currentPhase.nextItem; nrValidItems = currentPhase.nrValidItems; init(); } else { init(HEADER_SIZE, HEADER_SIZE, 0, 0); } } } /** * Constructor to build an empty transaction phase, which starts with a * given nextItem value. The enclosing FreeList object is initialized to use * this single phase. * * @param nextItem The starting point for new item allocations from this * phase. * @throws IllegalStateException if the enclosing FreeList already has a * phase, or is in an invalid state. * @throws IOException if an I/O error occurs. */ public Phase(long nextItem) throws IOException { this(HEADER_SIZE, HEADER_SIZE, nextItem, 0); } /** * Constructor. This creates a new phase as a duplicate of the specified phase, * and then adds it to the phase list of the enclosing FreeList object. * * @throws IOException if an I/O error occurs. */ public Phase(Phase p) throws IOException { synchronized (FreeList.this) { removeClosedPhases(); int index = phases.indexOf(p); if (index == -1) throw new IllegalStateException("Attempt to rollback to closed phase."); while (phases.size() > (index + 1)) { Phase removedPhase = (Phase)phases.removeLast(); assert removedPhase != p; } sequenceNumber = p.sequenceNumber + 1; head = p.head; tail = p.tail; nextItem = p.nextItem; nrValidItems = p.nrValidItems; init(); } } /** * Constructor to build a transaction phase, initializing its internal * values with a given Block. The enclosing FreeList object is initialized * to use this single phase. * * @param b The Block with the initialization values for the phase, in the * order: phase number; head; tail; nextItem. * @param offset The offset within the block to find the phase data. This offset can be due to a header appearing before the freelist data. * @throws IllegalStateException if the enclosing FreeList already has a * phase, or is in an invalid state. * @throws IOException if an I/O error occurs. */ public Phase(Block b, int offset) throws IOException { this( b.getLong(offset + IDX_HEAD), b.getLong(offset + IDX_TAIL), b.getLong(offset + IDX_NEXT_ITEM), b.getLong(offset + IDX_NR_VALID_ITEMS) ); } /** * Constructor to build a transaction phase, initializing its internal * values with given values. The enclosing FreeList object is initialized to * use this single phase. * * @param head The initial head of the freed items list. This will be * updated as new items are freed in the phase. * @param tail The tail of the freed items list. * @param nextItem The next item to allocate for this phase. * @param nrValidItems The number of valid items. This is the number of * items that have been allocated but not yet freed. * @throws IllegalStateException if the enclosing FreeList already has a * phase, or is in an invalid state. * @throws IOException if an I/O error occurs. */ private Phase( long head, long tail, long nextItem, long nrValidItems ) throws IOException { synchronized (FreeList.this) { if (currentPhase != null) { throw new IllegalStateException( "FreeList already has one initialized phase." ); } init(head, tail, nextItem, nrValidItems); if ( (getBlockId(head) >= nextBlockId) || (getBlockId(tail) >= nextBlockId) ) { throw new IllegalStateException( "Free list file may have been truncated: " + file ); } } } /** * Checks if this phase is in currently in use. If not then it may be * destroyed. * * @return true if this phase is in currently in use, false otherwise. */ public synchronized boolean isInUse() { getToken(); return refCount > 0; } /** * Gets the number of valid items in this phase. * * @return The number of valid items in this phase. */ public long getNrValidItems() { return nrValidItems; } /** * Writes the details of the current phase into a block. * * @param b The block to put the details into. * @param offset The distance of the logical phase block from the beginning * of <i>b</i>. */ public void writeToBlock(Block b, int offset) { b.putLong(offset + IDX_HEAD, head); b.putLong(offset + IDX_TAIL, tail); b.putLong(offset + IDX_NEXT_ITEM, nextItem); b.putLong(offset + IDX_NR_VALID_ITEMS, nrValidItems); } /** * Indicate that this phase is in use. Increments the reference count. * * @return A {@link Token} to represent the phase use. */ public synchronized Token use() { Token token = getToken(); if (token == null) { token = new Token(); tokenRef = new WeakReference<Token>(token); } ++refCount; // record the stack if debug is enabled if (logger.isDebugEnabled()) { assert stack != null; stack.add(new StackTrace()); } return token; } /** * Set a new head index within the file for this phase. * * @param head The new head index. */ void setHead(long head) { this.head = head; } /** * Set a new tail index within the file for this phase. * * @param tail The new tail index. */ void setTail(long tail) { this.tail = tail; } /** * Set the next unused item for this phase. * * @param nextItem the next item to allocate when the current * phase's free list is empty.. */ void setNextItem(long nextItem) { this.nextItem = nextItem; } /** * Gets the sequence number for the current phase. These start at zero * each time the Database is started. * * @return The sequence number for this phase. */ int getSequenceNumber() { return sequenceNumber; } /** * Gets the index of the head within the file for this phase. * * @return The head index for this phase. */ long getHead() { return head; } /** * Gets the index of the tail within the file for this phase. * * @return The tail index for this phase. */ long getTail() { return tail; } /** * The next free item for this phase. Only useful for the writing phase. * * @return The next item from this phase. */ long getNextItem() { return nextItem; } /** * Increment the number of valid items in this phase. */ void incNrValidItems() { ++nrValidItems; } /** * Reduce the number of valid items in this phase by one. */ void decNrValidItems() { assert nrValidItems > 0; --nrValidItems; } /** * Gets the token which represents a reference to the current phase. * A new token is created if no token is available and the phase is in use. */ private Token getToken() { Token token = (tokenRef != null) ? tokenRef.get() : null; if ( (token == null) && (refCount > 0)) { if (logger.isDebugEnabled()) { assert stack != null; logger.info("Lost phase token. Used " + stack.size() + " times:\n" + C.join(stack, "\n\n")); } refCount = 0; } return token; } /** * Sets initial values for this phase. Used by the various constructors. * * @param head The initial head of the freed items list. This will be * updated as new items are freed in the phase. * @param tail The tail of the freed items list. * @param nextItem The next item to allocate for this phase. * @param nrValidItems The number of valid items in this phase. * @throws IOException if an I/O error occurs. */ private void init( long head, long tail, long nextItem, long nrValidItems ) throws IOException { // Caller must be synchronized on FreeList.this. initFreeListFile(head == HEADER_SIZE && tail == HEADER_SIZE); long sizeInLongs = (nextItem + 1) / 2; itemToPhaseSeqMap.setSize(sizeInLongs); if ((nextItem & 1) == 1) { // Zero the unused part of the last long in the file. itemToPhaseSeqMap.putInt(nextItem, 0); } sequenceNumber = 0; this.head = head; firstHead = head; this.tail = tail; firstTail = tail; this.nextItem = nextItem; this.nrValidItems = nrValidItems; init(); } /** * Adds this phase to the phase list of the enclosing FreeList object. */ private void init() { if (logger.isDebugEnabled()) stack = new LinkedList<StackTrace>(); // Caller must be synchronized on FreeList.this. firstFree = head; reallocate = head; currentPhase = this; phases.addLast(this); } /** * Releases a reference to the current phase. When all references have been * released then the phase will no longer be valid. * * @return <code>true</code> when the phase has been completely release, and * <code>false</code> when there are remaining references. */ private synchronized boolean release() { assert getToken() != null:"released() when there is no valid token"; if (refCount == 0) throw new AssertionError("Attempt to release Phase with refCount == 0."); if (--refCount == 0) { tokenRef = null; return true; } return false; } /** * A class to represent a reference to the enclosing phase. * Used to hold onto phases, and to clean up any abandoned phases. */ public final class Token { private boolean valid = true; /** * Empty constructor, limits construction to the enclosing classes. */ Token() { // NO-OP } /** * Gets the {@link FreeList.Phase} that this token refers to. * * @return The Phase referred to by this token. */ public Phase getPhase() { assert valid:"Invalid Token"; return Phase.this; } /** * Release this reference to the phase. */ public void release() { assert valid:"Invalid Token"; if (Phase.this.release()) { valid = false; } } } } }