com.bigdata.journal.BasicBufferStrategy Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.journal;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import com.bigdata.counters.CounterSet;
import com.bigdata.io.FileChannelUtility;
/**
* Implements logic to read from and write on a buffer. This is sufficient
* for a {@link BufferMode#Transient} implementation or a
* {@link BufferMode#Mapped} implementation, but the
* {@link BufferMode#Direct} implementation needs to also implement write
* through to the disk.
*
* @author Bryan Thompson
* @version $Id$
*/
abstract public class BasicBufferStrategy extends AbstractBufferStrategy {
/**
* A buffer containing a write through image of the backing file. The image
* begins after the root blocks, making it impossible to write on the root
* blocks using the buffer. The offset of the image into the backing file is
* given by {@link FileMetadata#headerSize0}.
*
* Note: The {@link #buffer} reference is updated by {@link #truncate(long)}.
* Since both readers and writers MUST use the current value for this
* variable it is marked as volatile
.
*
* Note: Access to the buffer's internal state (its offset and limit) MUST
* be serialized in order to prevent a concurrent modification of the
* buffer's internal state resulting in an inconsistent view.
* Synchronization is on this (the {@link BasicBufferStrategy}
* reference).
*/
private volatile ByteBuffer buffer;
/**
* @param readOnly
* @return
*/
synchronized protected ByteBuffer getBufferView(boolean readOnly) {
if (readOnly) {
return buffer.asReadOnlyBuffer();
} else {
return buffer.duplicate();
}
}
/**
* The size of the journal header, including MAGIC, version, and both root
* blocks. This is used as an offset when computing the address of a record
* in an underlying file and is ignored by buffer modes that are not backed
* by a file (e.g., transient) or that are memory mapped (since the map is
* setup to skip over the header)
*/
final int headerSize;
/**
* The current length of the backing file in bytes.
*/
protected long extent;
/**
* The size of the user data space in bytes.
*/
protected long userExtent;
public long getExtent() {
return extent;
}
public long getUserExtent() {
return userExtent;
}
BasicBufferStrategy(long maximumExtent, int offsetBits, long nextOffset,
int headerSize, long extent, BufferMode bufferMode,
ByteBuffer buffer, boolean readOnly) {
super(extent, maximumExtent, offsetBits, nextOffset, bufferMode, readOnly);
this.buffer = buffer;
this.extent = extent;
this.headerSize = headerSize;
this.userExtent = extent - headerSize;
}
/**
* Releases the buffer.
*/
public void close() {
super.close();
// release the buffer.
buffer = null;
}
public long write(final ByteBuffer data) {
if (data == null)
throw new IllegalArgumentException(ERR_BUFFER_NULL);
if (isReadOnly())
throw new IllegalStateException(ERR_READ_ONLY);
// #of bytes to store.
final int nbytes = data.remaining();
if (nbytes == 0)
throw new IllegalArgumentException(ERR_BUFFER_EMPTY);
/*
* Write the record on the buffer.
*
* Note: This is synchronized in order to support concurrent writers.
*/
final long addr;
synchronized(this) // @todo probably can be synchronized(nextOffset)
{
// the next offset.
final long offset = nextOffset.get();
// formulate the address that can be used to recover that record.
addr = toAddr(nbytes, offset);
final long needed = (offset + nbytes) - userExtent;
if (offset + nbytes > Integer.MAX_VALUE) {
throw new RuntimeException(ERR_MAX_EXTENT);
}
if (needed > 0) {
if (!overflow(needed)) {
throw new OverflowException();
}
}
/*
* Note: The data MUST be copied within the synchronized() block
* since otherwise overflow() could cause the buffer reference to be
* invalidated. (Likewise, we do not "own" the limit and position on
* the [buffer] unless everyone is synchronized.)
*/
buffer.limit((int) offset + nbytes);
buffer.position((int) offset);
buffer.put(data);
// increment by the #of bytes written.
nextOffset.addAndGet(nbytes);
}
return addr;
}
public ByteBuffer read(final long addr) {
if (addr == 0L)
throw new IllegalArgumentException(ERR_ADDRESS_IS_NULL);
final long offset = getOffset(addr);
final int nbytes = getByteCount(addr);
if(nbytes==0) {
throw new IllegalArgumentException(ERR_RECORD_LENGTH_ZERO);
}
if (offset + nbytes > nextOffset.get()) {
throw new IllegalArgumentException(ERR_ADDRESS_NOT_WRITTEN);
}
/*
* Create a view onto the same buffer with distinct position, limit and
* mark. This allows us to perform concurrent reads on the buffer which
* in turn supports concurrent execution of concurrent transactions
* reading from various historical states.
*/
final ByteBuffer view;
synchronized(this) {
/*
* Note: Synchronization is required to have asReadOnlyBuffer()
* construct a consistent view with concurrent writers. If you do
* not synchronize then an IllegalArgumentException can get tossed
* out of here. The problem is that the operation is not atomic
* without synchronization (concurrent operations can modify the
* limit and position rendering the result inconsistent). This
* problem was revealed by the AbstractMRMWTestCase, but it does not
* show up on every run.
*/
view = buffer.asReadOnlyBuffer();
}
// return a read-only view onto the data in the store.
view.limit((int) offset + nbytes);
view.position((int) offset);
// the slice will show only the record being read.
return view.slice();
}
/**
* Note: This is synchronized since it MAY be invoked directly while
* concurrent writers are running and not just from {@link #overflow(long)}.
*/
synchronized public void truncate(final long newExtent) {
final long newUserExtent = newExtent - headerSize;
if (newUserExtent < getNextOffset() ) {
throw new IllegalArgumentException(ERR_TRUNCATE);
}
if (newUserExtent > Integer.MAX_VALUE) {
/*
* Constraint when using a buffered mode.
*/
throw new IllegalArgumentException(ERR_MAX_EXTENT);
}
if(newUserExtent == getUserExtent()) {
// NOP.
return;
}
final boolean isDirect = buffer.isDirect();
final int newCapacity = (int) newUserExtent;
final ByteBuffer tmp = (isDirect ? ByteBuffer
.allocateDirect(newCapacity) : ByteBuffer.allocate(newCapacity));
/*
* Copy at most those bytes that have been written on.
*/
buffer.limit((int)Math.min(nextOffset.get(),newCapacity));
buffer.position(0);
// Copy to the new buffer.
tmp.put(buffer);
// Replace the buffer reference.
buffer = tmp;
extent = newUserExtent + headerSize;
userExtent = newUserExtent;
if(log.isInfoEnabled())
log.info("Buffer: newCapacity=" + cf.format(newCapacity));
}
/**
* Note: This is synchronized so that concurrent writers must block during
* this operation.
*/
synchronized public long transferTo(final RandomAccessFile out)
throws IOException {
final long count = nextOffset.get();
final FileChannel outChannel = out.getChannel();
// current position on the output channel.
final long toPosition = outChannel.position();
// if(toPosition + count > Integer.MAX_VALUE) {
//
// throw new IOException("Would exceed int32 bytes.");
//
// }
/*
* Write all the data onto the output channel.
*/
// final long begin = System.currentTimeMillis();
// setup the buffer for the operation.
buffer.limit((int)count);//nextOffset);
buffer.position(0);
FileChannelUtility.writeAll(outChannel, buffer, toPosition);
outChannel.position(toPosition + count);
// // write the data : @todo use an explicit position for this write?
// final long nwritten = outChannel.write(buffer);
//
// if( nwritten != count ) {
//
// throw new AssertionError("Expected to write " + count
// + " bytes but wrote " + nwritten);
//
// }
//
// final long elapsed = System.currentTimeMillis() - begin;
//
// log.info("\nTransferred " + count
// + " bytes from memory to disk at offset=" + toPosition + " in "
// + elapsed + "ms");
return count;
}
/**
* FIXME Counters need to be added here for the {@link DirectBufferStrategy},
* {@link MappedBufferStrategy}, and {@link TransientBufferStrategy}.
*/
synchronized public CounterSet getCounters() {
if (root == null) {
root = new CounterSet();
}
return root;
}
private CounterSet root;
}