/* * 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; // Java 2 standard packages import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import org.mulgara.util.io.MappingUtil; /** * An implementation of BlockFile which uses memory mapped file IO. * Rather than mapping the entire file in one go, it gets mapped into * a series of regions. * * @created 2001-09-20 * * @author David Makepeace * * @version $Revision: 1.10 $ * * @modified $Date: 2005/06/30 01:14:40 $ * * @maintenanceAuthor $Author: pgearon $ * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @copyright ©2001 <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 MappedBlockFile extends AbstractBlockFile { /** * The size of each mapped region of the file. By mapping all files in * regions of this size we avoid fragmenting virtual memory. */ public final static long REGION_SIZE = 8 * 1024 * 1024; /** * The system page size. The offset into a file that a mapped region starts * at will be a multiple of this value. If this value is not equal to or * greater than the actual hardware page size then attempts to map regions * from overlapping byte ranges of the file (i.e. if the block size is not a * power of two) may fail. */ private final static long PAGE_SIZE = 8192; /** An initial number of mapped regions in the file. */ private final static int INITIAL_NR_REGIONS = 1024; /** An array of mapped region buffers in the file. */ private MappedByteBuffer[] mappedByteBuffers; /** Used as the source buffer when doing block moves between buffers. */ private ByteBuffer[] srcByteBuffers; /** The mappedByteBuffers cast as integer buffers. */ private IntBuffer[] intBuffers; /** The mappedByteBuffers cast as long buffers. */ private LongBuffer[] longBuffers; /** The number of mapped regions in the file. Equal to mappedByteBuffers.length. */ private int nrMappedRegions = 0; /** The distance from the beginning of one mapped region to the next. */ private long stride; /** * Constructs a MappedBlockFile for the file with the specified file name. * * @param file the block file. * @param blockSize the size of blocks in the block file. * @throws IOException if an I/O error occurs. */ MappedBlockFile(File file, int blockSize) throws IOException { super(file, blockSize); stride = REGION_SIZE; if (blockSize != (1 << XAUtils.log2(blockSize))) { // blockSize is not a power of 2. if (blockSize > (REGION_SIZE / 2)) { throw new IllegalArgumentException( "blockSize for " + file + " is too large: " + blockSize ); } // Adjust stride so the mappings overlap by at least a blockSize. stride -= (((blockSize + PAGE_SIZE) - 1) & ~ (PAGE_SIZE - 1)); } else if (blockSize > REGION_SIZE) { throw new IllegalArgumentException( "blockSize for " + file + " is too large: " + blockSize ); } int nrRegions = (nrBlocks > 0) ? (int) ((((nrBlocks - 1) * blockSize) / stride) + 1) : 0; int maxNrRegions = INITIAL_NR_REGIONS; while (maxNrRegions < nrRegions) { maxNrRegions *= 2; } mappedByteBuffers = new MappedByteBuffer[maxNrRegions]; srcByteBuffers = new ByteBuffer[maxNrRegions]; intBuffers = new IntBuffer[maxNrRegions]; longBuffers = new LongBuffer[maxNrRegions]; if (nrRegions > 0) { mapFile(nrRegions); } } /** * Constructs a MappedBlockFile for the file with the specified file name. * * @param fileName the file name of the block file. * @param blockSize the size of blocks in the block file. * @throws IOException if an I/O error occurs. */ MappedBlockFile(String fileName, int blockSize) throws IOException { this(new File(fileName), blockSize); } /** * Sets the length of the file in blocks. The file will not be expanded if it * is already large enough. The file will be truncated to the correct length * when {@link #close} is called. * * @param nrBlocks the length of the file in blocks. * @throws IOException if an I/O error occurs. */ public void setNrBlocks(long nrBlocks) throws IOException { if (nrBlocks == this.nrBlocks) return; long prevNrBlocks = this.nrBlocks; super.setNrBlocks(nrBlocks); if (nrBlocks <= prevNrBlocks) return; // Call mapFile() if the file must grow in size. int nrRegions = (nrBlocks > 0) ? (int) ((((nrBlocks - 1) * blockSize) / stride) + 1) : 0; if (nrRegions > nrMappedRegions) { mapFile(nrRegions); } } /** * Truncates the file to zero length. * * @throws IOException if an I/O error occurs. */ public void clear() throws IOException { int maxNrRegions = INITIAL_NR_REGIONS; mappedByteBuffers = new MappedByteBuffer[maxNrRegions]; srcByteBuffers = new ByteBuffer[maxNrRegions]; intBuffers = new IntBuffer[maxNrRegions]; longBuffers = new LongBuffer[maxNrRegions]; nrMappedRegions = 0; /* if (System.getProperty("os.name").startsWith("Win")) { // This is needed for Windows. System.gc(); try { Thread.sleep(100); } catch (InterruptedException ie) { } System.runFinalization(); } */ super.clear(); } /** * Ensures that all data for this BlockFile is stored in persistent storage * before returning. * * @throws IOException if an I/O error occurs. */ public synchronized void force() throws IOException { for (int i = 0; i < nrMappedRegions; ++i) { mappedByteBuffers[i].force(); } } /** * Allocates a ByteBuffer to be used for writing to the specified block. The * contents of the ByteBuffer are undefined. * * @param blockId The ID of the block that this buffer will be written to. * @return a ByteBuffer to be used for writing to the specified block. */ public Block allocateBlock(long blockId) { return readBlock(blockId); } /** * Frees a buffer that was allocated by calling either {@link #allocateBlock} * or {@link #readBlock}. The buffer should not be used after it has been * freed. * * @param block the buffer to be freed. */ public void releaseBlock(Block block) { } /** * Allocates a ByteBuffer which is filled with the contents of the specified * block. If the buffer is modified then the method {@link #writeBlock} * should be called to write the buffer back to the file. * @param blockId the block to read into the ByteBuffer. * @return The block that was read. */ public Block readBlock(long blockId) { if ((blockId < 0) || (blockId >= nrBlocks)) { throw new IllegalArgumentException("blockId: " + blockId + " of " + nrBlocks); } long fileOffset = blockId * blockSize; int regionNr = (int) (fileOffset / stride); int offset = (int) (fileOffset % stride); assert mappedByteBuffers != null; assert srcByteBuffers != null; assert intBuffers != null; assert longBuffers != null; return Block.newInstance( this, blockSize, blockId, offset, mappedByteBuffers[regionNr], srcByteBuffers[regionNr], intBuffers[regionNr], longBuffers[regionNr] ); } /** * Writes a buffer that was allocated by calling either {@link * #allocateBlock} or {@link #readBlock} to the specified block. The buffer * may only be written to the same block as was specified when the buffer was * allocated. * * @param block the buffer to write to the file. */ public void writeBlock(Block block) { assert(block.getBlockId() >= 0) && (block.getBlockId() < nrBlocks); // NO-OP - this is because mapped buffers are automatically written } /** * Allocates a new block for dstBlockId, copies the contents of srcBlock to * it, releases srcBlock and returns the new Block. * * @param block The block to copy data from. This block will be set to the * destination block before returning, and acts as the return value. * @param dstBlockId the ID of the new Block to be allocated. */ public void copyBlock(Block block, long dstBlockId) { assert(block.getBlockId() >= 0) && (block.getBlockId() < nrBlocks); assert(dstBlockId >= 0) && (dstBlockId < nrBlocks); assert block.getBlockId() != dstBlockId; int byteOffset = block.getByteOffset(); ByteBuffer srcBuffer = block.getSourceBuffer(); srcBuffer.limit(byteOffset + blockSize); srcBuffer.position(byteOffset); long dstFileOffset = dstBlockId * blockSize; int dstRegionNr = (int) (dstFileOffset / stride); int dstByteOffset = (int) (dstFileOffset % stride); ByteBuffer dstBuffer = mappedByteBuffers[dstRegionNr]; dstBuffer.position(dstByteOffset); // Copy the bytes. dstBuffer.put(srcBuffer); block.init( dstBlockId, dstByteOffset, dstBuffer, srcByteBuffers[dstRegionNr], intBuffers[dstRegionNr], longBuffers[dstRegionNr] ); } /** * Attempt to re-use the given Block and wrapped ByteBuffer to read the indicated block. * null ByteBuffer will behave like readBlock. * @author barmintor * @param blockId The block to read into the ByteBuffer. * @param block The ByteBuffer to attempt to re-use * @return The buffer that was read. * @throws IOException if an I/O error occurs. */ public Block recycleBlock(long blockId, Block block) throws IOException { return readBlock(blockId); } /** * Discards all file mappings, allowing the garbage collector to unmap the file. */ public synchronized void unmap() { // Discard the file mappings. MappingUtil.release(mappedByteBuffers); mappedByteBuffers = null; srcByteBuffers = null; intBuffers = null; longBuffers = null; nrMappedRegions = 0; } /** * Expands the file to contain nrRegions regions and maps the additional * regions. * * @param nrRegions The number of regions to expand to. Must be greater than the current * number of regions. * @throws IOException if an I/O error occurs. */ private synchronized void mapFile(int nrRegions) throws IOException { assert nrRegions > nrMappedRegions; // Check if the buffer arrays need to be increased in size. int maxNrRegions = mappedByteBuffers.length; if (maxNrRegions < nrRegions) { do { maxNrRegions *= 2; } while (maxNrRegions < nrRegions); MappedByteBuffer[] mbbs = new MappedByteBuffer[maxNrRegions]; ByteBuffer[] sbbs = new ByteBuffer[maxNrRegions]; IntBuffer[] ibs = new IntBuffer[maxNrRegions]; LongBuffer[] lbs = new LongBuffer[maxNrRegions]; System.arraycopy(mappedByteBuffers, 0, mbbs, 0, nrMappedRegions); System.arraycopy(srcByteBuffers, 0, sbbs, 0, nrMappedRegions); System.arraycopy(intBuffers, 0, ibs, 0, nrMappedRegions); System.arraycopy(longBuffers, 0, lbs, 0, nrMappedRegions); mappedByteBuffers = mbbs; srcByteBuffers = sbbs; intBuffers = ibs; longBuffers = lbs; } long mappedSize = (nrMappedRegions > 0) ? (((nrMappedRegions - 1) * stride) + REGION_SIZE) : 0; // Get the current file size. long currentFileSize = 0L; for (;;) { try { currentFileSize = fc.size(); break; } catch (ClosedChannelException ex) { // The Channel may have been inadvertently closed by another thread // being interrupted. Attempt to reopen the channel. if (!ensureOpen()) { throw ex; } // Loop back and retry the size(). } } if (currentFileSize < mappedSize) { throw new Error("File has shrunk: " + file); } long fileSize = ((nrRegions - 1) * stride) + REGION_SIZE; // Expand the file. for (;;) { try { raf.setLength(fileSize); break; } catch (ClosedChannelException ex) { // The Channel may have been inadvertently closed by another thread // being interrupted. Attempt to reopen the channel. if (!ensureOpen()) { throw ex; } // Loop back and retry the setLength(). } } for (int regionNr = nrMappedRegions; regionNr < nrRegions; ++regionNr) { int retries = 10; for (;;) { try { MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, regionNr * stride, REGION_SIZE ); mbb.order(byteOrder); mappedByteBuffers[regionNr] = mbb; srcByteBuffers[regionNr] = mbb.asReadOnlyBuffer(); mbb.rewind(); intBuffers[regionNr] = mbb.asIntBuffer(); mbb.rewind(); longBuffers[regionNr] = mbb.asLongBuffer(); break; } catch (ClosedChannelException ex) { // The Channel may have been inadvertently closed by another thread // being interrupted. Attempt to reopen the channel. if (!ensureOpen()) { throw ex; } // Loop back and retry the map(). } catch (IOException ex) { // Rethrow the exception if we are out of retries. if (retries-- == 0) { throw ex; } // Let some old mappings go away and try again. MappingUtil.systemCleanup(); } } } nrMappedRegions = nrRegions; } }