package com.neocoretechs.bigsack.io.pooled;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.neocoretechs.bigsack.ConcurrentArrayList;
import com.neocoretechs.bigsack.DBPhysicalConstants;
import com.neocoretechs.bigsack.io.IoManagerInterface;
import com.neocoretechs.bigsack.io.Optr;
import com.neocoretechs.bigsack.io.request.cluster.CompletionLatchInterface;
/**
* The MappedBlockBuffer is the buffer pool for each tablespace of each db. It functions
* as the request processor for IoManagerInterface implementors.
* The class functions as the used block list for BlockAccessIndex elements that represent our
* in memory pool of disk blocks. Its construction involves keeping track of the list of
* free blocks as well to move items between the two.
* There are one of these, per tablespace, per database, and act on a particular tablespace.
* The thread is designed to interact with latches and cyclic barriers bourne on the requests.
* Create the request with the appropriate instance of 'this' MappedBlockBuffer to call back
* methods here. Then the completion latch for that request is counted down.
* In the Master IO, the completion latch of the request is monitored for the proper number
* of counts. A series of cyclic barriers, for specific requests, allow barrier synchronization
* via the requests to ensure a consistent state before return to the thread processing. It is
* assumed the cyclic barriers and countdown latches are stored outside of this class to be used
* to synch operations coming from here. A corollary is the cyclic barriers used to internally
* synchronize then its for reuseability.
* @author jg
*
*/
public class MappedBlockBuffer extends ConcurrentHashMap<Long, BlockAccessIndex> implements Runnable {
private static final long serialVersionUID = -5744666991433173620L;
private static final boolean DEBUG = false;
private static final boolean NEWNODEPOSITIONDEBUG = false;
private boolean shouldRun = true;
private ConcurrentArrayList<BlockAccessIndex> freeBL; // free block list
private ObjectDBIO globalIO;
private IoManagerInterface ioManager;
private int tablespace;
private int minBufferSize = 10; // minimum number of buffers to reclaim on flush attempt
private BlockingQueue<CompletionLatchInterface> requestQueue;
/** use this to lock for read operations like get/iterator/contains.. */
private final Lock writeLock;
private static int POOLBLOCKS;
private static int QUEUEMAX = 256; // max requests before blocking
private static int cacheHit = 0; // cache hit rate
private static int cacheMiss = 0;
/**
* Construct the buffer for this tablespace and link the global IO manager
* @param ioManager
* @param tablespace
* @throws IOException If we try to add an active block the the freechain
*/
public MappedBlockBuffer(IoManagerInterface ioManager, int tablespace) throws IOException {
super(ioManager.getIO().getMAXBLOCKS()/DBPhysicalConstants.DTABLESPACES);
POOLBLOCKS = ioManager.getIO().getMAXBLOCKS()/DBPhysicalConstants.DTABLESPACES;
this.globalIO = ioManager.getIO();
this.ioManager = ioManager;
this.tablespace = tablespace;
this.freeBL = new ConcurrentArrayList<BlockAccessIndex>(POOLBLOCKS); // free blocks
// populate with blocks, they're all free for now
for (int i = 0; i < POOLBLOCKS; i++) {
freeBL.add(new BlockAccessIndex(true));
}
minBufferSize = POOLBLOCKS/10; // we need at least one
requestQueue = new ArrayBlockingQueue<CompletionLatchInterface>(QUEUEMAX, true); // true maintains FIFO order
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
writeLock = rwLock.writeLock();
}
public IoManagerInterface getIoManager() { return ioManager; }
public ObjectDBIO getGlobalIO() { return globalIO;}
/**
* acquireblk - get block from unused chunk or create a new blockchain. this method links a previous block<br>
* return acquired block
* Add a block to table of blocknums and block access index.
* No setting of block in BlockAccessIndex, no initial read reading through ioManager.addBlockAccessnoRead
* @param lastGoodBlk The block for us to link to
* @return The BlockAccessIndex
* @exception IOException if db not open or can't get block
*/
public BlockAccessIndex acquireblk(BlockAccessIndex lastGoodBlk) throws IOException {
long newblock;
BlockAccessIndex ablk = lastGoodBlk;
// this way puts it all in one tablespace
//int tbsp = getTablespace(lastGoodBlk.getBlockNum());
//int tbsp = new Random().nextInt(DBPhysicalConstants.DTABLESPACES);
newblock = GlobalDBIO.makeVblock(tablespace, ioManager.getNextFreeBlock(tablespace));
// update old block
ablk.getBlk().setNextblk(newblock);
ablk.getBlk().setIncore(true);
ablk.getBlk().setInlog(false);
//ablk.decrementAccesses();
// new block number for BlockAccessIndex set in addBlockAccessNoRead
BlockAccessIndex dblk = ioManager.addBlockAccessNoRead(new Long(newblock));
dblk.getBlk().setPrevblk(lastGoodBlk.getBlockNum());
dblk.getBlk().setNextblk(-1L);
dblk.getBlk().setBytesused((short) 0);
dblk.getBlk().setBytesinuse ((short)0);
dblk.getBlk().setPageLSN(-1L);
dblk.getBlk().setIncore(true);
dblk.getBlk().setInlog(false);
return dblk;
}
/**
* stealblk - get block from unused chunk or create chunk and get.
* We dont try to link it to anything because our little linked lists
* of instance blocks are not themselves linked to anything<br>
* return acquired block. Read through the ioManager calling addBlockAccessNoRead with new block
* @param currentBlk Current block so we can deallocate and replace it off chain
* @return The BlockAccessIndex of stolen blk
* @exception IOException if db not open or can't get block
*/
public BlockAccessIndex stealblk(BlockAccessIndex currentBlk) throws IOException {
long newblock;
if (currentBlk != null)
currentBlk.decrementAccesses();
newblock = GlobalDBIO.makeVblock(tablespace, ioManager.getNextFreeBlock(tablespace));
// new block number for BlockAccessIndex set in addBlockAccessNoRead
// calls setBlockNumber, which should up the access
BlockAccessIndex dblk = ioManager.addBlockAccessNoRead(new Long(newblock));
dblk.getBlk().setPrevblk(-1L);
dblk.getBlk().setNextblk(-1L);
dblk.getBlk().setBytesused((short) 0);
dblk.getBlk().setBytesinuse((short)0);
dblk.getBlk().setPageLSN(-1L);
dblk.getBlk().setIncore(false);
dblk.getBlk().setInlog(false);
return dblk;
}
/**
* Get an element from free list 0 and remove and return it
* @return
*/
public BlockAccessIndex take() {
return freeBL.remove(0);
}
public void put(BlockAccessIndex bai) {
freeBL.add(bai);
}
public void forceBufferClear() {
writeLock.lock();
try {
Enumeration<BlockAccessIndex> it = elements();
while(it.hasMoreElements()) {
BlockAccessIndex bai = (BlockAccessIndex) it.nextElement();
bai.resetBlock();
put(bai);
}
clear();
} finally {
writeLock.unlock();
}
}
public BlockAccessIndex getUsedBlock(long loc) {
if( DEBUG )
System.out.println("MappedBlockBuffer.getusedBlock Calling for USED BLOCK "+GlobalDBIO.valueOf(loc)+" "+loc);
BlockAccessIndex bai = get(loc);
if( bai == null ) ++cacheMiss;
else ++cacheHit;
return bai;
}
/**
* Get from free list, puts in used list of block access index.
* Comes here when we can't find blocknum in table in findOrAddBlock.
* New instance of BlockAccessIndex causes allocation
* @param Lbn block number to add
* @exception IOException if new dblock cannot be created
*/
private BlockAccessIndex addBlockAccess(Long Lbn) throws IOException {
// make sure we have open slots
if( DEBUG ) {
System.out.println("MappedBlockBuffer.addBlockAccess "+GlobalDBIO.valueOf(Lbn)+" "+this);
}
checkBufferFlush(Lbn);
BlockAccessIndex bai = take();
// ups access
bai.setBlockNumber(Lbn);
ioManager.FseekAndRead(Lbn, bai.getBlk());
put(Lbn, bai);
if( DEBUG ) {
System.out.println("MappedBlockBuffer.addBlockAccess "+GlobalDBIO.valueOf(Lbn)+" returning after freeBL take "+bai+" "+this);
}
return bai;
}
/**
* Add a block to table of blocknums and block access index.
* Comes here for acquireBlock. No setting of block in BlockAccessIndex, no initial read
* @param Lbn block number to add
* @exception IOException if new dblock cannot be created
*/
public BlockAccessIndex addBlockAccessNoRead(Long Lbn) throws IOException {
if( DEBUG ) {
System.out.println("MappedBlockBuffer.addBlockAccessNoRead "+GlobalDBIO.valueOf(Lbn)+" "+this);
}
// make sure we have open slots
checkBufferFlush(Lbn);
BlockAccessIndex bai = take();
// ups the access latch
bai.setBlockNumber(Lbn);
put(Lbn, bai);
if( DEBUG ) {
System.out.println("MappedBlockBuffer.addBlockAccessNoRead "+GlobalDBIO.valueOf(Lbn)+" returning after freeBL take "+bai+" "+this);
}
return bai;
}
public BlockAccessIndex findOrAddBlockAccess(long bn) throws IOException {
if( DEBUG ) {
System.out.println("MappedBlockBuffer.findOrAddBlockAccess "+GlobalDBIO.valueOf(bn)+" "+this);
}
Long Lbn = new Long(bn);
BlockAccessIndex bai = getUsedBlock(bn);
if( DEBUG ) {
System.out.println("MappedBlockBuffer.findOrAddBlockAccess "+GlobalDBIO.valueOf(bn)+" got block "+bai+" "+this);
}
if (bai != null) {
if( bai.getAccesses() == 0 )
bai.addAccess();
return bai;
}
// didn't find it, we must add
return addBlockAccess(Lbn);
}
/**
* seek_fwd - long seek forward from current spot
* @param offset offset from current
* @exception IOException If we cannot acquire next block
*/
public boolean seek_fwd(BlockAccessIndex tbai, long offset) throws IOException {
long runcount = offset;
BlockAccessIndex lbai = tbai;
do {
if (runcount >= (lbai.getBlk().getBytesused() - lbai.getByteindex())) {
runcount -= (lbai.getBlk().getBytesused() - lbai.getByteindex());
lbai = getnextblk(lbai);
if(lbai.getBlk().getNextblk() == -1)
return false;
} else {
lbai.setByteindex((short) (lbai.getByteindex() + runcount));
runcount = 0;
}
} while (runcount > 0);
return true;
}
/**
* new_node_position<br>
* determine location of new node, store in new_node_pos.
* Attempts to cluster entries in used blocks near insertion point
* @return The Optr pointing to the new node position
* @exception IOException If we cannot get block for new node
*/
public Optr getNewNodePosition(BlockAccessIndex lbai) throws IOException {
long blockNum = lbai.getBlockNum();
short bytesUsed = lbai.getBlk().getBytesused();
// ok, 5 bytes is rather arbitrary but seems a waste to start a big ole object so close to the end of a block
if (blockNum == -1L || lbai.getBlk().getBytesused()+5 >= DBPhysicalConstants.DATASIZE) {
BlockAccessIndex tbai = stealblk(lbai);
blockNum = tbai.getBlockNum();
bytesUsed = tbai.getBlk().getBytesused();
}
if( NEWNODEPOSITIONDEBUG )
System.out.println("MappedBlockBuffer.getNewNodePosition "+blockNum+" "+bytesUsed);
return new Optr(blockNum, bytesUsed);
}
/**
* Attempt to free pool blocks by the following:
* we will try to allocate at least 1 free block if size is 0, if we cant, throw exception
* If we must sweep buffer, try to fee up to minBufferSize
* We collect the elements and then transfer them from used to free block list
* 1) Try and roll through the buffer finding 0 access blocks
* 2) if 0 access block found, check if its in core, written and then deallocated but not written
* 3) If its in core and not in log, write the log then the block
* 4) If BOTH in core and in log, throw an exception, should NEVER be in core and under write.
* 5) If it is not the 0 index block per tablespace then add it to list of blocks to remove
* 6) check whether minimum blocks to recover reached, if so continue to removal
* 7) If no blocks reached at end, an error must be thrown
*
*/
public void checkBufferFlush(long Lbn) throws IOException {
writeLock.lock();
try {
int latched = 0;
int bufSize = this.size();
if( bufSize < POOLBLOCKS )
return;
Enumeration<BlockAccessIndex> elbn = this.elements();
int numGot = 0;
BlockAccessIndex[] found = new BlockAccessIndex[minBufferSize];// our candidates
while (elbn.hasMoreElements()) {
BlockAccessIndex ebaii = (elbn.nextElement());
if( DEBUG ) {
System.out.println("MappedBlockBuffer.checkBufferFlush Block buffer "+ebaii);
if( ebaii.getAccesses() > 1 )
System.out.println("****COMMIT BUFFER access "+ebaii.getAccesses()+" for buffer "+ebaii);
}
assert(!(ebaii.getBlk().isIncore() && ebaii.getBlk().isInlog())) : "****COMMIT BUFFER block in core and log simultaneously! "+ebaii;
if (Lbn != ebaii.getBlockNum() && ebaii.getAccesses() <= 1) {
if(ebaii.getBlk().isIncore()) {
continue; // cant throw out this one
//ioManager.getUlog(tablespace).writeLog(ebaii); // will set incore, inlog, and push to raw store via applyChange of Loggable
//throw new IOException("Accesses 0 but incore true " + ebaii);
}
// Dont toss block at 0,0. its our BTree root and we will most likely need it soon
if( ebaii.getBlockNum() == 0L )
continue;
found[numGot] = ebaii;
if( ++numGot == minBufferSize ) {
break;
}
} else {
if( DEBUG )
if( ebaii.getAccesses() > 1 ) System.out.println("FLUSH:"+ebaii);
++latched;
}
}
//
// We found none this way, proceed to look for accessed blocks
int latched2 = 0;
if( numGot == 0 ) {
elbn = this.elements();
while (elbn.hasMoreElements()) {
BlockAccessIndex ebaii = (elbn.nextElement());
if( DEBUG )
System.out.println("MappedBlockBuffer.checkBufferFlush PHASE II Block buffer "+ebaii+" "+this);
if(Lbn != ebaii.getBlockNum() && ebaii.getAccesses() == 1) {
if(ebaii.getBlk().isIncore() && !ebaii.getBlk().isInlog()) {
if( DEBUG )
System.out.println("MappedBlockBuffer.checkBufferFlush set to write pool entry to log "+ebaii);
ioManager.getUlog(tablespace).writeLog(ebaii); // will set incore, inlog, and push to raw store via applyChange of Loggable
//throw new IOException("Accesses 0 but incore true " + ebaii);
}
// Dont toss block at 0,0. its our BTree root and we will most likely need it soon
if( ebaii.getBlockNum() == 0L )
continue;
found[numGot] = ebaii;
if( ++numGot == minBufferSize ) {
break;
}
} else {
++latched2;
}
}
if( numGot == 0 ) {
if( DEBUG ) {
Enumeration elems = this.elements();
while(elems.hasMoreElements())
System.out.println(elems.nextElement());
}
throw new IOException("Unable to free up blocks in buffer pool with "+latched+" singley and "+latched2+" MULTIPLY latched.");
}
}
for(int i = 0; i < numGot; i++) {
if( found[i] != null ) {
this.remove(found[i].getBlockNum());
// reset all to zero before re-freechain
found[i].resetBlock();
freeBL.add(found[i]);
}
}
} finally {
writeLock.unlock();
}
}
/**
* Commit all outstanding blocks in the buffer.
* @throws IOException
*/
public void commitBufferFlush() throws IOException {
writeLock.lock();
try {
Enumeration<BlockAccessIndex> elbn = this.elements();
while (elbn.hasMoreElements()) {
BlockAccessIndex ebaii = (elbn.nextElement());
if( ebaii.getAccesses() > 1 )
throw new IOException("****COMMIT BUFFER access "+ebaii.getAccesses()+" for buffer "+ebaii);
if(ebaii.getBlk().isIncore() && ebaii.getBlk().isInlog())
throw new IOException("****COMMIT BUFFER block in core and log simultaneously! "+ebaii);
if (ebaii.getAccesses() < 2) {
if(ebaii.getBlk().isIncore() && !ebaii.getBlk().isInlog()) {
ioManager.getUlog(tablespace).writeLog(ebaii); // will set incore, inlog, and push to raw store via applyChange of Loggable
}
ebaii.decrementAccesses();
ebaii.getBlk().resetBlock();
freeBL.add(ebaii);
}
}
clear();
cacheHit = 0;
cacheMiss = 0;
} finally {
writeLock.unlock();
}
}
/**
* Commit all outstanding blocks in the buffer, bypassing the log subsystem. Should be used with forethought
* @throws IOException
*/
public void directBufferWrite() throws IOException {
writeLock.lock();
try {
Enumeration<BlockAccessIndex> elbn = this.elements();
if(DEBUG) System.out.println("MappedBlockBuffer.direct buffer write");
while (elbn.hasMoreElements()) {
BlockAccessIndex ebaii = (elbn.nextElement());
if (ebaii.getAccesses() == 0 && ebaii.getBlk().isIncore() ) {
if(DEBUG)
System.out.println("fully writing "+ebaii.getBlockNum()+" "+ebaii.getBlk());
ioManager.FseekAndWriteFully(ebaii.getBlockNum(), ebaii.getBlk());
ioManager.Fforce();
ebaii.getBlk().setIncore(false);
}
}
} finally {
writeLock.unlock();
}
}
/**
* Find or add the block to in-mem list. First deallocate the currently
* used block, get the new block, then allocate it
* @param tbn The virtual block to retrieve
*
* @exception IOException If low-level access fails
*/
public BlockAccessIndex findOrAddBlock(long tbn) throws IOException {
int tblsp = GlobalDBIO.getTablespace(tbn);
BlockAccessIndex lbai = findOrAddBlockAccess(tbn);
assert( tblsp == tablespace ) : "Block retrieveal misdelegated to tablespace block buffer for "+tbn+" "+tblsp+" from:"+tablespace;
if( DEBUG )
System.out.println("BlockDBIO.findOrAddBlock tablespace "+tblsp+" pos:"+GlobalDBIO.valueOf(tbn)+" current:"+lbai);
return lbai;
}
/**
* getnextblk - read the next chained Datablock
* @return true if success
* @exception IOException If we cannot read next block
*/
public BlockAccessIndex getnextblk(BlockAccessIndex lbai) throws IOException {
lbai.decrementAccesses();
if (lbai.getBlk().getNextblk() == -1L) {
//if( DEBUG )
// System.out.println("MappedBlockBuffer.getnextblk returning with no next block "+lbai);
return null;
}
if( DEBUG )
System.out.println("MappedBlockBuffer.getnextblk next block fetch:"+lbai);
return findOrAddBlock(lbai.getBlk().getNextblk());
}
/**
* readn - read n bytes from pool
* @param buf byte buffer to fill
* @param numbyte number of bytes to read
* @return number of bytes read
* @exception IOException If we cannot acquire next block
*/
public int readn(BlockAccessIndex lbai, byte[] buf, int numbyte) throws IOException {
BlockAccessIndex tblk;
int i = 0, runcount = numbyte, blkbytes;
// see if we need the next block to start
// and flag our position
if (lbai.getByteindex() >= lbai.getBlk().getBytesused())
if((tblk=getnextblk(lbai)) != null) {
lbai=tblk;
return (i != 0 ? i : -1);
}
for (;;) {
blkbytes = lbai.getBlk().getBytesused() - lbai.getByteindex();
if (runcount > blkbytes) {
runcount -= blkbytes;
System.arraycopy(
lbai.getBlk().getData(),
lbai.getByteindex(),
buf,
i,
blkbytes);
lbai.setByteindex((short) (lbai.getByteindex() + (short)blkbytes));
i += blkbytes;
if ((tblk=getnextblk(lbai)) != null) {
lbai=tblk;
} else {
return (i != 0 ? i : -1);
}
} else {
System.arraycopy(
lbai.getBlk().getData(),
lbai.getByteindex(),
buf,
i,
runcount);
lbai.setByteindex((short) (lbai.getByteindex() + runcount));
i += runcount;
return (i != 0 ? i : -1);
}
}
}
/**
* readn - read n bytes from pool
* @param buf byte buffer to fill
* @param numbyte number of bytes to read
* @return number of bytes read
* @exception IOException If we cannot acquire next block
*/
public int readn(BlockAccessIndex lbai, ByteBuffer buf, int numbyte) throws IOException {
BlockAccessIndex tblk;
int i = 0, runcount = numbyte, blkbytes;
// see if we need the next block to start
// and flag our position
if (lbai.getByteindex() >= lbai.getBlk().getBytesused())
if((tblk=getnextblk(lbai)) != null) {
lbai=tblk;
} else {
return (i != 0 ? i : -1);
}
for (;;) {
blkbytes = lbai.getBlk().getBytesused() - lbai.getByteindex();
if (runcount > blkbytes) {
runcount -= blkbytes;
buf.position(i);
buf.put(lbai.getBlk().getData(), lbai.getByteindex(), blkbytes);
lbai.setByteindex((short) (lbai.getByteindex() + (short)blkbytes));
i += blkbytes;
if((tblk=getnextblk(lbai)) != null) {
lbai=tblk;
} else {
return (i != 0 ? i : -1);
}
} else {
buf.position(i);
buf.put(lbai.getBlk().getData(), lbai.getByteindex(), runcount);
lbai.setByteindex((short) (lbai.getByteindex() + runcount));
i += runcount;
return (i != 0 ? i : -1);
}
}
}
/**
* readi - read 1 byte from pool.
* This method designed to be called from DBInput.
* @return the byte as integer for InputStream
* @exception IOException If we cannot acquire next block
*/
public int readi(BlockAccessIndex lbai) throws IOException {
BlockAccessIndex tblk;
// see if we need the next block to start
// and flag our position
if (lbai.getByteindex() >= lbai.getBlk().getBytesused()) {
if((tblk=getnextblk(lbai)) == null) {
return -1;
}
lbai = tblk;
}
int ret = lbai.getBlk().getData()[lbai.getByteindex()] & 255;
lbai.setByteindex((short) (lbai.getByteindex() + 1));
return ret;
}
/**
* writen - write n bytes to pool. This
* will overwrite to next block if necessary, or allocate from end
* @param buf byte buffer to write
* @param numbyte number of bytes to write
* @return number of bytes written
* @exception IOException if can't acquire new block
*/
public int writen(BlockAccessIndex lbai, byte[] buf, int numbyte) throws IOException {
BlockAccessIndex tblk;
int i = 0, runcount = numbyte, blkbytes;
// see if we need the next block to start
// and flag our position
if (lbai.getByteindex() >= DBPhysicalConstants.DATASIZE)
if ((tblk=getnextblk(lbai)) == null) {
lbai = acquireblk(lbai);
} else {
lbai = tblk;
}
//
for (;;) {
blkbytes = DBPhysicalConstants.DATASIZE - lbai.getByteindex();
if (runcount > blkbytes) {
runcount -= blkbytes;
System.arraycopy(
buf,
i,
lbai.getBlk().getData(),
lbai.getByteindex(),
blkbytes);
lbai.setByteindex((short) (lbai.getByteindex() + (short)blkbytes));
i += blkbytes;
lbai.getBlk().setBytesused(DBPhysicalConstants.DATASIZE);
//update control info
lbai.getBlk().setBytesinuse(DBPhysicalConstants.DATASIZE);
lbai.getBlk().setIncore(true);
lbai.getBlk().setInlog(false);
if ((tblk=getnextblk(lbai)) == null) {
lbai = acquireblk(lbai);
} else {
lbai = tblk;
}
} else {
System.arraycopy(
buf,
i,
lbai.getBlk().getData(),
lbai.getByteindex(),
runcount);
lbai.setByteindex((short) (lbai.getByteindex() + runcount));
i += runcount;
if (lbai.getByteindex() > lbai.getBlk().getBytesused()) {
//update control info
lbai.getBlk().setBytesused(lbai.getByteindex());
lbai.getBlk().setBytesinuse(lbai.getBlk().getBytesused());
}
lbai.getBlk().setIncore(true);
lbai.getBlk().setInlog(false);
return i;
}
}
}
/**
* writen - write n bytes to pool. This will overwrite to next block if necessary, or allocate from end
* The blocks written have their 'inCore' property set to true and their 'inLog' property set to false.
* The is used in the Seekable DB channel that moves data from store to pool
* @param buf byte buffer to write
* @param numbyte number of bytes to write
* @return number of bytes written
* @exception IOException if can't acquire new block
*/
public int writen(BlockAccessIndex lbai, ByteBuffer buf, int numbyte) throws IOException {
BlockAccessIndex tblk;
int i = 0, runcount = numbyte, blkbytes;
// see if we need the next block to start
// and flag our position
// sets the incore to true and the inlog to false on both blocks
if (lbai.getByteindex() >= DBPhysicalConstants.DATASIZE)
if((tblk=getnextblk(lbai)) == null) {
lbai = acquireblk(lbai);
} else {
lbai = tblk;
}
//
for (;;) {
blkbytes = DBPhysicalConstants.DATASIZE - lbai.getByteindex();
if (runcount > blkbytes) {
runcount -= blkbytes;
buf.position(i);
buf.get(lbai.getBlk().getData(), lbai.getByteindex(), blkbytes);
lbai.setByteindex((short) (lbai.getByteindex() + (short)blkbytes));
i += blkbytes;
lbai.getBlk().setBytesused(DBPhysicalConstants.DATASIZE);
//update control info
lbai.getBlk().setBytesinuse(DBPhysicalConstants.DATASIZE);
lbai.getBlk().setIncore(true);
lbai.getBlk().setInlog(false);
if ((tblk=getnextblk(lbai)) == null) {
lbai = acquireblk(lbai);
} else {
lbai = tblk;
}
} else {
buf.position(i);
buf.get(lbai.getBlk().getData(), lbai.getByteindex(), runcount);
lbai.setByteindex((short) (lbai.getByteindex() + runcount));
i += runcount;
if (lbai.getByteindex() >= lbai.getBlk().getBytesused()) {
//update control info
lbai.getBlk().setBytesused(lbai.getByteindex());
lbai.getBlk().setBytesinuse(lbai.getBlk().getBytesused());
}
lbai.getBlk().setIncore(true);
lbai.getBlk().setInlog(false);
return i;
}
}
}
/**
* writei - write 1 byte to pool.
* This method designed to be called from DBOutput.
* Will overwrite to next blk if necessary.
* @param byte to write
* @exception IOException If cannot acquire new block
*/
public void writei(BlockAccessIndex lbai, int tbyte) throws IOException {
BlockAccessIndex tblk;
// see if we need the next block to start
// and flag our position
if (lbai.getByteindex() >= DBPhysicalConstants.DATASIZE)
if((tblk = getnextblk(lbai)) == null) {
lbai = acquireblk(lbai);
} else {
lbai = tblk;
}
if (!lbai.getBlk().isIncore())
lbai.getBlk().setIncore(true);
if (lbai.getBlk().isInlog())
lbai.getBlk().setInlog(false);
lbai.getBlk().getData()[lbai.getByteindex()] = (byte) tbyte;
lbai.setByteindex((short) (lbai.getByteindex() + 1));
if (lbai.getByteindex() > lbai.getBlk().getBytesused()) {
//update control info
lbai.getBlk().setBytesused( lbai.getByteindex()) ;
lbai.getBlk().setBytesinuse(lbai.getBlk().getBytesused());
}
}
/**
* deleten - delete n bytes from object / directory
* @param osize number bytes to delete
* @return true if success
* @exception IOException If we cannot write block
*/
public boolean deleten(BlockAccessIndex lbai, int osize) throws IOException {
BlockAccessIndex tblk;
int runcount = osize;
if (osize <= 0)
throw new IOException("Attempt to delete object with size invalid: " + osize);
for (;;) {
// bytesused is high water mark, bytesinuse is # bytes occupied by data
// we assume contiguous data
// this case spans whole block or block to end
//
int bspan = (lbai.getBlk().getBytesused() - lbai.getByteindex());
if (runcount >= bspan) {
runcount -= bspan;
lbai.getBlk().setBytesinuse((short) (lbai.getBlk().getBytesinuse() - bspan));
// delete contiguously to end of block
// byteindex is start of del entry
// which is new high water byte count
// since everything to end of block is going
lbai.getBlk().setBytesused(lbai.getByteindex());
} else {
// we span somewhere in block to not end
lbai.getBlk().setBytesinuse((short) (lbai.getBlk().getBytesinuse() - runcount));
runcount = 0;
}
// assertion
if (lbai.getBlk().getBytesinuse() < 0)
throw new IOException(this.toString() + " negative bytesinuse "+lbai.getBlk().getBytesinuse()+" from runcount "+runcount);
//
lbai.getBlk().setIncore(true);
lbai.getBlk().setInlog(false);
//
if (runcount > 0) {
if((tblk = getnextblk(lbai)) == null)
throw new IOException(
"attempted delete past end of chain for "
+ osize
+ " bytes in "
+ lbai);
lbai = tblk;
} else
break;
}
return true;
}
public String toString() {
return "MappedBlockBuffer tablespace "+tablespace+" blocks:"+this.size()+" free:"+freeBL.size()+" requests:"+requestQueue.size()+" cache hit="+cacheHit+" miss="+cacheMiss;
}
public void queueRequest(CompletionLatchInterface ior) {
try {
requestQueue.put(ior);
} catch (InterruptedException e) {
// executor calls for shutdown during wait for queue to process entries while full or busy
}
}
@Override
public void run() {
CompletionLatchInterface ior = null;
while(shouldRun) {
try {
ior = requestQueue.take();
} catch (InterruptedException e) {
// executor calling for shutdown
break;
}
try {
ior.setTablespace(tablespace);
if( DEBUG ) {
System.out.println("MappedBlockBuffer.run processing request "+ior+" "+this);
}
ior.process();
} catch (IOException e) {
System.out.println("MappedBlockBuffer exception processing request "+ior+" "+e);
break;
}
}
}
}