/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.map.diskmap;
/*
* A DiskPage has 3-4 DiskPageSections:
* - Erdo ids
* - Timestamps (leaf only)
* - Keys
* - Records
*/
import com.github.geophile.erdo.AbstractKey;
import com.github.geophile.erdo.AbstractRecord;
import com.github.geophile.erdo.immutableitemcache.CacheEntry;
import com.github.geophile.erdo.map.Factory;
import com.github.geophile.erdo.map.LazyRecord;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DiskPage extends CacheEntry<PageId, DiskPage>
{
// Object interface
@Override
public String toString()
{
return String.format("L%s%s", level, pageId);
}
// CacheEntry interface
@Override
public PageId id()
{
return pageId;
}
@Override
public DiskPage item()
{
return this;
}
@Override
public boolean okToEvict()
{
return !newlyAllocated && referenceCount.get() == 0;
}
@Override
public int referenceCount()
{
return referenceCount.get();
}
// DiskPage interface
public int nRecords()
{
return keySection.count();
}
public int level()
{
return level;
}
public boolean append(LazyRecord record) throws IOException, InterruptedException
{
boolean appended;
// Mark the DiskPageSections. If any append fails, or if they all succeed but the page has insufficient space,
// the DiskPageSections will be reset.
keySection.mark(); // TODO: Expose removeLast, and use mark/resetToMark internally in DiskPageSection
recordSection.mark();
// Append to each section.
try {
AbstractKey key = record.key();
erdoIdSection.append(key.erdoId());
if (timestampSection != null) {
timestampSection.append(key.transactionTimestamp());
}
keySection.append(key);
if (record.prefersSerialized()) {
recordSection.append(record.recordBuffer());
} else {
recordSection.append(record.materializeRecord());
}
// If we get this far, then all appends succeeded. Now check to see if the page has enough room.
appended = filledSize() <= pageSize;
} catch (BufferOverflowException e) {
appended = false;
}
if (!appended) {
// No room.
erdoIdSection.removeLast();
if (timestampSection != null) {
timestampSection.removeLast();
}
keySection.resetToMark();
recordSection.resetToMark();
}
return appended;
}
public void close()
{
assert nRecords() > 0;
if (!closed) {
// erdoId section
erdoIdSection.close();
// timestamp section
if (timestampSection != null) {
timestampSection.close();
}
if (level > 0) {
keyCache = new AbstractKey[keySection.count()];
}
// key section
keySection.close();
// record section
recordSection.close();
closed = true;
}
}
// If the key is present, return the record number of the record containing the key. Otherwise, return
// -p-1, where p is the position at which the key would be present if it were to be inserted. This is
// the same behavior as Arrays.binarySearch.
public int recordNumber(AbstractKey searchKey)
{
ByteBuffer searchBuffer = keySection.accessBuffer();
int lo = 0;
int hi = keySection.count() - 1;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
int c = key(searchBuffer, mid).compareTo(searchKey);
if (c < 0) {
lo = mid + 1;
} else if (c > 0) {
hi = mid - 1;
} else {
return mid; // key found
}
}
return -(lo + 1); // key not found.
}
public int pageAddress()
{
return pageAddress;
}
public AccessBuffers accessBuffers()
{
return new AccessBuffers(this);
}
public ByteBuffer pageBuffer()
{
return pageBuffer;
}
// For creating a new, empty page
public DiskPage(Factory factory,
PageId pageId,
int level,
int pageAddress,
ByteBuffer erdoIdBuffer,
ByteBuffer timestampBuffer,
ByteBuffer keyBuffer,
ByteBuffer recordBuffer)
{
this.factory = factory;
this.pageId = pageId;
this.pageSize = factory.configuration().diskPageSizeBytes();
this.pageAddress = pageAddress;
this.level = level;
this.pageBuffer = null;
// erdo ids
this.erdoIdSection = ErdoIdSection.forWrite(erdoIdBuffer, factory);
// timestamps
assert (level == 0) == (timestampBuffer != null);
this.timestampSection =
level == 0
? TimestampSection.forWrite(timestampBuffer, factory)
: null;
// keys
this.keySection = DiskPageSectionVariableLengthRecords.forWrite(0, keyBuffer);
// records
this.recordSection = DiskPageSectionVariableLengthRecords.forWrite(0, recordBuffer);
}
// For creating a page read from disk
public DiskPage(Factory factory, PageId pageId, int pageAddress, int level, ByteBuffer pageBuffer)
{
this.factory = factory;
this.pageId = pageId;
this.pageAddress = pageAddress;
this.pageSize = factory.configuration().diskPageSizeBytes();
this.level = level;
this.pageBuffer = pageBuffer;
// After each section is read, pageBuffer.position() points to the beginning of the next section.
// erdoIds
assert pageBuffer.position() == 0 : pageBuffer;
this.erdoIdSection = ErdoIdSection.forRead(pageBuffer);
int erdoIdSectionEnd = pageBuffer.position();
assert erdoIdSectionEnd == erdoIdSection.size() : pageBuffer;
// timestamps
int timestampSectionEnd;
if (level == 0) {
timestampSection = TimestampSection.forRead(pageBuffer);
timestampSectionEnd = pageBuffer.position();
assert timestampSectionEnd == erdoIdSectionEnd + timestampSection.size() : pageBuffer;
} else {
timestampSection = null;
timestampSectionEnd = erdoIdSectionEnd;
}
// keys
this.keySection = DiskPageSectionVariableLengthRecords.forRead(pageBuffer);
int keySectionEnd = pageBuffer.position();
assert keySectionEnd == timestampSectionEnd + keySection.size() : pageBuffer;
// records
this.recordSection = DiskPageSectionVariableLengthRecords.forRead(pageBuffer);
int recordSectionEnd = pageBuffer.position();
assert recordSectionEnd == keySectionEnd + recordSection.size();
assert recordSectionEnd <= pageSize;
if (level > 0) {
this.keyCache = new AbstractKey[keySection.count()];
}
}
public AbstractKey firstKey()
{
assert keySection.count() > 0 : this;
return key(keySection.accessBuffer(), 0);
}
public AbstractKey lastKey()
{
assert keySection.count() > 0 : this;
return key(keySection.accessBuffer(), keySection.count() - 1);
}
public AbstractKey readKey(int position, AccessBuffers accessBuffers)
{
AbstractKey key;
AccessBuffers repositioned = positionAccessBuffers(position, accessBuffers);
assert repositioned == accessBuffers;
key =
level == 0
? AbstractKey.deserialize(factory,
accessBuffers.keyBuffer(),
erdoIdSection.erdoId(position),
timestampSection.timestamp(position))
: AbstractKey.deserialize(factory,
accessBuffers.keyBuffer(),
erdoIdSection.erdoId(position));
return key;
}
public AbstractRecord readRecord(int position, AccessBuffers accessBuffers)
{
AbstractRecord record;
AccessBuffers repositioned = positionAccessBuffers(position, accessBuffers);
assert repositioned == accessBuffers;
if (level == 0) {
record = AbstractRecord.deserialize(factory,
accessBuffers,
erdoIdSection.erdoId(position),
timestampSection.timestamp(position));
} else {
record = IndexRecord.deserialize(factory, accessBuffers, erdoIdSection.erdoId(position));
}
return record;
}
public AccessBuffers positionAccessBuffers(int position, AccessBuffers recordReference)
{
assert position >= 0 && position < keySection.count() : position;
if (recordReference == null || recordReference.underlyingArray() != pageBuffer.array()) {
recordReference = new AccessBuffers(this);
}
keySection.setBoundariesInBuffer(position, recordReference.keyBuffer);
recordSection.setBoundariesInBuffer(position, recordReference.recordBuffer);
return recordReference;
}
public void addReference(boolean randomRead)
{
// DO NOT CHANGE THE ORDER OF THE NEXT TWO STATEMENTS!
// Setting newlyAllocated to false before referenceCount is incremented would create a window in which
// a newly allocated page can be evicted before the reference count is incremented.
referenceCount.incrementAndGet();
newlyAllocated = false;
if (LOG.isLoggable(Level.FINEST)) {
LOG.log(Level.FINEST, "{0}: add reference -> {1}", new Object[]{this, referenceCount});
LOG.log(Level.FINEST, "stack", new Exception());
}
}
public void removeReference()
{
referenceCount.decrementAndGet();
if (LOG.isLoggable(Level.FINEST)) {
LOG.log(Level.FINEST, "{0}: remove reference -> {1}", new Object[]{this, referenceCount});
LOG.log(Level.FINEST, "stack", new Exception());
}
}
// For use by this package
int filledSize()
{
assert !closed;
int filledSize = keySection.size() + recordSection.size() + erdoIdSection.size();
if (timestampSection != null) {
filledSize += timestampSection.size();
}
return filledSize;
}
// For use by this class
private AbstractKey key(ByteBuffer keyBuffer, int position)
{
AbstractKey key = null;
if (keyCache != null) {
key = keyCache[position];
}
if (key == null) {
keySection.setBoundariesInBuffer(position, keyBuffer);
int erdoId = erdoIdSection.erdoId(position);
key = factory.recordFactory(erdoId).newKey();
key.readFrom(keyBuffer);
keyBuffer.clear();
key.erdoId(erdoId);
if (timestampSection != null) {
key.transactionTimestamp(timestampSection.timestamp(position));
}
if (keyCache != null) {
keyCache[position] = key;
}
}
return key;
}
// Class state
private static final Logger LOG = Logger.getLogger(DiskPage.class.getName());
// Object state
private final Factory factory;
private final PageId pageId;
private final int pageAddress;
private final int pageSize;
private final int level;
private final ByteBuffer pageBuffer;
private final ErdoIdSection erdoIdSection;
private final TimestampSection timestampSection;
private final DiskPageSection keySection;
private final DiskPageSection recordSection;
private AbstractKey[] keyCache;
private boolean newlyAllocated = true;
private boolean closed = false;
// For management of disk pages by DiskPageCache and PageMemoryManager
private AtomicInteger referenceCount = new AtomicInteger(0);
// Inner classes
public static class AccessBuffers
{
public ByteBuffer keyBuffer()
{
return keyBuffer;
}
public ByteBuffer recordBuffer()
{
return recordBuffer;
}
byte[] underlyingArray()
{
return keyBuffer.array();
}
AccessBuffers(DiskPage diskPage)
{
keyBuffer = diskPage.keySection.accessBuffer();
recordBuffer = diskPage.recordSection.accessBuffer();
}
final ByteBuffer keyBuffer;
final ByteBuffer recordBuffer;
}
}